Лекции

Наблюдаемость и секреты
Секреты, TLS и идея mTLS

Секрет не кладут в git и не запекают в слой образа. TLS на краю шифрует канал до балансировщика. mTLS — обе стороны показывают сертификат, а не «просто HTTPS снаружи».

Наблюдаемость и секреты
Логи, correlation id и трейсы

Структурированный лог с id запроса склеивает строки между сервисами. Трейс — дерево отрезков работы со временем. Метрики, логи и трейсы дополняют друг друга. Секреты в логах — инцидент.

Наблюдаемость и секреты
Метрики, RED/USE, SLI/SLO/SLA и P99

RED описывает запросы сервиса, USE — ресурсы. SLI — что меряем, SLO — цель, SLA — договор с клиентом, обычно слабее SLO. Хвост смотрят по P99, не по среднему.

CI/CD
Стратегии релиза: blue/green, canary, rollback

Blue/green держит два контура и переключает трафик после прогрева. Canary отдаёт долю запросов новой версии и смотрит метрики. Rollback возвращает прежний digest. После несовместимой миграции БД откат кода может быть невозможен.

CI/CD
CI/CD пайплайн: lint, test, build, scan, deploy

Сначала проверяют код и собирают один неизменяемый образ. Потом его выкатывают. В staging и production уходит тот же digest, который собрали в CI. Не свежий latest с сервера.

Контейнеры и оркестрация
Pod, реплики, пробы и Service

Под — единица деплоя: контейнеры делят сеть и тома. ReplicaSet держит нужное число подов. Service шлёт трафик только на Ready. Liveness и readiness отвечают на разные вопросы.

Контейнеры и оркестрация
Compose: сети, тома, healthcheck

В сети Compose сервисы находят друг друга по имени. localhost внутри контейнера — это не Postgres на ноутбуке. Том переживает удаление контейнера. Healthcheck говорит, готов ли сервис, а не «процесс просто стартовал».

Контейнеры и оркестрация
Образы Docker: слои, PID 1, тег vs digest

Образ — снимок файлов и настроек. Контейнер — запущенный процесс из этого снимка. В прод ставят digest, не latest. PID 1 должен принимать SIGTERM, иначе приложение убивают посреди запроса.

Проектирование сервисов
Событийная архитектура: связность и схема

Продюсер события не ждёт потребителей: связность уходит в контракт топика и в эволюцию схемы. Имя события, владелец полей и совместимость — часть дизайна, а не «потом добавим ключ в JSON».

Проектирование сервисов
API gateway и BFF

Gateway — единый вход: TLS, аутентификация, маршруты, лимиты. BFF — фасад под конкретный клиент. Оба полезны на краю. Оба становятся точкой отказа, если в них переезжает бизнес-логика.

Проектирование сервисов
Монолит vs микросервисы и distributed monolith

Сервис выделяют ради независимого деплоя, масштаба и отказов, а не потому что «так модно». Distributed monolith — сеть и операционка микросервисов при общей базе и lock-step релизах.

Проектирование сервисов
Clean architecture в Go: границы и интерфейсы

Сущности и сценарии не знают HTTP и SQL: зависимости направлены к центру. В Go интерфейс объявляет потребитель на границе, а не «у каждой структуры пакет с моком».

Распределённые системы
Retry storm, backoff, breaker, bulkhead

Повторы без лимита и джиттера усиливают аварию соседа. Устойчивый клиент сочетает идемпотентность, экспоненциальный backoff с jitter, circuit breaker и изоляцию пулов — bulkhead.

Распределённые системы
Распределённые транзакции: saga vs 2PC

Двухфазный коммит блокирует ресурсы и плохо переживает падение координатора. Сага — цепочка локальных шагов плюс явная отмена: вернуть деньги, снять бронь. Это не магический rollback на две базы.

Распределённые системы
Модели согласованности

Согласованность — обещание, что клиент увидит после записи. Strong, eventual, read-your-writes и monotonic reads различаются окном видимости и ценой координации. Это не то же самое, что транзакция в одной БД.

Распределённые системы
CAP и PACELC

При разрыве сети нельзя быть сразу строго согласованным и всегда доступным. CP жертвует ответом, AP — единым снимком. PACELC добавляет тот же выбор в обычной работе: скорость или свежесть данных.

Авторизация и устойчивость HTTP
Graceful shutdown: SIGTERM, preStop, in-flight

SIGTERM — не рубить accept сразу. Сначала снять под с балансировки, перестать принимать новое, дождаться in-flight, закрыть пулы и только потом выйти. Иначе клиент ретраит оборванный ответ и может удвоить POST.

Авторизация и устойчивость HTTP
Rate limiting: token bucket и где ставить лимит

Token bucket дозирует среднюю скорость и разрешает короткий всплеск до ёмкости ведра. Лимит на gateway режет дёшево по IP или ключу. В приложении — по пользователю после auth. Без общего счётчика N реплик дают N-кратную квоту.

Авторизация и устойчивость HTTP
JWT vs сессия, refresh, HS256/RS256, где хранить

Сессия держит состояние на сервере: отозвать её проще — удалили ключ в store. JWT access самодостаточен после подписи; отзыв сложнее. HS256 делит секрет с каждым, кто проверяет. RS256 оставляет подпись у issuer. Где хранить токен — развилка XSS против CSRF.

Авторизация и устойчивость HTTP
Таймауты HTTP: connect, request, idle, slowloris

Нужны разные таймауты: connect на handshake, чтение заголовков и тела, запись ответа, idle keep-alive. Нулевой таймаут часто значит «ждать вечно». Context отменяет работу, когда клиент ушёл. Slowloris держит слоты капельной подачей заголовков.

API и RPC
Идемпотентность HTTP и Idempotency-Key

По семантике HTTP методы GET, PUT и DELETE идемпотентны, POST — нет. Повтор POST после таймаута без ключа легко создаёт вторую операцию: запись могла пройти, а ответ потеряться.

API и RPC
REST vs gRPC: protobuf и когда RPC

REST в быту — ресурсы, JSON и семантика HTTP. gRPC — контракт в .proto, protobuf на проводе и вызовы как функции, обычно поверх HTTP/2. Публичному браузерному API чаще нужен REST; внутреннему mesh — RPC.

Транспорт и DNS
HTTP/1.1, pipelining, HTTP/2 multiplex, head-of-line

HTTP/1.1 держит соединение и страдает очередью запросов на одном TCP. HTTP/2 мультиплексирует стримы в одном соединении. Head-of-line на уровне TCP при потере пакета остаётся до HTTP/3.

Транспорт и DNS
DNS: A/AAAA, CNAME, TTL, кэш, split horizon

Резолв имени — это записи A и AAAA, цепочки CNAME, TTL и несколько кэшей. Split horizon отдаёт разные ответы на одно имя внутри сети и снаружи.

Транспорт и DNS
TCP: handshake, TIME_WAIT, keepalive, Nagle

До HTTP работает TCP: три рукопожатия, TIME_WAIT, keepalive и Nagle. TCP keepalive проверяет, жив ли сосед. HTTP keep-alive повторно использует соединение. Это разные вещи.

Надёжная доставка
Poison, DLQ, retry и идемпотентный consumer

Poison message бесконечно валит воркера: в Kafka стопорит партицию на одном offset, в Rabbit крутится requeue. Retry с backoff и лимитом, затем DLQ. Консьюмер без идемпотентности удваивает деньги на каждом ретрае.

Надёжная доставка
Transactional outbox и dual write

Dual write — это commit в базу и publish в брокер без общей транзакции: можно получить заказ без события или событие без заказа. Outbox пишет событие в той же транзакции, что и данные; публикатор читает таблицу или WAL после commit.

RabbitMQ
Kafka vs RabbitMQ: лог и брокер

Kafka — append-лог с replay и retention: консьюмер двигает курсор. RabbitMQ — брокер с маршрутизацией: после ack сообщение исчезает. Выбирают под модель потребления и нагрузку, а не по тому, «что уже стоит в кластере».

RabbitMQ
Exchange, очередь, ack и prefetch

Продюсер RabbitMQ публикует в exchange, а не в очередь напрямую. Binding решает, в какие очереди попадёт сообщение. После ack сообщение исчезает. Prefetch ограничивает, сколько неакнутых сообщений держать на консьюмере.

Kafka
At-most, at-least, exactly-once

At-least-once — рабочая норма с дублями. Exactly-once в Kafka — идемпотентный продюсер и транзакции между топиками, а не «ровно один UPDATE в вашей Postgres». Граница системы важнее маркетингового EOS.

Kafka
Consumer group, rebalance и commit offset

Consumer group делит партиции: одну партицию в группе читает не больше одного консьюмера. Offset — курсор в логе. Commit после обработки даёт at-least-once: при крэше сообщение прочитают снова.

Kafka
Топик, партиция, ключ и порядок

Топик в Kafka — распределённый лог, разрезанный на партиции. Порядок гарантирован внутри партиции, а не по всему топику. Ключ продюсера выбирает партицию и тем самым порядок событий одной сущности.

Кэш и Redis
Cache-aside, write-through, write-back и инвалидация

Cache-aside держит кэш рядом с базой: приложение само читает и пишет. Инвалидация сложнее, чем SET. Write-back быстрее на записи и опаснее для durability.

Кэш и Redis
Eviction, hot key и thundering herd

Когда память кончилась, срабатывает политика eviction. Hot key греет один шард и один поток. Thundering herd — согласованный промах: все клиенты одновременно идут в базу.

Кэш и Redis
Типы Redis: string, hash, list, set, zset, TTL

Redis — структуры данных в памяти с TTL на ключе. Выбор типа задаёт сложность команд и паттерн доступа, а не «просто кэш строк с JSON».

PostgreSQL
N+1, JOIN и prepared statements

N+1 — это N сетевых кругов вместо одного запроса, а не «сложная сортировка в планировщике». JOIN или батч-preload лечат чтение. Prepared statement снимает parse на горячем пути и не заменяет индекс.

PostgreSQL
Пул соединений и PgBouncer

Каждое клиентское соединение в PostgreSQL — отдельный серверный процесс. Лимит max_connections невелик. Пул в приложении и PgBouncer решают разные этажи одной задачи.

PostgreSQL
WAL, replay, лаг реплики и failover

WAL — журнал изменений: локальная durability и канал для физической реплики. Асинхронная реплика отстаёт. Failover — это promote и то, как клиенты найдут новый primary, а не «перезапустили под».

PostgreSQL
EXPLAIN ANALYZE: Seq Scan, Index Scan, buffers

EXPLAIN показывает оценку планировщика. EXPLAIN ANALYZE выполняет запрос и даёт фактические цифры. Читают actual rows против planned, тип узла и буферы, а не «стрелочки красивого дерева».

PostgreSQL
Блокировки строк, FOR UPDATE, deadlock

MVCC не отменяет локи писателей. FOR UPDATE берёт row lock без изменения данных. При тупике PostgreSQL прерывает одну транзакцию. Не держите лок, пока ходите в сеть.

PostgreSQL
MVCC, xmin/xmax, vacuum и bloat

UPDATE в PostgreSQL не переписывает строку на месте: появляется новая версия, старая жива, пока её кто-то может видеть. Vacuum подбирает мёртвое. Долгая транзакция удерживает горизонт и раздувает bloat.

PostgreSQL
Read Committed, Repeatable Read, Serializable

По умолчанию в PostgreSQL — Read Committed. Repeatable Read здесь это snapshot isolation. Serializable ловит write skew ценой ретраев по serialization_failure.

PostgreSQL
ACID коротко: зачем транзакция

Транзакция задаёт границу «всё или ничего» для нескольких изменений. ACID описывает свойства этой границы, а не магию изоляции и не бизнес-инварианты приложения.

PostgreSQL
Составной индекс и leftmost prefix

Индекс (a, b, c) работает как телефонная книга: сначала a, потом b. Без ведущей колонки классический B-tree почти бесполезен для поиска.

PostgreSQL
B-tree индексы, Seq Scan и covering

B-tree — индекс по умолчанию в PostgreSQL. Seq Scan не ошибка: на малой таблице или при низкой селективности он дешевле. Covering-индекс с INCLUDE даёт шанс на Index Only Scan.

Операционные системы
Мультиплексирование I/O: select, epoll, kqueue

Один поток может ждать готовности многих файловых дескрипторов. На Linux это epoll, на BSD и macOS — kqueue. Netpoller Go сидит на этом механизме: горутина паркуется на Read, а не занимает OS thread на каждое соединение.

Операционные системы
Виртуальная память и page cache

Процесс работает с виртуальными адресами; ядро отображает страницы на RAM или диск. RSS — то, что реально в памяти. Page cache ОС ускоряет чтение файлов и часто WAL, и это не Redis и не куча Go.

Операционные системы
Процессы, потоки и файловые дескрипторы

Процесс — своё адресное пространство. Потоки одного процесса делят память и таблицу файловых дескрипторов. Горутина легче потока и мультиплексируется планировщиком M:N; забытый Close у HTTP Body часто течёт именно дескрипторами, а не «GMP».

Алгоритмы и структуры данных
Графы: BFS, DFS, когда что

BFS находит кратчайший путь в невзвешенном графе. DFS удобен для компонент, циклов и топологической сортировки. На бэкенде те же идеи: зависимости сервисов, DAG пайплайнов, обход оргструктур и граф вызовов.

Алгоритмы и структуры данных
Куча, стек, очередь, deque

Стек — LIFO, очередь — FIFO, двоичная куча отдаёт минимум или максимум за O(log n). В Go слайс закрывает стек и простую очередь, а container/heap — приоритет; куча процесса с GC к этой структуре не относится.

Алгоритмы и структуры данных
B-tree и почему им индексы

B-tree и B+tree держат мало уровней на диске и хорошо отвечают на равенство и диапазон. Именно поэтому обычный индекс PostgreSQL — это дерево страниц, а не бинарное дерево указателей в RAM.

Алгоритмы и структуры данных
Хеш-таблицы и коллизии

Хэш-таблица делит ключи по бакетам и даёт средний O(1) на поиск. Коллизии неизбежны: плохой хэш, высокий load factor и ресайз превращают константу в линейный обход и в паузы под нагрузкой.

Алгоритмы и структуры данных
Сложность алгоритмов и Big O

Big O описывает, как растут время и память с размером входа. На бэкенде это про индексы, N+1, аллокации и P99, а не про олимпиадную формулу с доски.

Рантайм и инструменты Go
Тесты, бенчмарки и httptest

Тест фиксирует контракт: таблица случаев, при необходимости t.Parallel и httptest без сети. Бенчмарк с ReportAllocs измеряет аллокации. Покрытие без граничных случаев само по себе ничего не доказывает.

Рантайм и инструменты Go
Ошибки: wrapping, sentinel, panic

Ошибка в Go — значение, которое возвращают и оборачивают. Sentinel проверяют через errors.Is, типы — через As. panic оставляют багам, не веткам вроде невалидного ввода.

Рантайм и инструменты Go
pprof: CPU, heap, goroutine

pprof снимает профили CPU, кучи, блокировок и горутин. Разговор про «медленно» без цифр и без профиля обычно ничем не кончается.

Рантайм и инструменты Go
Сборщик мусора и GOGC

Сборщик в Go помечает живые объекты параллельно с программой. Полной остановки мира на всю кучу нет, но короткие паузы есть. GOGC задаёт, насколько куча может вырасти относительно живых данных между циклами.

Рантайм и инструменты Go
Escape analysis, стек и куча

Локальная переменная живёт на стеке горутины, пока компилятор не решит, что она «убегает». Тогда объект на куче, и его жизнь оплачивает сборщик мусора.

Рантайм и инструменты Go
defer: порядок и аргументы

defer ставит вызов в стек и выполняет его при выходе из функции, с конца. Аргументы считаются сразу, в момент defer, а не в момент выхода.

Конкурентность Go
Data race и memory model

Data race — две горутины трогают одну переменную, среди доступов есть запись, и нет договорённости «кто раньше». Это баг, а не «на моей машине работает».

Конкурентность Go
WaitGroup, Once, атомики

WaitGroup ждёт набор горутин. Once гарантирует один вызов функции. atomic закрывает простые счётчики. Составное условие из нескольких полей атомиком не защитить.

Конкурентность Go
Mutex, RWMutex и каналы как синхронизация

Mutex защищает общую память. Канал передаёт владение значением между горутинами. RWMutex выгоден, когда чтений много, а запись редкая.

Конкурентность Go
Context: cancel, deadline, values

Context несёт отмену и дедлайн вниз по вызовам. Values годятся для редких полей запроса вроде trace id, а не для логгера, конфига и внедрения зависимостей.

Конкурентность Go
Select, таймаут и default

select ждёт, пока будет готов хотя бы один case, и среди готовых выбирает случайный. default — попытка без ожидания. Таймаут делают через timer или context, не через бесконечный sleep.

Конкурентность Go
Каналы: буфер, close, владение

Канал передаёт значения и синхронизирует горутины. Закрывает тот, кто пишет. Send в закрытый канал — panic. Receive из закрытого — нуль типа и ok=false.

Конкурентность Go
Горутины и планировщик GMP

Горутина — дешёвая единица работы со своим стеком, не поток ОС. Планировщик связывает G (горутина), M (поток) и P (очередь исполнения).

Типы и память Go
Embedding структур и интерфейсов

Безымянное поле продвигает методы внутреннего типа наружу. Это сборка из частей, не наследование классов: подтипа «является» и виртуального override в Go нет.

Типы и память Go
Интерфейсы и nil

Интерфейс — это пара «какой тип внутри + само значение». Нильный указатель в интерфейсе не равен nil. Отсюда ложные if err != nil и паники на методах.

Типы и память Go
Строки, байты и руны

Строка в Go — неизменяемая последовательность байт в UTF-8. len считает байты, range отдаёт руны, а нарезка по индексу может разрезать букву пополам.

Типы и память Go
Мапы: хэш, comma-ok, concurrent write

Map в Go — хэш-таблица: ключ находится быстро, порядка обхода нет. Нулевая map паникует на записи. Конкурентная запись без блокировки — data race и часто падение процесса.

Типы и память Go
Слайсы: len, cap, append и шаринг массива

Срез — это окно на массив: указатель, длина и ёмкость. От этого зависят append, общие подсрезы и то, что видно снаружи после вызова функции.