СИЭУиП • Конспект лекций • Занятие 27

Архитектура исполняемого файла проекта: подключение к СУБД и пул соединений

Фундаментальный теоретический конспект для студентов: сетевой жизненный цикл соединения, архитектура Connection Pool, синтаксис конфигураций (JSON, YAML, .env) и вопросы для самопроверки.

1. Сетевое взаимодействие клиентского приложения и СУБД

Любая современная реляционная база данных (PostgreSQL, MySQL, Microsoft SQL Server) в архитектуре информационной системы функционирует как независимый сетевой сервис. Исполняемый файл клиентского приложения (Desktop GUI, Web API Backend, консольная утилита) взаимодействует с СУБД по сетевому протоколу поверх стека TCP/IP.

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

+-----------------------------------------------------------------------------------+ | ПОЛНЫЙ ЖИЗНЕННЫЙ ЦИКЛ ПОДКЛЮЧЕНИЯ К СУБД (HANDSHAKE) | +-----------------------------------------------------------------------------------+ [Клиентское приложение] [Сервер СУБД (PostgreSQL)] | | (1) |------ TCP SYN (Port 5432) ---------------------------------->| (L4 Socket) |<----- TCP SYN-ACK -------------------------------------------| |------ TCP ACK ---------------------------------------------->| [TCP сокет открыт] | | (2) |------ SSL Request Code (80877103) -------------------------->| (L7 TLS) |<----- Server 'S' (SSL Supported) ----------------------------| |<====> TLS Handshake: Exchange Certificates, ECDHE Keys <====>| [Канал зашифрован] | | (3) |------ StartupMessage (user=app_user, database=prod_db) ----->| (Аутентификация) |<----- AuthenticationSASL (SCRAM-SHA-256 challenge) ---------| |------ SASL Client Proof (Хэшированный ответ) --------------->| |<----- AuthenticationOk (Postgres Backend процесс выделен) ----| [Сессия создана] | | (4) |======================== ПУЛ СОЕДИНЕНИЙ ======================| | Соединение помещается в пул готовых сокетов (Ready State) | | | (5) |------ Query: "SELECT 1;" (Аренда сокета из пула) ----------->| (Выполнение) |<----- Data Rows + CommandComplete ('SELECT 1') --------------| | | (6) |==> Сокет освобождается и ВОЗВРАЩАЕТСЯ В ПУЛ (НЕ ЗАКРЫВАЕТСЯ) | +--------------------------------------------------------------+

В отсутствие пула соединений шаги (1)–(3) повторяются на каждый запрос, отнимая от 80 до 200 миллисекунд процессорного времени и сетевых задержек. При высокой нагрузке сервер СУБД тратит все ресурсы на создание процессов и падает.

2. Строка подключения (Connection String) и её параметры

Connection String — это стандартизированная строка, содержащая полную метаинформацию, необходимую драйверу для установления физического сокета с базой данных.

Стандарт URI (Uniform Resource Identifier)

Используется в ORM, современных веб-фреймворках (Node.js Prisma, Python SQLAlchemy, Go GORM):

postgresql://user:password@hostname:5432/dbname?sslmode=verify-full&connect_timeout=5

Стандарт Key-Value (libpq)

Используется в классических утилитах PostgreSQL (psql, pg_dump), C/C++, .NET Npgsql:

host=hostname port=5432 dbname=dbname user=user password=password sslmode=verify-full connect_timeout=5

Детальная анатомия параметров

3. Архитектура и принципы работы пула соединений (Connection Pool)

Пул соединений — это программный кэш открытых физических соединений с СУБД, управляемый клиентом или выделенным прокси-сервером (pgBouncer, HikariCP, c3p0, SQLAlchemy Pool).

Жизненный цикл сокета в пуле соединений:

  1. Инициализация пула (Pool Warm-up): при старте приложения создается min_connections (например, 2 или 5) готовых физических TCP-сессий с СУБД.
  2. Аренда (Acquire / Borrow): рабочий поток приложения запрашивает соединение у пула: conn = pool.get_connection(). Задержка составляет менее 1 мс.
  3. Выполнение транзакции (Transaction Execution): через полученный сокет отправляются SQL-команды.
  4. Освобождение (Release / Return): поток вызывает conn.close(), но драйвер не разрывает TCP-сокет, а возвращает его обратно в статус доступных для других потоков.
  5. Очистка по бездействию (Idle Eviction): если число соединений превышает минимум и сокет простаивает дольше idle_timeout, он безопасно закрывается.

Критическая опасность: Утечка соединений (Connection Leak)

Если разработчик арендовал сокет из пула и забыл вызвать возврат (или возникло неотловленное исключение до вызова закрытия), сокет навсегда выбывает из пула. Когда свободные сокеты заканчиваются, новые запросы становятся в очередь и падают с ConnectionTimeoutException (Pool exhausted).

Золотое правило: всегда использовать автоматическое управление ресурсами:

# Python: контекстный менеджер гарантирует возврат в пул даже при падении!
with db_pool.connection() as conn:
    with conn.cursor() as cur:
        cur.execute("SELECT 1")
        result = cur.fetchone()

// C#: блок using гарантирует вызов Dispose() и возврат в пул
using (var conn = new NpgsqlConnection(connString)) {
    conn.Open();
    // выполнение операций
}

4. Синтаксис и форматы файлов конфигурации

В соответствии с методологией 12-Factor App (пункт III. Config), параметры подключения строго отделяются от исходного кода программы.

1. Формат JSON (config.json)

Иерархический, типизированный, встроенный парсер во всех языках:

{
  "database": {
    "host": "127.0.0.1",
    "port": 5432,
    "name": "warehouse_db",
    "user": "app_user",
    "password": "SecurePassword123!",
    "pool": {
      "min_size": 2,
      "max_size": 5,
      "idle_timeout_sec": 300
    },
    "ssl": {
      "mode": "verify-full",
      "ca_path": "/etc/ssl/ca.crt"
    }
  }
}

2. Формат YAML (config.yaml)

Лаконичный, человекочитаемый синтаксис на основе отступов:

database:
  host: "127.0.0.1"
  port: 5432
  name: "medical_records"
  user: "registry_svc"
  password: "RegistrySecretKey#2026"
  pool:
    min_connections: 2
    max_connections: 10
    idle_timeout: 300
  ssl:
    mode: "verify-full"
    ca_cert: "./certs/root-ca.crt"
    client_cert: "./certs/client.crt"
    client_key: "./certs/client.key"

3. Формат переменных окружения (.env)

Стандарт для контейнеризации (Docker, Kubernetes) и микросервисов. Файл .env никогда не добавляется в Git!

DB_HOST=127.0.0.1
DB_PORT=5432
DB_NAME=college_portal
DB_USER=portal_admin
DB_PASSWORD=StrongPortalPassword2026!
DB_POOL_MIN=2
DB_POOL_MAX=8
DB_CONNECT_TIMEOUT=5
DB_SSLMODE=verify-full

5. Диагностика и логирование сетевых ошибок

При запуске исполняемого модуля первоочередной задачей является выполнение Health Check (тестового пинга) базы данных запросом SELECT 1;. Если СУБД недоступна, приложение должно перехватить исключение и записать событие в лог:

[2026-10-01 10:15:32.418] [ERROR] [DB_CONN] Failed to connect to PostgreSQL at 127.0.0.1:5432.
Error Code: ECONNREFUSED (SQLState: 08001).
Message: Connection refused. Is the database server running on host "127.0.0.1" and accepting TCP/IP connections?
Action: Retrying in 5 seconds (Attempt 1 of 3)...

6. Вопросы для самопроверки студентов

1. В чем заключается физическое отличие открытия нового TCP-соединения от аренды сокета из пула соединений?
Открытие нового соединения требует тройного рукопожатия TCP, TLS-хэндшейка, выделения памяти и порождения процесса postgres backend на сервере (задержка 100–200 мс). Аренда из пула использует уже открытый прогретый сокет за 0.2–0.5 мс.
2. Чем опасен режим sslmode=disable и в каких случаях обязателен sslmode=verify-full?
При sslmode=disable все учетные данные и запросы передаются открытым текстом и могут быть перехвачены сниффером. Режим verify-full обязателен для защиты от подделки сервера и атак Man-in-the-Middle при передаче персональных данных.
3. Что происходит с приложением, если в коде возникает Connection Leak (утечка соединений)?
Соединения не возвращаются в пул, лимит max_connections исчерпывается, все последующие запросы становятся в очередь и завершаются аварийным отказом по таймауту.
4. Почему параметр connect_timeout не рекомендуется оставлять равным 0 (бесконечность)?
При разрыве сетевого соединения или зависании узла поток приложения зависнет навсегда в ожидании TCP ACK, расходуя системную память и блокируя выполнение программы.
5. Почему файл с переменными окружения (.env) категорически запрещено публиковать в системе контроля версий?
Файл содержит открытые секреты (пароли, закрытые ключи SSL). При попадании в Git они становятся доступны третьим лицам и приводят к компрометации всей базы данных.