← Все статьи

Как устроен современный веб: от браузера до базы данных

Полный путь HTTP-запроса — DNS, TLS, CDN, балансировщик, Nginx, backend, Redis, очереди, PostgreSQL и обратно в браузер. Практический разбор для разработчиков и архитекторов.

Содержание

Сегодня открытие любой веб-страницы запускает цепочку из десятков технологий, сервисов и протоколов. Пользователь нажимает ссылку — и через доли секунды видит готовый интерфейс, хотя за кликом скрыты браузер, DNS, сеть доставки контента, балансировщики, веб-серверы, приложения, кэши, очереди и базы данных. В этой статье пройдём весь путь запроса: от ввода https://example.com до записи в PostgreSQL и возврата ответа обратно в браузер.

Ключевые выводы

Веб — это распределённая система, а не «сайт на сервере». Даже простая страница проходит через DNS, TCP/TLS, возможно CDN и reverse proxy, прежде чем код приложения увидит запрос. Понимание каждого звена помогает отличать проблему сети от проблемы SQL.

Границы ответственности разделены намеренно. Веб-сервер (Nginx, Caddy) принимает соединения и раздаёт статику; приложение (NestJS, Laravel, Go) содержит бизнес-логику; БД хранит состояние. Смешивать роли удобно на старте и дорого при росте.

Кэш и асинхронность — не оптимизация «на потом». Browser cache, CDN, Redis и очереди сообщений определяют, выдержит ли продукт пик нагрузки без деградации латентности. Синхронная отправка email в HTTP-обработчике — архитектурный антипаттерн.

Фронтенд 2020-х — продолжение backend, а не замена. React, SSR, SSG и гидратация решают задачи первой отрисовки и интерактивности, но не отменяют API, авторизацию и данные на сервере. История смены подходов — в отдельном обзоре эволюции веб-архитектур.

Без наблюдаемости систему не сопровождать. Логи, метрики и трейсинг (OpenTelemetry, Prometheus, Grafana) — часть архитектуры, когда сервисов больше одного.


Введение: зачем разбирать путь запроса целиком

Разработчик, который видит только свой слой — React-компонент или SQL-запрос — часто диагностирует симптомы, а не причину. «Страница тормозит» может означать отсутствие индекса в PostgreSQL, промах CDN, блокирующий middleware или тяжёлый JavaScript-бандл. Карта полного пути превращает догадки в чеклист.

Представьте инцидент: пользователи жалуются на медленное оформление заказа. Фронтенд-разработчик смотрит на Waterfall в DevTools и видит API POST /orders — 4 секунды. Бэкендер открывает APM и обнаруживает, что 3,8 с уходит на один SQL без индекса. DevOps параллельно замечает, что балансировщик шлёт трафик на инстанс с 98% CPU из‑за утечки памяти в воркере. Без общей карты каждый чинит «свою» часть, а корневая причина остаётся. Полный путь запроса — язык, на котором говорят фронт, бэк, DBA и SRE.

Дальше мы идём в том же порядке, в каком пакеты и данные движутся по сети: от URL в адресной строке до JSON в DevTools и обратно. Там, где уместно, приводим примеры из типичного стека (Nginx, Node/NestJS, PostgreSQL, Redis), но принципы переносятся на PHP, Go и Java без переписывания логики.


Что происходит после ввода адреса сайта

Пользователь вводит https://example.com/products или переходит по ссылке. Браузеру нужно ответить на три вопроса: какой IP-адрес обслуживает хост, как установить защищённое соединение и какой именно ресурс запросить (/products).

Пока DNS не вернёт адрес, TCP-соединение не начнётся. Пока не завершится TLS-handshake, HTTP-запрос не уйдёт в открытом виде. Эти шаги занимают миллисекунды при тёплом кэше и сотни миллисекунд при холодном старте на другом континенте.

DNS — интернетный «телефонный справочник»

Браузер не подключается к доменным именам напрямую — ему нужен IP-адрес, например 192.0.2.1. За преобразование example.com → IP отвечает DNS (Domain Name System).

Типичная цепочка резолвинга:

  1. Кэш браузера и ОС — если адрес недавно запрашивали, ответ уже известен.
  2. DNS Resolver провайдера или публичный (1.1.1.1, 8.8.8.8) — первый внешний узел.
  3. Корневые серверы — указывают, где искать зону .com.
  4. TLD-серверы — делегируют на авторитетные серверы домена.
  5. Авторитетные DNS-сервера домена — возвращают A/AAAA-записи (и часто CNAME на CDN).

Кэширование DNS снижает нагрузку и ускоряет повторные визиты, но создаёт задержку распространения изменений (TTL). При миграции хостинга инженеры уменьшают TTL заранее — с 86400 до 300 секунд за сутки до переключения — иначе часть пользователей ещё сутки ходит на старый IP.

Помимо A-записей (IPv4) и AAAA (IPv6), на практике важны CNAME (алиас на CDN или другой хост), MX (почта), TXT (верификация, SPF, DKIM). Для веб-приложения чаще всего критичны A/AAAA или CNAME на edge-провайдера. Ошибка в DNS — «сайт лежит» при живом сервере: браузер стучится не туда.

Инструменты вроде dig example.com, nslookup и панели Cloudflare помогают отладить цепочку без гадания. Если резолвинг занимает секунды, проверьте TTL, перегрузку resolver и блокировки корпоративного DNS.


Установка соединения

TCP Handshake

После получения IP браузер открывает TCP-соединение с сервером на порту 443 (HTTPS). Классический трёхсторонний handshake:

  • клиент отправляет SYN;
  • сервер отвечает SYN-ACK;
  • клиент подтверждает ACK.

Соединение нельзя «открыть мгновенно»: каждый round-trip добавляет латентность. На мобильных сетях с высоким RTT (100–200 ms) только TCP+TLS могут стоить четверть секунды до первого байта HTTP. HTTP/2 мультиплексирует несколько запросов в одном соединении; HTTP/3 поверх QUIC переносит handshake ближе к одному RTT и лучше переживает смену сети (Wi‑Fi → LTE), потому что идентификатор соединения не жёстко привязан к IP.

Пул соединений (keep-alive) критичен: без него каждый asset на странице платил бы полный TCP+TLS заново. Браузеры и современные HTTP-клиенты переиспользуют соединения по умолчанию — ещё одна причина не отключать keep-alive на Nginx без веской нужды.

TLS и HTTPS

Поверх TCP начинается TLS (преемник SSL). Упрощённо этапы: ClientHello (версии, шифры) → ServerHello + сертификат → проверка цепочки до корневого CA → обмен ключами → зашифрованный канал.

Браузер проверяет, что имя в сертификате совпадает с доменом (SAN), срок действия, отзыв (OCSP stapling на хорошо настроенных серверах). Невалидный или просроченный сертификат — предупреждение или блокировка страницы. Let's Encrypt и аналоги сделали HTTPS массовым: автоматическое продление через ACME, бесплатные сертификаты на каждый поддомен.

HTTPS сегодня — базовый стандарт: защита паролей и cookie от перехвата в публичном Wi‑Fi, целостность данных (никто не подменит JS по пути), доверие поисковых систем и браузеров (HTTP помечается как небезопасный). HSTS заголовок заставляет браузер всегда использовать HTTPS для домена — защита от downgrade-атак.


HTTP-запрос

Когда канал готов, браузер отправляет HTTP-запрос. Пример:

GET /products HTTP/1.1
Host: example.com
Accept: text/html,application/json
Accept-Language: ru-RU,ru;q=0.9
User-Agent: Mozilla/5.0 ...
Cookie: session_id=abc123
Authorization: Bearer eyJhbG...

Метод определяет семантику: GET — чтение, POST — создание, PUT/PATCH — обновление, DELETE — удаление. Идемпотентность и кэшируемость зависят от метода — это влияет на проектирование API.

Заголовки несут метаданные: тип контента (Content-Type), язык, аутентификация (Authorization, Cookie), кэш-директивы (Cache-Control), CORS при cross-origin запросах.

Тело запроса используется в POST/PUT/PATCH — JSON формы заказа, загрузка файла, GraphQL payload. Для GET тело обычно пустое; параметры — в query string (?page=2).

Ответ сервера тоже следует той же модели: статус-код (200, 201, 400, 401, 404, 500), заголовки (Set-Cookie, Location при редиректе) и тело. Коды сгруппированы: 2xx успех, 3xx перенаправление, 4xx ошибка клиента, 5xx ошибка сервера. API проектируют так, чтобы фронт мог различать «не найдено» и «нет прав» без разбора HTML-страницы ошибки.

HTTP/1.1 держит одно запрос-ответное сообщение за раз на соединении (без pipelining в типичных клиентах); HTTP/2 — бинарные фреймы, несколько потоков, сжатие заголовков HPACK. Для разработчика это значит: десятки мелких запросов на одной странице перестали быть катастрофой для latency, но размер заголовков (большие cookie) по-прежнему бьёт по сети.


CDN — почему сайт открывается быстро

CDN (Content Delivery Network) — сеть edge-серверов в десятках городов. Статика (изображения, CSS, JS, шрифты) и иногда целые HTML-страницы отдаются с ближайшей точки, а не с единственного датацентра в США.

Пользователь из Москвы может получить app.js из европейского PoP, а не ждать трансатлантический канал. Провайдеры вроде Cloudflare, Fastly, Akamai кэшируют ответы по правилам Cache-Control и собственным политикам.

Именно поэтому изображения и бандлы редко грузятся с origin-сервера при каждом визите. Origin остаётся источником истины; CDN — ускоритель и щит от DDoS. Динамические API-запросы (POST /orders) чаще идут на origin, но и их можно кэшировать осторожно (GraphQL CDN, edge functions).

Важны директивы кэша: max-age — сколько секунд ответ свежий; s-maxage — для shared cache (CDN); stale-while-revalidate — отдать устаревший ответ и обновить в фоне. Пурж (инвалидация) по URL или тегу после деплоя — обязательная операция, иначе пользователи видят старый app.js с багом, который вы уже исправили.

На практике статику кладут на CDN с длинным max-age и fingerprint в имени файла (app.a1b2c3.js), а HTML — с коротким кэшем или без него, чтобы ссылки на новые assets подхватывались сразу.


Балансировщик нагрузки

Продакшен-сайт редко живёт на одной виртуальной машине. Перед пулом приложений стоит балансировщикNginx, HAProxy, AWS Application Load Balancer, GCP Load Balancer.

Задачи балансировщика: распределить трафик между здоровыми инстансами, снять SSL (termination), проверять health checks, маршрутизировать по пути (/api → backend, / → frontend).

Алгоритмы: Round Robin по кругу, Least Connections на наименее загруженный узел, Weighted — разный вес для мощных машин. При падении одного сервера трафик уходит на остальные — пользователь не видит «сайт лёг», если пул достаточен.

Sticky sessions (привязка клиента к одному инстансу) нужны, если сессия хранится в памяти процесса — антипаттерн для масштабирования, но ещё встречается. Лучше вынести сессии в Redis. Health check обычно бьёт в GET /health каждые 5–10 секунд; неживой инстанс исключается из пула до восстановления.

На уровне облака балансировщик часто работает на L4 (TCP) или L7 (HTTP с маршрутизацией по path и host). L7 позволяет направить api.example.com и www.example.com на разные target group без отдельных IP.


Веб-сервер

За балансировщиком (или вместо него на малых проектах) — веб-сервер: Nginx, Apache, Caddy.

Он принимает TCP-соединения, завершает TLS (или получает уже расшифрованный трафик), раздаёт статические файлы с диска, проксирует динамические запросы на upstream (приложение), сжимает ответы (gzip, brotli), ограничивает rate limit.

Веб-сервер ≠ приложение. Nginx не должен содержать бизнес-правила заказов — он эффективно двигает байты и защищает upstream от медленных клиентов. Путаница ролей приводит к PHP/Node внутри Apache без пула воркеров и к «одному процессу на всё».

Nginx использует event-driven модель: мало процессов обслуживают тысячи соединений. worker_connections, буферы proxy_buffer_size, таймауты proxy_read_timeout — типичные ручки тюнинга. Caddy выделяется автоматическим HTTPS; Apache с mod_php исторически популярен на shared hosting, но в облаках чаще связка Nginx + FPM/Node upstream.

Типичный location /api/ проксирует на http://127.0.0.1:3000, а location / отдаёт root /var/www/dist для SPA или проксирует на SSR-сервер.


Сервер приложения

Здесь живёт бизнес-логика. Стек выбирают по команде, экосистеме и ограничениям:

PHP: PHP-FPM + Laravel / Symfony — зрелый хостинг, быстрый CRUD, огромная кодовая база в интернете.

Node.js: Express, NestJS, Fastify — один язык с фронтом, event loop, JSON API; важно не блокировать поток тяжёлой CPU-работой.

Java: Spring Boot — энтерпрайз, строгая типизация, богатая экосистема.

.NET: ASP.NET Core — производительность, интеграция с Microsoft-стеком.

Go: Gin, Fiber — компилируемые бинарники, низкое потребление памяти, сильный concurrency.

Приложение слушает внутренний порт (3000, 8080, 9000), недоступный из интернета напрямую — только через Nginx. В контейнерах тот же порт публикуется в docker network; снаружи открыт только ingress.

Процессы приложения часто запускают через systemd, PM2 (Node), supervisor или оркестратор (Kubernetes Deployment с несколькими репликами). Горизонтальное масштабирование — добавление одинаковых инстансов за балансировщиком; вертикальное — больше CPU/RAM на одну машину. Для CPU-bound Node иногда выгоднее несколько воркеров на одном хосте, чем один гигантский процесс.


Что происходит внутри приложения

Рассмотрим POST /orders — создание заказа.

Роутинг сопоставляет URL и метод с обработчиком: router.post('/orders', createOrder).

Middleware — цепочка до контроллера: парсинг JSON, CORS, логирование, аутентификация, проверка прав, валидация схемы (Zod, class-validator).

Контроллер извлекает тело запроса, вызывает сервис, формирует HTTP-ответ (статус, JSON).

Сервисный слой — правила: «можно ли заказать при нулевом остатке», расчёт скидки, вызов платёжного шлюза.

Репозиторий (или ORM) — OrderRepository.save(), SQL через Prisma, TypeORM, Eloquent.

Такое разделение упрощает тесты: сервис мокает репозиторий, контроллер мокает сервис.

Пошагово для POST /orders с телом {"productId":42,"qty":2}: middleware auth читает JWT из заголовка и кладёт userId в контекст; validate проверяет схему; контроллер вызывает orderService.create(userId, items); сервис в транзакции проверяет остаток на складе, резервирует товар, вызывает paymentClient.charge(), сохраняет заказ через репозиторий, публикует событие OrderCreated в очередь для email; контроллер возвращает 201 с {orderId}. Любой сбой на шаге оплаты откатывает транзакцию — пользователь не видит «заказ создан», если деньги не списались.

Ошибки маппятся в HTTP осмысленно: 409 Conflict при нехватке товара, 402/422 при отказе платёжки, 500 только для неожиданных исключений с логированием stack trace на сервере, а не в ответе клиенту.


Аутентификация и авторизация

Сервер должен понять кто вызывает API и что ему разрешено.

Сессии: после логина сервер создаёт запись в Redis/БД, клиент хранит session_id в cookie. Плюсы — отзыв сессии на сервере; минусы — sticky sessions, состояние на сервере. Подробнее о рисках cookie и JWT в Node — в материале про ошибки безопасности JWT.

JWT: самодостаточный токен с подписью; сервер не хранит сессию, проверяет криптографию. Удобно для микросервисов и SPA; сложнее отозвать до истечения срока.

OAuth 2.0 / OpenID Connect: вход через Google, GitHub, Microsoft — делегирование идентификации провайдеру; ваш backend получает код и меняет на токены.

Авторизация (роли, permissions) часто живёт отдельно: RBAC, ABAC, policy в middleware.

Для cookie с сессией критичны флаги HttpOnly (JS не читает), Secure (только HTTPS), SameSite (защита от CSRF). JWT в localStorage удобен для SPA, но уязвим к XSS — любой внедрённый скрипт украдёт токен. Компромисс: короткоживущий access token + refresh в HttpOnly cookie. Подробнее о типичных дырах — в материале про безопасность Node.js.


Кэширование

Без кэша современный веб не масштабируется. Уровни:

Browser cache — по заголовкам Cache-Control, ETag; повторный визит не качает те же assets.

CDN cache — статика и cacheable API на edge.

Reverse proxy cache — Nginx кэширует ответы upstream по ключу URL.

Redis (application cache) — горячие данные: профиль пользователя, каталог категорий, результаты тяжёлых агрегаций. Инвалидация — самая сложная часть; паттерны разобраны в статье про кэширование в Node.

Правильный кэш ускоряет систему в десятки раз и разгружает БД; неправильный — отдаёт устаревшие цены.

Типичная ошибка — cache stampede: ключ истёк, тысяча запросов одновременно идут в БД. Решения: jitter к TTL, single-flight (один запрос наполняет кэш, остальные ждут), mutex в Redis. Для каталога с редкими обновлениями часто ставят TTL 5–15 минут и ручной purge при изменении цены.

Паттерн cache-aside: приложение сначала читает Redis; при промахе — SQL, затем записывает в Redis. Write-through обновляет кэш вместе с БД — сложнее, но консистентнее для критичных данных.


Очереди сообщений

Не каждую операцию нужно делать в HTTP-запросе. Отправка email, генерация PDF, ресайз изображений, синхронизация с CRM — задачи для очередей: RabbitMQ, Apache Kafka, Redis (Bull/BullMQ), Amazon SQS.

Паттерн: API кладёт сообщение в очередь и сразу отвечает 202 Accepted с job_id; воркеры обрабатывают в фоне. Пользователь не ждёт 30 секунд SMTP.

Асинхронность критична при росте: пики заказов не убивают latency API, воркеры масштабируются отдельно. Trade-off — eventual consistency: заказ «создан», письмо придёт через минуту.

Kafka выигрывает при потоках событий и replay (аналитика, несколько потребителей одной темы). RabbitMQ — классические задачи и сложная маршрутизация. BullMQ на Redis удобен в Node-экосистеме: минимум инфраструктуры, если Redis уже есть. Воркеры должны быть идемпотентными: повторная доставка сообщения не должна отправить два одинаковых письма — используйте jobId или уникальный ключ в БД.

Мониторинг очереди: длина очереди, age старейшего сообщения, rate ошибок в dead-letter queue. Если очередь растёт быстрее, чем воркеры успевают — пора масштабировать воркеры, а не API.


Работа с базой данных

Большинство бизнес-систем хранят состояние в БД.

Реляционные (SQL): PostgreSQL, MySQL, SQL Server. Таблицы, связи (FK), транзакции (ACID), индексы для ускорения поиска. Подходят для заказов, финансов, отчётности.

NoSQL: MongoDB (документы), Cassandra (широкие колонки, запись), Redis (ключ-значение, TTL). Имеют смысл при гибкой схеме, экстремальном объёме записи или когда данные естественно ложатся не в таблицы — но не как замена SQL «потому что модно».

ORM (Prisma, Hibernate, SQLAlchemy) генерирует SQL; сырой SQL остаётся для отчётов и оптимизации.

Пул соединений (PgBouncer, встроенный в драйвер) ограничивает число открытых коннектов к PostgreSQL — без него тысяча параллельных HTTP-запросов откроют тысячу коннектов и положат БД. Типичный размер пула — десятки, не тысячи.

Read replicas снимают нагрузку чтения с primary: отчёты и тяжёлые SELECT идут на реплику с задержкой репликации в доли секунды. Запись всегда на primary. Для финансовых транзакций читают с primary или учитывают lag.


Что происходит внутри базы данных

Запрос SELECT * FROM orders WHERE id = 100 проходит путь:

  1. Парсер SQL — синтаксис и AST.
  2. Оптимизатор — выбор плана: full scan vs index seek.
  3. Исполнитель — чтение страниц с диска (или из shared buffers в памяти).
  4. Возврат строк приложению.

Один и тот же запрос выполняется за миллисекунды с индексом по id и за минуты при full table scan на миллиарде строк. EXPLAIN ANALYZE — первый инструмент DBA и backend-разработчика.

Транзакции группируют операции: либо commit всего, либо rollback. Блокировки защищают консистентность и создают очереди ожидания при плохих запросах.

PostgreSQL хранит данные в страницах 8 KB; shared buffers держат горячие страницы в RAM. VACUUM и autovacuum убирают мёртвые строки после UPDATE/DELETE — без них раздувается таблица и падает производительность. Индекс B-tree по primary key делает поиск по id логарифмическим; составной индекс (user_id, created_at) ускоряет «заказы пользователя за период».

Уровни изоляции (READ COMMITTED по умолчанию в Postgres) определяют, видит ли транзакция незакоммиченные изменения других. Phantom read и lost update — причины явных SELECT FOR UPDATE в критичных сценариях (списание со склада).


Откуда берутся проблемы производительности

Типичные узкие места на пути «браузер → БД → браузер» разбирают по слоям.

Медленные SQL без индексов — классика: отчёт «по всем заказам за год» без фильтра по партиции сканирует миллионы строк. N+1 возникает, когда ORM загружает список заказов, а потом для каждого делает отдельный запрос за позициями — один запрос с JOIN или include в Prisma решает проблему.

Долгие транзакции держат блокировки: пользователь нажал «оплатить», транзакция ждёт ответ банка 30 секунд — остальные не могут обновить те же строки склада. Гигантский JSON — отдача 5 MB справочника на каждый визит вместо пагинации. Лишние HTTP-запросы — 40 мелких API вместо одного агрегирующего BFF-эндпоинта. Тяжёлый JS-бандл — 2 MB main chunk блокирует main thread при парсинге.

Диагностика начинается с где теряется время: DevTools Network (TTFB vs download), APM-трейсы (span на SQL), slow query log PostgreSQL (log_min_duration_statement), метрики Redis hit rate. Правило: оптимизируйте самый медленный слой в цепочке — ускорение Nginx на 5 ms бессмысленно, если SQL занимает 3 с.


Как ответ возвращается обратно

Приложение формирует ответ:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, max-age=0

{"orderId":100,"status":"pending"}

Путь назад: БД → приложение → веб-сервер → (CDN) → браузер. Сжатие, HTTP/2 multiplexing, keep-alive снижают overhead. Браузер парсит JSON, обновляет UI (React setState, rerender).

Ошибки тоже структурированы: 4xx — клиент, 5xx — сервер; тело с кодом ошибки помогает фронту показать понятное сообщение.

Сжатие (gzip, brotli) на Nginx уменьшает JSON и HTML в 3–5 раз; браузер расшифровывает прозрачно. Chunked transfer позволяет начать отдачу до полной сборки ответа — полезно для стриминга. HTTP/2 server push почти не используют; вместо него — preload ссылок в HTML.

На пути назад CDN может кэшировать только если origin отдал публичные заголовки; персональные ответы (Cache-Control: private) идут мимо edge straight to browser.


Современный фронтенд

После HTML и JSON работает JavaScript. React строит UI из компонентов; Virtual DOM (и современные альтернативы) минимизируют дорогие операции с реальным DOM.

SPA (Single Page Application) грузит оболочку один раз, дальше — API. SSR (Server Side Rendering) отдаёт готовый HTML с сервера — лучше для SEO и первой отрисовки. SSG генерирует страницы на этапе сборки (Astro, Next static). Гидратация — прикрепление обработчиков событий к уже отрисованному HTML; один из самых хрупких этапов (несовпадение server/client markup).

Выбор модели — компромисс интерактивности, SEO и сложности деплоя; подробнее о платформенных сдвигах — в обзоре CSS и нативных возможностей браузера.

Astro и аналоги минимизируют JS на странице по умолчанию — «острова» интерактивности. Next.js комбинирует SSR, SSG и client components в одном репозитории. После загрузки JS React сравнивает virtual DOM и обновляет только изменённые узлы — но сама гидратация и первый render стоят CPU на слабых телефонах.

Метрики фронта: LCP (крупный контент), INP (отзывчивость), CLS (стабильность вёрстки). Они зависят не только от React, но и от TTFB сервера, размера шрифтов и CDN. Оптимизация «только бандла» не спасёт, если API отвечает 2 секунды.


Наблюдаемость и эксплуатация

Распределённую систему нельзя чинить по одному лог-файлу.

Логи — структурированный JSON в ELK, OpenSearch, Loki; correlation id сквозь сервисы.

МетрикиPrometheus + Grafana: RPS, latency p95/p99, error rate, использование CPU/RAM.

ТрейсингOpenTelemetry, Jaeger: один trace на запрос через Nginx → API → Redis → PostgreSQL.

Алерты по SLO («p99 < 500 ms») опережают жалобы пользователей. Без этого стек микросервисов превращается в чёрный ящик.

Три столпа observability дополняют друг друга. Логи отвечают «что случилось с этим request id». Метрики — «сколько ошибок в минуту и выросла ли латентность». Трейсы — «какой span съел 90% времени». Correlation id пробрасывают из Nginx (X-Request-Id) через все сервисы в заголовках и полях лога.

На практике начинают с метрик (Prometheus + Grafana) и структурированных логов; трейсинг добавляют, когда микросервисов больше трёх и причину инцидента иначе ищут часами.


Как выглядит архитектура современного веб-приложения

Сводная схема типичного продакшена:

Пользователь
    ↓
Браузер (HTML, JS, cache)
    ↓
DNS → CDN (статика)
    ↓
Балансировщик
    ↓
Nginx / Caddy (TLS, proxy, static)
    ↓
Backend API (Node / Go / Java / PHP)
    ↓
Redis (cache, sessions)
    ↓
Очередь (RabbitMQ / Kafka / SQS)
    ↓
PostgreSQL (+ read replicas)
    ↓
Объектное хранилище (S3, MinIO) — файлы, бэкапы

Не каждый проект нуждается во всех блоках с первого дня. Стартап может жить на monolith + managed PostgreSQL; маркетплейс с пиками — CDN, Redis, очереди и горизонтальное масштабирование API.

Эволюция типична: MVP на одном VPS → вынос статики на CDN → Redis для сессий → read replica при росте отчётов → очередь при фоновых задачах → Kubernetes, когда деплоев больше десяти в неделю и нужны rolling updates без простоя. Прыгать сразу на полную схему — тратить месяцы на инфраструктуру вместо продукта.

Файлы (аватары, вложения) не кладут в PostgreSQL BYTEA на масштабе — S3, MinIO, Cloudflare R2 с подписанными URL для загрузки и CDN для раздачи.


Как изменился веб за последние 20 лет

2005: PHP на одном сервере, MySQL, HTML-шаблоны, полная перезагрузка страницы. Простой деплой по FTP, один лог-файл, SEO «из коробки». Узкий потолок по UX — каждый клик ждёт round-trip на сервер.

2015: SPA (Angular, ранний React), REST API, Docker, начало облаков. Богатый UI в браузере, боль pain с SEO и сборкой фронта. Появились отдельные команды «только фронт» и «только API», контракты OpenAPI, CORS как ежедневная тема.

2025: React / Next.js / Astro, Kubernetes в крупных командах, Redis и Kafka как норма, CDN на периферии, edge functions, ИИ-ассистенты в продукте. Server Components и partial hydration снова сдвигают логику к серверу — спираль, описанная в эволюции архитектуры веб-приложений.

Веб стал быстрее для пользователя и сложнее для команды: больше слоёв, больше инструментов наблюдаемости, выше цена архитектурной ошибки. Урок двадцати лет: каждый слой решал конкретное ограничение своего времени; копировать чужой стек без тех же ограничений — получить сложность без выгоды.


Частые вопросы

Почему сайт долго открывается в первый раз?

Часто виноваты холодный DNS, полный TLS-handshake, отсутствие CDN и тяжёлый первый бандл JavaScript. Повторный визит быстрее за счёт кэша браузера и keep-alive.

Чем веб-сервер отличается от сервера приложения?

Веб-сервер (Nginx) принимает соединения, терминирует TLS, отдаёт файлы и проксирует запросы. Приложение выполняет бизнес-логику и ходит в БД. Разделение позволяет масштабировать и обновлять слои независимо.

Нужен ли Redis, если уже есть PostgreSQL?

Redis не заменяет PostgreSQL. Он ускоряет сессии, кэш, rate limiting, блокировки, простые очереди. Данные в Redis должны быть восстанавливаемы или допустимо эфемерны.

Когда использовать очередь вместо синхронного API?

Когда операция дольше 200–500 ms, не критична для немедленного ответа пользователю или должна пережить пики (email, отчёты, интеграции). Платёж в checkout часто остаётся синхронным с таймаутом и идемпотентностью.

Почему один SQL-запрос «вдруг» стал медленным?

Рост таблицы без индекса, устаревшая статистика планировщика, блокировки, конкуренция за диск. Смотрите EXPLAIN ANALYZE и slow query log.

SPA или SSR в 2026 году?

Зависит от продукта: личный кабинет и дашборды — SPA/SSR с гидратацией; контентный маркетинг — SSG или SSR; максимальная простота — серверные шаблоны + HTMX. Универсального ответа нет.

Обязателен ли Kubernetes?

Нет. Для многих команд достаточно PaaS, Docker Compose на VPS или serverless. Kubernetes оправдан при десятках сервисов и собственной платформенной команде.

Что такое TTFB и почему он важен?

Time To First Byte — время от запроса до первого байта ответа. В него входят DNS, TCP, TLS, обработка на сервере и очередь за балансировщиком. Высокий TTFB при маленьком теле ответа указывает на backend или БД, а не на «медленный интернет».

Нужен ли отдельный BFF (Backend for Frontend)?

Когда мобильному и веб-клиенту нужны разные агрегации данных, тонкий BFF-слой сокращает число round-trip и держит тяжёлую логику в одном месте. Для одного SPA часто хватает общего API с версионированием (/v1/).


Дальнейшее чтение

Если вы строите или сопровождаете веб-продукт, логично углубиться в смежные темы на этом блоге: эволюция веб-архитектур, скрытые риски безопасности Node.js, кэширование на уровне приложения, ошибки с JWT и нативные возможности CSS вместо лишнего JS.


Заключение

Клик по ссылке запускает распределённую систему: DNS находит хост, TCP и TLS строят канал, HTTP несёт намерение клиента через CDN и балансировщики к коду, который читает кэш, ставит задачи в очередь и записывает данные в БД. Ответ идёт обратным путём, а браузер превращает байты в интерфейс.

Понимание полного маршрута запроса — не академическое упражнение. Оно помогает выбирать, где кэшировать, где асинхронить, где индексировать и где не копировать архитектуру Netflix в команде из пяти человек. Сохраните эту статью как карту и при следующем инциденте проходите слой за слоем — от Network tab до EXPLAIN в базе.