Поиск и фильтры
Секреты, TLS и идея mTLS
Секрет не кладут в git и не запекают в слой образа. TLS на краю шифрует канал до балансировщика. mTLS — обе стороны показывают сертификат, а не «просто HTTPS снаружи».
Логи, correlation id и трейсы
Структурированный лог с id запроса склеивает строки между сервисами. Трейс — дерево отрезков работы со временем. Метрики, логи и трейсы дополняют друг друга. Секреты в логах — инцидент.
Метрики, RED/USE, SLI/SLO/SLA и P99
RED описывает запросы сервиса, USE — ресурсы. SLI — что меряем, SLO — цель, SLA — договор с клиентом, обычно слабее SLO. Хвост смотрят по P99, не по среднему.
Стратегии релиза: blue/green, canary, rollback
Blue/green держит два контура и переключает трафик после прогрева. Canary отдаёт долю запросов новой версии и смотрит метрики. Rollback возвращает прежний digest. После несовместимой миграции БД откат кода может быть невозможен.
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 добавляет тот же выбор в обычной работе: скорость или свежесть данных.
Graceful shutdown: SIGTERM, preStop, in-flight
SIGTERM — не рубить accept сразу. Сначала снять под с балансировки, перестать принимать новое, дождаться in-flight, закрыть пулы и только потом выйти. Иначе клиент ретраит оборванный ответ и может удвоить POST.
Rate limiting: token bucket и где ставить лимит
Token bucket дозирует среднюю скорость и разрешает короткий всплеск до ёмкости ведра. Лимит на gateway режет дёшево по IP или ключу. В приложении — по пользователю после auth. Без общего счётчика N реплик дают N-кратную квоту.
JWT vs сессия, refresh, HS256/RS256, где хранить
Сессия держит состояние на сервере: отозвать её проще — удалили ключ в store. JWT access самодостаточен после подписи; отзыв сложнее. HS256 делит секрет с каждым, кто проверяет. RS256 оставляет подпись у issuer. Где хранить токен — развилка XSS против CSRF.
Таймауты HTTP: connect, request, idle, slowloris
Нужны разные таймауты: connect на handshake, чтение заголовков и тела, запись ответа, idle keep-alive. Нулевой таймаут часто значит «ждать вечно». Context отменяет работу, когда клиент ушёл. Slowloris держит слоты капельной подачей заголовков.
Идемпотентность HTTP и Idempotency-Key
По семантике HTTP методы GET, PUT и DELETE идемпотентны, POST — нет. Повтор POST после таймаута без ключа легко создаёт вторую операцию: запись могла пройти, а ответ потеряться.
REST vs gRPC: protobuf и когда RPC
REST в быту — ресурсы, JSON и семантика HTTP. gRPC — контракт в .proto, protobuf на проводе и вызовы как функции, обычно поверх HTTP/2. Публичному браузерному API чаще нужен REST; внутреннему mesh — RPC.
HTTP/1.1, pipelining, HTTP/2 multiplex, head-of-line
HTTP/1.1 держит соединение и страдает очередью запросов на одном TCP. HTTP/2 мультиплексирует стримы в одном соединении. Head-of-line на уровне TCP при потере пакета остаётся до HTTP/3.
DNS: A/AAAA, CNAME, TTL, кэш, split horizon
Резолв имени — это записи A и AAAA, цепочки CNAME, TTL и несколько кэшей. Split horizon отдаёт разные ответы на одно имя внутри сети и снаружи.
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.
Kafka vs RabbitMQ: лог и брокер
Kafka — append-лог с replay и retention: консьюмер двигает курсор. RabbitMQ — брокер с маршрутизацией: после ack сообщение исчезает. Выбирают под модель потребления и нагрузку, а не по тому, «что уже стоит в кластере».
Exchange, очередь, ack и prefetch
Продюсер RabbitMQ публикует в exchange, а не в очередь напрямую. Binding решает, в какие очереди попадёт сообщение. После ack сообщение исчезает. Prefetch ограничивает, сколько неакнутых сообщений держать на консьюмере.
At-most, at-least, exactly-once
At-least-once — рабочая норма с дублями. Exactly-once в Kafka — идемпотентный продюсер и транзакции между топиками, а не «ровно один UPDATE в вашей Postgres». Граница системы важнее маркетингового EOS.
Consumer group, rebalance и commit offset
Consumer group делит партиции: одну партицию в группе читает не больше одного консьюмера. Offset — курсор в логе. Commit после обработки даёт at-least-once: при крэше сообщение прочитают снова.
Топик, партиция, ключ и порядок
Топик в Kafka — распределённый лог, разрезанный на партиции. Порядок гарантирован внутри партиции, а не по всему топику. Ключ продюсера выбирает партицию и тем самым порядок событий одной сущности.
Cache-aside, write-through, write-back и инвалидация
Cache-aside держит кэш рядом с базой: приложение само читает и пишет. Инвалидация сложнее, чем SET. Write-back быстрее на записи и опаснее для durability.
Eviction, hot key и thundering herd
Когда память кончилась, срабатывает политика eviction. Hot key греет один шард и один поток. Thundering herd — согласованный промах: все клиенты одновременно идут в базу.
Типы Redis: string, hash, list, set, zset, TTL
Redis — структуры данных в памяти с TTL на ключе. Выбор типа задаёт сложность команд и паттерн доступа, а не «просто кэш строк с JSON».
N+1, JOIN и prepared statements
N+1 — это N сетевых кругов вместо одного запроса, а не «сложная сортировка в планировщике». JOIN или батч-preload лечат чтение. Prepared statement снимает parse на горячем пути и не заменяет индекс.
Пул соединений и PgBouncer
Каждое клиентское соединение в PostgreSQL — отдельный серверный процесс. Лимит max_connections невелик. Пул в приложении и PgBouncer решают разные этажи одной задачи.
WAL, replay, лаг реплики и failover
WAL — журнал изменений: локальная durability и канал для физической реплики. Асинхронная реплика отстаёт. Failover — это promote и то, как клиенты найдут новый primary, а не «перезапустили под».
EXPLAIN ANALYZE: Seq Scan, Index Scan, buffers
EXPLAIN показывает оценку планировщика. EXPLAIN ANALYZE выполняет запрос и даёт фактические цифры. Читают actual rows против planned, тип узла и буферы, а не «стрелочки красивого дерева».
Блокировки строк, FOR UPDATE, deadlock
MVCC не отменяет локи писателей. FOR UPDATE берёт row lock без изменения данных. При тупике PostgreSQL прерывает одну транзакцию. Не держите лок, пока ходите в сеть.
MVCC, xmin/xmax, vacuum и bloat
UPDATE в PostgreSQL не переписывает строку на месте: появляется новая версия, старая жива, пока её кто-то может видеть. Vacuum подбирает мёртвое. Долгая транзакция удерживает горизонт и раздувает bloat.
Read Committed, Repeatable Read, Serializable
По умолчанию в PostgreSQL — Read Committed. Repeatable Read здесь это snapshot isolation. Serializable ловит write skew ценой ретраев по serialization_failure.
ACID коротко: зачем транзакция
Транзакция задаёт границу «всё или ничего» для нескольких изменений. ACID описывает свойства этой границы, а не магию изоляции и не бизнес-инварианты приложения.
Составной индекс и leftmost prefix
Индекс (a, b, c) работает как телефонная книга: сначала a, потом b. Без ведущей колонки классический B-tree почти бесполезен для поиска.
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, а не про олимпиадную формулу с доски.
Тесты, бенчмарки и httptest
Тест фиксирует контракт: таблица случаев, при необходимости t.Parallel и httptest без сети. Бенчмарк с ReportAllocs измеряет аллокации. Покрытие без граничных случаев само по себе ничего не доказывает.
Ошибки: wrapping, sentinel, panic
Ошибка в Go — значение, которое возвращают и оборачивают. Sentinel проверяют через errors.Is, типы — через As. panic оставляют багам, не веткам вроде невалидного ввода.
pprof: CPU, heap, goroutine
pprof снимает профили CPU, кучи, блокировок и горутин. Разговор про «медленно» без цифр и без профиля обычно ничем не кончается.
Сборщик мусора и GOGC
Сборщик в Go помечает живые объекты параллельно с программой. Полной остановки мира на всю кучу нет, но короткие паузы есть. GOGC задаёт, насколько куча может вырасти относительно живых данных между циклами.
Escape analysis, стек и куча
Локальная переменная живёт на стеке горутины, пока компилятор не решит, что она «убегает». Тогда объект на куче, и его жизнь оплачивает сборщик мусора.
defer: порядок и аргументы
defer ставит вызов в стек и выполняет его при выходе из функции, с конца. Аргументы считаются сразу, в момент defer, а не в момент выхода.
Data race и memory model
Data race — две горутины трогают одну переменную, среди доступов есть запись, и нет договорённости «кто раньше». Это баг, а не «на моей машине работает».
WaitGroup, Once, атомики
WaitGroup ждёт набор горутин. Once гарантирует один вызов функции. atomic закрывает простые счётчики. Составное условие из нескольких полей атомиком не защитить.
Mutex, RWMutex и каналы как синхронизация
Mutex защищает общую память. Канал передаёт владение значением между горутинами. RWMutex выгоден, когда чтений много, а запись редкая.
Context: cancel, deadline, values
Context несёт отмену и дедлайн вниз по вызовам. Values годятся для редких полей запроса вроде trace id, а не для логгера, конфига и внедрения зависимостей.
Select, таймаут и default
select ждёт, пока будет готов хотя бы один case, и среди готовых выбирает случайный. default — попытка без ожидания. Таймаут делают через timer или context, не через бесконечный sleep.
Каналы: буфер, close, владение
Канал передаёт значения и синхронизирует горутины. Закрывает тот, кто пишет. Send в закрытый канал — panic. Receive из закрытого — нуль типа и ok=false.
Горутины и планировщик GMP
Горутина — дешёвая единица работы со своим стеком, не поток ОС. Планировщик связывает G (горутина), M (поток) и P (очередь исполнения).
Embedding структур и интерфейсов
Безымянное поле продвигает методы внутреннего типа наружу. Это сборка из частей, не наследование классов: подтипа «является» и виртуального override в Go нет.
Интерфейсы и nil
Интерфейс — это пара «какой тип внутри + само значение». Нильный указатель в интерфейсе не равен nil. Отсюда ложные if err != nil и паники на методах.
Строки, байты и руны
Строка в Go — неизменяемая последовательность байт в UTF-8. len считает байты, range отдаёт руны, а нарезка по индексу может разрезать букву пополам.
Мапы: хэш, comma-ok, concurrent write
Map в Go — хэш-таблица: ключ находится быстро, порядка обхода нет. Нулевая map паникует на записи. Конкурентная запись без блокировки — data race и часто падение процесса.
Слайсы: len, cap, append и шаринг массива
Срез — это окно на массив: указатель, длина и ёмкость. От этого зависят append, общие подсрезы и то, что видно снаружи после вызова функции.