awesome-everything EN
↑ Обратно к восхождению

Архитектура бэкенда

Зачем пул: цена создания соединения

Суть Открыть соединение с базой недёшево — TCP, TLS и аутентификация стоят миллисекунд, а сервер выделяет целый backend-процесс. Пул создаёт небольшой набор соединений один раз и одалживает их, поэтому запросы платят за setup почти никогда вместо каждого раза.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 11 min

Junior-сервис открывает свежее соединение с Postgres на каждый запрос, делает один быстрый SELECT и закрывает его. Запрос занимает 1 мс; обработка занимает 35. Недостающие 34 мс — чистый setup: TCP-рукопожатие, TLS-рукопожатие и база, аутентифицирующая пользователя и форкающая backend-процесс — каждый раз, выброшенное после одного запроса. Под нагрузкой база тратит больше CPU на создание и уничтожение соединений, чем на ответы. Никто не написал медленный запрос. Написали быстрый запрос, обёрнутый в дорогой ритуал, повторяемый на каждом запросе.

Соединение дорого создавать

Новичку соединение с базой кажется бесплатным — вызови connect(), получи объект. Под капотом открытие одного — цепочка медленных шагов:

  • TCP-рукопожатие — круговой обмен для установки сокета (один RTT до того, как потекут байты).
  • TLS-рукопожатие — настройка шифрования, 2 круговых обмена на TLS 1.3, 3 на TLS 1.2; на канале с реальной задержкой это одно — десятки-сотни миллисекунд.
  • Аутентификация — база проверяет учётные данные, часто со своим challenge/response-обменом.
  • Выделение backend — Postgres форкает выделенный OS-процесс на соединение; MySQL порождает поток. Этот процесс резервирует память (~2–3 МБ общей/служебной памяти до того, как выполнит хоть один запрос).

Так что соединение — не дешёвая ручка, это TCP-сокет плюс TLS-сессия плюс живой серверный процесс. Создавать и сносить его на запрос значит платить всё это за работу в один запрос.

Пул амортизирует цену

Пул соединений — небольшой долгоживущий набор уже открытых соединений, держимых наготове. Вместо connect → query → close поток становится:

  1. Взять (acquire/borrow) готовое соединение из пула.
  2. Выполнить запрос на нём.
  3. Вернуть (release) его в пул — не закрыть, лишь пометить свободным для следующего запроса.

Дорогие рукопожатия случаются раз, когда пул наполняется, затем те же соединения переиспользуются тысячи раз. Это амортизация: фиксированная цена setup, размазанная по многим использованиям, пока доля на запрос не станет ничтожной. Запрос теперь платит ~0 мс setup вместо ~34, а база перестаёт жечь CPU на churn соединений.

Почему это работает

Зачем держать соединения открытыми и простаивающими, а не открывать по требованию и закрывать по завершении? Затем, что весь смысл — не платить за setup на пути запроса. Простаивающее пулевое соединение стоит немного памяти на клиенте и сервере, но оно мгновенно доступно — взятие почти бесплатная внутрипроцессная операция. Альтернатива, открытие на запрос, переносит задержку рукопожатия прямо во время ожидания пользователя и превращает базу в фабрику соединений под нагрузкой. Пул меняет небольшую, ограниченную, постоянную цену (горстка простаивающих соединений) на устранение большой, повторяемой, на-каждый-запрос цены. Этот размен почти всегда оправдан для сервера, обрабатывающего больше чем струйку трафика.

Пул — это ещё и лимит

Переиспользование — очевидная польза, но вторая работа пула важна не меньше: это граница. Пул размера 20 значит, что от этого сервиса к базе одновременно существует не более 20 соединений, сколько бы запросов ни пришло. Этот потолок — фича: он защищает базу, которая может держать лишь столько backend-ов, прежде чем упадёт (Postgres по умолчанию max_connections = 100). Без пула всплеск в 5 000 конкурентных запросов попытался бы открыть 5 000 соединений, и база отвергла бы их «too many clients». Пул превращает неограниченный спрос в фиксированное, выживаемое число — та же идея ограниченной конкурентности из прошлого юнита, применённая к самому дорогому downstream-ресурсу бэкенда.

Соединение на запросПулевые соединения
Цена setup платитсяКаждый запрос (TCP+TLS+auth)Раз, при наполнении пула
Задержка на запрос+ десятки мс рукопожатия~0 мс (внутрипроцессное взятие)
Нагрузка на стороне БДПостоянный fork/teardown churnСтабильный набор backend-ов
Конкурентность к БДНеограниченная (всплеск → перегруз)Ограничена размером пула
Отказ при всплеске«too many clients», отказЗапросы ждут или падают быстро
Викторина

Хэндлер выполняет запрос на 1 мс, но каждый запрос занимает ~35 мс целиком, и CPU базы высок, хотя запросы тривиальны. Какова вероятнейшая причина?

Викторина

Помимо переиспользования соединений, какую вторую защиту даёт бэкенду фиксированный пул?

Расставь шаги по порядку

Упорядочи жизненный цикл одного запроса с пулом соединений:

  1. 1 Взять готовое соединение из пула
  2. 2 Выполнить запрос на одолженном соединении
  3. 3 Вернуть соединение в пул (свободно, не закрыто)
  4. 4 Поздний запрос берёт то же соединение и переиспользует его
Вспомните перед уходом
  1. 01
    Почему создание соединения с базой дорого, шаг за шагом?
  2. 02
    Как работает пул соединений и что значит амортизация здесь?
  3. 03
    Почему пул — это ещё и граница, и почему это важно?
Итог

Соединение с базой выглядит бесплатным, но это цепочка дорогого setup: TCP-рукопожатие, TLS-рукопожатие (два круговых обмена на 1.3, три на 1.2), аутентификация и серверный backend-процесс, резервирующий пару мегабайт до любой работы. Открытие одного на запрос хоронит десятки миллисекунд рукопожатия в каждом вызове и заставляет базу жечь CPU на форк и снос процессов — быстрый запрос, обёрнутый в дорогой ритуал. Пул соединений чинит обе половины: открывает небольшой набор соединений раз и одалживает их (взять, запрос, вернуть-не-закрыть), амортизируя setup до почти нуля на запрос, и ограничивает общее число соединений своим размером, защищая базу со всего 100 backend-ами по умолчанию от перегруза-всплеском ‘too many clients’. Пул — это ограниченная конкурентность для самого дорогого ресурса, которого касается бэкенд. Но пул помогает лишь при правильном размере — и следующий урок покажет, почему больше контринтуитивно не лучше и как вычислить число, реально максимизирующее пропускную способность.

Связанные уроки
встречается в185
Продолжить восхождение ↑Размер пула: почему больше не значит быстрее
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.