Перейти к содержимому

Go Middle 2 Roadmap — путь до Senior (2025-2026)

Цель: уверенный Middle Go-разработчик уровня Middle 2 (M2 / Middle+), готовый идти на собеседования в Yandex (G16/G17), Avito, Ozon, VK, Tinkoff, Sber на позиции “Middle 2 / Senior Go Developer”. Документ — шпаргалка + roadmap.

Источники: данные собраны из веб-поисков (Habr, Medium, dev.to, оф. блог Go, levels.fyi, Glassdoor, Pyroscope/Grafana docs, Kubernetes docs, Хабр).


Yandex (по levels.fyi):

  • G14: ~1.84M ₽/год — Junior
  • G15: ~2.5–3M ₽/год — Junior+/Middle 1
  • G16: ~3.5–4.5M ₽/год — Middle / Middle 1
  • G17 (Middle 2): медиана ~5.87M ₽/год; диапазон ~3.15M–7.46M ₽
  • G18: ~6–9M ₽/год — Senior
  • G19+: ~9–12M+ ₽/год — Senior+/Staff

Tinkoff (Software Engineer):

  • SDE I (Junior): ~2.2M ₽/год
  • SDE III (Middle 2): ~3.4M ₽/год (медиана)
  • SDE V (Senior): ~4.2M+ ₽/год

VK Senior SWE: медиана ~5.47M ₽/год (диапазон 4–6.77M+)

Avito / Ozon: Middle 2 Go — 300–450 тыс. ₽/мес (3.6–5.4M ₽/год TC); Senior — от 450 тыс. ₽/мес и выше. На M2 уже стабильно дают офферы.

Источник: Levels.fyi Yandex G17, Tinkoff Bank salaries, ENIGMA AI зарплата Go

Что отличает Middle 2 от Middle 1:

  • Не просто “пишу код по тикетам” — умею самостоятельно дизайнить сервис среднего масштаба
  • Знаю Go на уровне runtime/internals, не только синтаксис
  • Понимаю distributed systems (CAP, идемпотентность, Saga, Outbox)
  • Могу профилировать продакшен и оптимизировать (pprof, flame graphs, Pyroscope)
  • Веду техлид-функции в рамках команды: code review, наставничество Junior-ов
  • На собесе ожидают system design (URL shortener, чат, лента) уровня LLD + базовый HLD

G (Goroutine) — пользовательская корутина (~2KB стек).
M (Machine) — OS-поток.
P (Processor) — логический процессор, держит локальную run-очередь.

  • GOMAXPROCS = N → N штук P.
  • Каждый P держит локальную LRQ (Local Run Queue, ~256 goroutines).
  • Есть GRQ (Global Run Queue) — куда идут “пересыпы”.

Work stealing:

  1. Goroutine кончается на P → сначала идём за работой в GRQ (раз в ~61 такт также).
  2. Если GRQ пуст — steal half из другого P (с хвоста очереди другого P).
  3. Если совсем нечего делать — M паркуется (futex), пока не разбудят.

Network poller (netpoll):

  • Когда goroutine делает blocking I/O (read/write socket), она уходит в netpoller.
  • Реализация: epoll (Linux) / kqueue (macOS/BSD) / iocp (Windows).
  • M освобождается и берёт следующую goroutine из LRQ.
  • Когда I/O готов → netpoller возвращает goroutine в run-очередь.
  • Это позволяет тысячам соединений жить на десятке M.

Syscalls:

  • Когда G делает блокирующий syscall (не netpoll), M “отлепляется” от P.
  • P подхватывается другой M (берётся из пула holdoff-M или создаётся новая).
  • Когда syscall завершается, исходный M пытается заново захватить P, иначе кладёт G в GRQ и засыпает.

Asynchronous preemption (Go 1.14+):

  • До Go 1.14 — кооперативная преемпция (точки вызова функций).
  • С Go 1.14 — сигнально-асинхронная: runtime отправляет SIGURG потоку, обработчик прерывает goroutine на безопасной точке.
  • Решает проблему “tight loop без вызовов функций” — раньше такая горутина блокировала весь P.

Parking:

  • При chan recv на пустом канале → goroutine паркуется (g.status = waiting), удаляется из LRQ.
  • При sending → wake up через runtime.goready().

Источники: bytesizego scheduler 2025, container-aware GOMAXPROCS

Иерархия (от частного к общему):

goroutine → P.mcache → mcentral[size_class] → mheap → OS (mmap)
  • mcache — per-P (нет лока). Держит спаны размерных классов.
  • mcentral — глобальный пул для одного size class. 136 mcentral (по числу spanClass).
  • mheap — глобальная куча, выделяет арены у OS.

Размерные классы (~67-70 классов small + huge):

  • Малые объекты (≤16B) — tiny allocator (комбинирует в один блок).
  • Малые (16B–32KB) — round up до ближайшего size class (16, 32, 48, …, 32K).
  • Большие (>32KB) — выделяются напрямую из mheap (large object).

Аллокация:

  1. Размер → size class
  2. Идём в mcache.alloc[spanClass] — находим свободный slot в bitmap span’а
  3. Если span полон → mcentral.cacheSpan() — берём новый span (с локом mcentral)
  4. Если mcentral пуст → mheap.alloc() — у OS просим арену (64MB)

span (mspan): диапазон страниц (8KB каждая), разбитый на объекты одного size class.

Источник: Memory Allocation in Go (2025)

Go 1.24 — Swiss Tables maps: новая хеш-таблица в runtime, ~2-3% быстрее в среднем по бенчмаркам.

Go использует concurrent, tri-color, mark-sweep GC (без compaction).

Цикл GC:

  1. STW start (~10µs) — barrier turn on, корни (стеки) подготовлены
  2. Concurrent mark — goroutines продолжают работать, GC помечает живые объекты
  3. STW mark termination (~10µs) — финал маркинга
  4. Concurrent sweep — освобождает память, лениво

Write barrier: при записи указателя в маркинг-фазе runtime интерсептит запись, чтобы не потерять живой объект.

Pacer (как выбирает момент старта):

  • Цель — закончить GC до того, как куча вырастет до target heap size.
  • target = live_heap × (1 + GOGC/100) — при GOGC=100 куча может удвоиться.
  • Pacer считает CPU assist: если приложение аллоцирует быстрее, чем GC чистит, аллоцирующие goroutines помогают GC (тратят свои CPU-такты на маркинг).
  • С Go 1.18 — новый pacer: учитывает не только heap size, но и goal CPU usage (~25% макс на GC).

GOMEMLIMIT (с Go 1.19):

  • Soft limit на общее потребление памяти Go runtime.
  • Когда близко к лимиту — pacer триггерит GC чаще, даже если GOGC говорит “ещё не время”.
  • Полезно в контейнерах с memory limit: GOMEMLIMIT=900MiB при контейнере 1Gi.
  • GOGC=off + GOMEMLIMIT=900MiB — GC запускается только когда близко к лимиту (batch jobs).

Источники: Go GC Guide, goperf.dev GC

  • Каждая goroutine стартует с 2KB стеком (с Go 1.4).
  • Стек сегментированный (раньше был “split stacks”; теперь contiguous stacks).
  • Если функция требует больше места → runtime выделяет новый, в 2x больший стек, копирует фреймы туда (move).
  • При копировании — переписываются указатели в стек.
  • Поэтому указатели на стек могут менять адрес — нельзя кэшить их извне.
  • GC может shrinking стек: если меньше 1/4 используется на следующем GC, стек уменьшается.
  • Максимум стека: 1GB (по умолчанию, можно runtime/debug.SetMaxStack).

С Go 1.14 появились open-coded defers (proposal Russ Cox).

Три типа defer:

  1. Heap-allocated (старый) — медленный, ~50ns, аллокация _defer структуры.
  2. Stack-allocated (с Go 1.13) — ~35ns, без аллокации.
  3. Open-coded (с Go 1.14) — ~6ns, инлайн-код, сравнимо с прямым вызовом (~4.4ns).

Когда работает open-coded:

  • ≤ 8 defer’ов в функции
  • defer не в цикле
  • Все аргументы известны
  • Нет recover() без defer

При panic’е — компилятор хранит метаданные (funcdata), чтобы корректно развернуть стек.

Источник: Go defer benchmark

panic — структура _panic в стеке goroutine, образует связный список (panic во время panic’а).

Что делает panic:

  1. Останавливает выполнение функции.
  2. Раскручивает стек вверх, выполняя все defer’ы.
  3. Если в defer вызван recover() — panic “снимается”, выполнение продолжается из defer’а.
  4. Если recover не было — goroutine падает, runtime печатает stack trace и kills весь процесс.

Важно: panic в одной goroutine, не обработанный, убивает всю программу, не только эту goroutine.

recover возвращает значение из panic только если вызван прямо внутри defer’ed функции.

  • Inlining — функции до ~80 nodes (с Go 1.17), управляется -gcflags=-m=2.
  • Devirtualization — если компилятор видит конкретный тип интерфейса, заменяет виртуальный вызов прямым.
  • Bounds check elimination (BCE) — компилятор доказывает, что индекс в массиве в пределах, удаляет проверку.
  • Escape analysis — определяет, “сбегает” ли переменная в heap. Не сбежавшие — на стеке.
  • PGO (Profile-Guided Optimization) — с Go 1.21: подкладываете default.pgo в корень проекта, компилятор оптимизирует hot paths. Прирост ~5-15% (улучшение inlining + devirt).

Просмотр escape analysis: go build -gcflags="-m -m" → видно “escapes to heap”.

//go:build linux && amd64
// +build linux,amd64
package mypkg
  • Файлы _linux.go, _darwin.go, _amd64.go — автотеги.
  • //go:build (новый стиль с Go 1.17) поддерживает выражения с &&, ||, !.
  • Полезно для:
    • Платформо-зависимого кода
    • Разделения unit/integration тестов: //go:build integration + go test -tags=integration
    • Feature flags на уровне сборки

Когда нельзя избежать:

  • Системные библиотеки без Go bindings (SQLite — mattn/go-sqlite3, ImageMagick)
  • Аппаратное ускорение (CUDA, FFmpeg, OpenSSL)

Подводные камни:

  1. Стоимость вызова CGO ~ 200ns — это в 1000 раз дороже обычного Go вызова. Не вызывайте в hot loop.
  2. Goroutine с CGO-вызовом блокирует M (OS thread) на время вызова — нужно больше M.
  3. GC не видит указатели в C-памяти, и наоборот.
  4. Cross-compilation ломается — нужны cross-toolchains.
  5. CGO + race detector — медленный.
  6. Запрещено передавать Go pointers, содержащие другие Go pointers, в C (runtime check cgocheck).

С Go 1.24 появились #cgo noescape funcName и #cgo nocallback funcName — снижают overhead.

  • //go:noescape — обещание компилятору: эта функция не “сбегает” указателями в heap. Применяется к assembly-функциям и stubs.
  • //go:linkname localName remotePackage.remoteName — линкер делает алиас на символ. Используется stdlib (например, time.now алиасит runtime.nanotime).
  • //go:nosplit — функция не должна вызывать stack-growth check (для critical-path).
  • //go:noinline — запрет инлайнить (нужно для бенчмарков).

С Go 1.23+ запрещены новые использования //go:linkname для внутренних символов stdlib (только legacy).


Go умеет atomics (sync/atomic): LoadInt64, StoreInt64, CompareAndSwapInt64, AddInt64.
С Go 1.19 — типы atomic.Int64, atomic.Pointer[T], atomic.Bool (удобнее).

Lock-free паттерны:

  • Atomic counteratomic.AddInt64(&counter, 1)
  • Atomic config swapatomic.Pointer[Config]: cfg.Store(newCfg), cfg.Load()
  • Lock-free queue — Michael-Scott queue (есть реализации в go.uber.org/atomic)
  • RCU (Read-Copy-Update) — копируем структуру, меняем, atomically swap указатель

Главное правило: lock-free дороже Mutex’а только при низкой контеншн. При высокой контеншн atomics проигрывают (cache line ping-pong).

sync.Map оптимизирован под 2 кейса:

  1. Write-once, read-many (например, кэш конфигов).
  2. Disjoint key sets — разные goroutines пишут в разные ключи.

Внутри: двойной слой — read-only readMap (atomic-pointer) + dirtyMap (mutex). Чтения идут без лока, пока ключ есть в readMap.

RWMutex + map:

  • Лучше для write-heavy или mixed нагрузки.
  • Лучше для range — sync.Map iterate медленный.
  • Меньше памяти.

Бенчмарки (грубо):

  • 90% read / 10% write на 1M ключей: sync.Map ~2-3x быстрее.
  • 50/50: RWMutex+map ~1.5x быстрее.
  • 100% write: RWMutex+map существенно быстрее.

Источник: dev.to sync.Map

var bufPool = sync.Pool{
New: func() any { return new(bytes.Buffer) },
}
buf := bufPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufPool.Put(buf)
}()

Что класть:

  • Большие, дорого-инициализируемые объекты (bytes.Buffer, JSON-decoder, X.509 cert)
  • Объекты, которые переиспользуются часто в hot path

Что НЕ класть:

  • Маленькие объекты (<100B) — overhead pool’а больше выгоды
  • Объекты с указателями на внешние данные (могут утечь)
  • Объекты с финализаторами

Race с GC:

  • Каждый GC цикл опустошает sync.Pool (до Go 1.13 — полностью; с 1.13 — двухуровневый, более щадящий).
  • Поэтому Pool не подходит для долгоживущего кэша — используйте freelist-структуру.

Бонус — per-P sharded: sync.Pool внутри использует per-P pools (no contention).

Источник: Leapcell sync.Pool

golang.org/x/sync/errgroup:

g, ctx := errgroup.WithContext(ctx)
for _, url := range urls {
url := url
g.Go(func() error { return fetch(ctx, url) })
}
if err := g.Wait(); err != nil { return err }
  • Первая ошибка → ctx.Cancel() для всех остальных.
  • g.SetLimit(N) (с Go 1.21) — ограничение concurrency.

semaphore.Weighted:

sem := semaphore.NewWeighted(10) // 10 одновременно
if err := sem.Acquire(ctx, 1); err != nil { ... }
defer sem.Release(1)
  • Weighted = можно взять K единиц за раз (для heavy запросов).

singleflight:

var g singleflight.Group
val, err, _ := g.Do("user:123", func() (any, error) {
return loadUserFromDB(123)
})
  • 100 параллельных запросов с тем же ключом → DB query будет один, остальные ждут результата.
  • Стандартное решение thundering herd на кэш-мисс.

Bounded channel — простой backpressure:

jobs := make(chan Job, 100) // буфер 100; если полон — producer ждёт

Drop-old / drop-new:

select {
case jobs <- j:
default: // drop, log "queue full"
}

Token bucket:

import "golang.org/x/time/rate"
lim := rate.NewLimiter(rate.Limit(100), 200) // 100 RPS, burst 200
if err := lim.Wait(ctx); err != nil { return err }

Token bucket (golang.org/x/time/rate) — Limit(rps), burst.
Leaky bucket — фиксированный rate; нет burst.
Sliding window — точнее, но дороже (хранит timestamps).

Для distributed — Redis с Lua-скриптом или сервис envoy/ratelimit, или Uber’s ratelimit library.

Принципы (вдохновлены Trio / Kotlin coroutines):

  1. Никогда не запускайте goroutine “в никуда” без owner’а.
  2. Все дочерние goroutines должны завершаться до возврата родительской функции.
  3. Ошибки распространяются вверх.
  4. Cancellation распространяется вниз.

В Go это errgroup.WithContext + defer g.Wait(). Никаких “fire and forget” в hot path.

  • go test -race — всегда в CI.
  • testing/synctest (экспериментальный, Go 1.24): виртуальное время, детерминированные тесты concurrency.
  • go.uber.org/goleakdefer goleak.VerifyNone(t) в каждом тесте, ловит утечки goroutines.
  • Stress-тестированиеgo test -count=1000 -race.

Go Memory Model (обновлён в Go 1.19): атомики sequentially consistent.

Гарантии:

  • Channel: N-я успешная отправка happens-before N-го успешного приёма. Закрытие канала happens-before приёма zero-value.
  • Mutex: N-й Unlock happens-before (N+1)-го Lock.
  • Once: Do(f) — единственный вызов f happens-before любого возврата из Do.
  • Atomic: все атомарные операции sequentially consistent (с Go 1.19).
  • WaitGroup: Done happens-before возврата из Wait.

Без синхронизации — нет гарантий! Чтение переменной без синхронизации в Go = data race = неопределённое поведение (компилятор может переставить чтения).

Источник: Go Memory Model


Стандартный pprof:

import _ "net/http/pprof"
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()

Endpoints: /debug/pprof/heap, /profile, /goroutine, /block, /mutex, /allocs, /trace.

Окно терминала
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
(pprof) top10
(pprof) list FunctionName
(pprof) web # flame graph в браузере

Continuous profiling — Pyroscope / Grafana Cloud Profiles:

import "github.com/grafana/pyroscope-go"
pyroscope.Start(pyroscope.Config{
ApplicationName: "my-service",
ServerAddress: "http://pyroscope:4040",
Tags: map[string]string{"region": "eu"},
ProfileTypes: []pyroscope.ProfileType{
pyroscope.ProfileCPU,
pyroscope.ProfileAllocObjects,
pyroscope.ProfileAllocSpace,
pyroscope.ProfileInuseObjects,
pyroscope.ProfileInuseSpace,
},
})

Overhead: ~2-5% CPU.
Plus: можно сравнивать профили до/после деплоя — gold standard для перформанс-работы.

Источник: Pyroscope Go docs

  • X-ось — относительное время в функции (НЕ хронология).
  • Y-ось — стек вызовов (root внизу).
  • Ширина — сколько CPU/allocations.
  • Ищите широкие плато на вершине — это место для оптимизации.
  • pprof -http=:8080 profile.pb.gz → встроенный flame в браузере.

Parca / Pixie:

  • eBPF-агент на ноде, samples stack traces ~19-100Hz.
  • Не требует инструментации кода — работает с любым Go процессом.
  • Overhead <1%.
  • Pixie умеет dynamic logging для Go: указываете функцию, eBPF-пробник собирает аргументы и возврат.

Полезно когда:

  • pprof endpoint выключен / нельзя добавить
  • Нужно профилировать всю ноду (включая сторонние процессы)

Источник: Pixie eBPF docs

// ПЛОХО: каждая итерация — аллокация
var result string
for _, s := range parts {
result += s
}
// ХОРОШО
var b strings.Builder
b.Grow(estimatedSize) // pre-allocate
for _, s := range parts {
b.WriteString(s)
}
return b.String()
// []byte vs string
func process(b []byte) {
s := string(b) // АЛЛОКАЦИЯ (копия)
s := unsafe.String(...) // zero-copy (с Go 1.20)
}
// Pre-allocate slices
xs := make([]int, 0, 1000) // знаем capacity заранее

Эскейп анализ:

Окно терминала
go build -gcflags="-m" .
# main.go:10:13: &x escapes to heap

Правила:

  • Возврат указателя из функции → escape to heap.
  • Передача в interface{} → escape (interface boxing).
  • Передача в goroutine → может escape.
  • Большой объект (>10MB) → всегда heap (Go runtime).
СимптомТипГде смотреть
CPU 100%, latency растётCPU-boundpprof profile, flame graph
CPU низкое, latency растётI/O-boundtrace (go tool trace), network metrics
GC pauses частые, p99 латенси скачетGC pressurepprof heap, GODEBUG=gctrace=1
Goroutines растутGoroutine leakpprof goroutine, look for “chan receive”
Mutex contentionLock contentionpprof mutex, pprof block

GODEBUG=gctrace=1 ./app — в stderr каждый GC цикл.

Окно терминала
go test -bench=. -benchmem -count=10 > old.txt
# меняем код
go test -bench=. -benchmem -count=10 > new.txt
benchstat old.txt new.txt

Показывает статистически значимые изменения с p-value.

В Kubernetes:

До Go 1.25:

import _ "go.uber.org/automaxprocs" // в main

Это поправит GOMAXPROCS под cgroup CPU limit.

С Go 1.25 — GOMAXPROCS container-aware by default (проверяет cgroup limit каждые 10 секунд и пересчитывает).

Рекомендуемые env vars:

env:
- name: GOMAXPROCS # с Go 1.25 можно опустить
valueFrom: ... # обычно автоматом
- name: GOMEMLIMIT
value: "900MiB" # 90% от memory limit пода
- name: GOGC
value: "100" # default, можно 50 для low-latency, 200 для batch

Anti-pattern: GOMAXPROCS=8 в поде с CPU limit 0.5 → cgroup throttling, p99 latency взрывается. Improvement: ~25x p99.

Источник: Container-aware GOMAXPROCS, VictoriaMetrics k8s CPU


HTTP/1.1HTTP/2HTTP/3
ТранспортTCPTCPQUIC (UDP)
МультиплексированиеPipelining (плохо)Streams (отлично)Streams (отлично, без HOL blocking)
Header compressionHPACKQPACK
TLSOptionalEffectively requiredRequired (TLS 1.3)
Server pushДа (deprecated)Нет

В Go:

  • net/http — HTTP/1.1 + HTTP/2 (transparent с HTTPS).
  • HTTP/3: github.com/quic-go/quic-go или github.com/quic-go/quic-go/http3.

Head-of-line blocking: HTTP/2 → один TCP stream, packet loss блокирует всех. HTTP/3 → независимые QUIC streams.

  • github.com/gorilla/websocket — самый популярный, стабильный.
  • github.com/coder/websocket (бывший nhooyr/websocket) — современнее, использует context, проще API.

Паттерны:

  • Одна goroutine для чтения, одна для записи (обязательно).
  • Heartbeat (ping/pong) каждые 30 сек.
  • Backpressure через bounded channel перед WriteJSON.

Типы вызовов:

  1. Unary — request/response
  2. Server streaming — client отправляет 1, server возвращает stream
  3. Client streaming — client стримит, server отвечает 1
  4. Bidirectional streaming — оба стримят

Interceptors:

func loggingInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (any, error) {
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("%s took %v err=%v", info.FullMethod, time.Since(start), err)
return resp, err
}

Deadlines:

ctx, cancel := context.WithTimeout(parent, 2*time.Second)
defer cancel()
resp, err := client.GetUser(ctx, req)

Deadline пропагируется в server и дальше во все downstream вызовы.

Retries:

import "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry"
conn, _ := grpc.Dial(addr, grpc.WithUnaryInterceptor(
retry.UnaryClientInterceptor(
retry.WithMax(3),
retry.WithBackoff(retry.BackoffExponential(100*time.Millisecond)),
retry.WithCodes(codes.Unavailable, codes.DeadlineExceeded),
),
))

ВАЖНО: Retry ТОЛЬКО для идемпотентных RPC. Иначе двойные платежи и т.п.

  • Well-known types: Timestamp, Duration, Empty, Struct, Any, FieldMask.
  • Custom options:
    extend google.protobuf.FieldOptions {
    bool sensitive = 50001;
    }
    message User {
    string email = 1 [(sensitive) = true];
    }
  • Versioning:
    • Не меняйте номера полей.
    • Не переиспользуйте удалённые номера — reserved 5, 7;.
    • Новые поля — optional (proto3 — все optional).
    • Удаление поля — НЕ удаляйте номер, добавьте reserved.

connectrpc.com — Buf Build.

  • Работает над HTTP/1.1, HTTP/2 (HTTP/3 на подходе).
  • Совместим с gRPC (можно поднять Connect сервер, обслуживать и Connect, и gRPC clients).
  • Проще debug: можно curl в endpoint, если JSON.
  • Идиоматичный Go API (без unnatural patterns gRPC).
cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
clientCAs := x509.NewCertPool()
clientCAs.AppendCertsFromPEM(caBundle)
tlsCfg := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientCAs: clientCAs,
ClientAuth: tls.RequireAndVerifyClientCert, // mTLS!
MinVersion: tls.VersionTLS13,
}

mTLS = клиент тоже предъявляет сертификат, сервер проверяет. Стандарт для service-to-service inside cluster.


EXPLAIN ANALYZE:

EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM orders WHERE user_id = 42 AND created_at > '2025-01-01';

Что читать:

  • Seq Scan — плохо на больших таблицах
  • Index Scan / Bitmap Index Scan — хорошо
  • Hash Join / Merge Join / Nested Loop — выбор зависит от размера
  • actual time=X..Y rows=N loops=Mactual time × loops = реальное общее время
  • Rows estimate vs actual — расхождение в 10x → нужен ANALYZE table

Индексы:

  • B-tree (default) — equality + range
  • Hash — только equality (редко нужен)
  • GIN — массивы, JSONB, full-text search
  • GiST — геометрия, диапазоны, full-text
  • BRIN — большие отсортированные таблицы (логи)
  • Partial indexCREATE INDEX ... WHERE deleted_at IS NULL;
  • Covering index (INCLUDE) — индекс хранит дополнительные колонки, чтобы index-only scan

MVCC:

  • Каждая строка хранит xmin, xmax (creating/deleting transaction IDs).
  • Транзакция видит только строки с xmin ≤ snapshot_xid и (xmax > snapshot_xid или NULL).
  • Update не меняет строку — создаёт новую версию, помечает старую как dead tuple.
  • VACUUM чистит dead tuples (но не возвращает место OS — для этого VACUUM FULL, который lockает таблицу).
  • Autovacuum — фоновой процесс, запускается при n_dead_tup > autovacuum_vacuum_threshold + scale_factor * n_live_tup.

Bloat — таблица + индексы растут из-за dead tuples. Мониторить через pg_stat_user_tables.

Источник: PostgreSQL MVCC

Connection pooling:

  • pgx/pgxpool (Go) — пул на уровне приложения.
  • PgBouncer — внешний пулер, режим transaction pooling (1 backend на транзакцию).
  • Pgcat — современный (rust), supports read-replicas routing.

Two-Phase Commit (2PC) — не используйте в микросервисах. Блокирующий, single point of coordinator failure.

Saga pattern:

  • Orchestration — координатор знает все шаги, дёргает каждый сервис, при ошибке вызывает компенсации.
  • Choreography — каждый сервис публикует событие, другие реагируют.

Choreography пример (бронирование):

OrderCreated → PaymentService (charges) → PaymentSucceeded
→ InventoryService (reserve) → InventoryReserved
→ ShippingService (book) → ShippingFailed!!!
→ InventoryService (compensate: release)
→ PaymentService (compensate: refund)

Правила:

  • Каждый шаг и каждая компенсация идемпотентны (вызовут несколько раз — норм).
  • Не теряем события (см. Outbox).
  • Логируем sagaID в каждом событии.

Источник: Saga in Go

Проблема: атомарно записать в БД + опубликовать в Kafka — нельзя (две системы).

Решение:

-- В той же транзакции:
INSERT INTO orders ...;
INSERT INTO outbox (event_type, payload, created_at) VALUES (...);
COMMIT;

Фоновой outbox-relay читает outbox, публикует в Kafka, удаляет/помечает sent.

Современный подход — CDC (Change Data Capture):

  • Debezium читает PostgreSQL WAL → публикует изменения в Kafka.
  • Не нужно отдельной outbox-таблицы, но требует setup.
  • Состояние = последовательность событий.
  • Event Store = append-only лог.
  • Текущее состояние = свёртка всех событий (left fold).
  • Snapshots — периодически сохраняем “промежуточное” состояние для скорости.

Pros: аудит, time travel, отлаживаемость.
Cons: schema evolution событий — сложно, нужны upcasters.

Command Query Responsibility Segregation:

  • Write model (Commands) — normalized, optimized for consistency.
  • Read model (Queries) — denormalized, optimized for queries (materialized views).
  • Write → publish event → projection обновляет read model (eventually consistent).

Подходит, когда чтение сильно отличается от записи (например, отчёты vs OLTP).

  • Read replicas — readonly slaves для масштабирования чтения. Async repl → eventual consistency, read-your-writes проблема.
  • Sharding (горизонтальное) — данные разделены по ключу (user_id mod N). Сложно с cross-shard JOIN.
  • Partitioning (PostgreSQL declarative) — одна логическая таблица, физически разбита (по дате/range/hash). Полезно для большой events таблицы.
Use caseНе подходит для
MongoDBДокументы с гибкой схемой, прототипы, real-time analytics на докахСложные JOIN, аналитика на BIG data
CassandraВысокий write throughput, time-series, гео-распределениеСложные queries, JOIN, ad-hoc analytics
ClickHouseOLAP, аналитика, agg-queries по миллиардам строкOLTP, частые UPDATE/DELETE
DynamoDBKV/document с предсказуемой latency на AWSСложные queries без знания шардинга
RedisКэш, sessions, pub/sub, locks, queuesPersistent storage главного бизнеса
ElasticsearchFull-text search, логи, поиск с фасетамиOLTP, ACID транзакции
  • Inverted index — для каждого токена хранится список doc ID.
  • Analyzer = tokenizer + filters (lowercase, stop words, stemming).
  • Mapping — схема (определять заранее, не auto!).
  • Шарды — primary + replicas; реплики читают, primary пишет.
  • Bulk API — массовая индексация.
  • Refresh interval — стандарт 1 сек (eventually searchable), для high-throughput ставят 30 сек.

Go клиенты: github.com/elastic/go-elasticsearch/v8 или github.com/opensearch-project/opensearch-go.


В сетевом разделении (P) выбираем:

  • CP — consistency over availability (PostgreSQL master-replica, etcd, ZooKeeper)
  • AP — availability over consistency (Cassandra, DynamoDB eventual)

В реальности нет ни одной системы 100% CP или AP — это спектр. PACELC уточняет: при partition выбираем PA или PC; otherwise (else) — латентность L или консистентность C.

API должно быть безопасно вызвать N раз с одним и тем же результатом.

Способы:

  • Idempotency-Key header (Stripe-style): клиент генерирует UUID, server хранит результаты ключей.
  • Natural idempotency: PUT /resource/{id} идемпотентен; POST /resources — нет.
  • Conditional updates в БД: UPDATE ... WHERE version = ? AND status = 'pending'.
  • Read replicas, кэши, шардинг → eventually consistent.
  • Read-your-writes — если пользователь только что написал, должен видеть свою запись. Решение: после write читать с master в течение N секунд.
  • Monotonic reads — нельзя “откатить” в чтении. Решение: sticky session к одной реплике.

Redis Redlock:

  • Локает на N (5) независимых Redis. Замок считается взятым, если acquired на majority (3/5).
  • Лучше использовать готовое: github.com/go-redsync/redsync.
  • Caveat (Martin Kleppmann): Redlock небезопасен при clock drift. Для критичных задач — fencing tokens или etcd/ZK.

etcd lock:

session, _ := concurrency.NewSession(etcdClient)
mu := concurrency.NewMutex(session, "/locks/myresource")
mu.Lock(ctx); defer mu.Unlock(ctx)

Linearizable (Raft consensus), но дороже.

Когда что:

  • Redlock — кэширование, де-дупликация, “best effort”
  • etcd/ZK — финансовые транзакции, единственный лидер

Источник: How to Implement Redlock in Go

  • Consul — health checks, KV store, multi-DC.
  • etcd — strongly consistent KV, основа Kubernetes.
  • Kubernetes Service / DNS — наиболее распространённый сейчас (через kube-dns / CoreDNS).

Circuit Breaker:

  • 3 состояния: Closed (норма) → Open (ошибки выше threshold, не пускаем) → Half-Open (пробуем 1 запрос).
  • Library: github.com/sony/gobreaker или github.com/afex/hystrix-go.

Retries with backoff + jitter:

backoff := time.Second
for i := 0; i < maxRetries; i++ {
if err := call(); err == nil { return nil }
time.Sleep(backoff + jitter())
backoff *= 2
}

Library: github.com/avast/retry-go или cenkalti/backoff.

Timeouts — всегда. На HTTP, на DB query, на channel send.

Bulkhead — изоляция ресурсов:

  • Отдельные goroutine pools / pools соединений для разных downstream.
  • Если один deps медленный, не съест все слоты.

OpenTelemetry (стандарт 2025):

import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
exporter, _ := otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint("collector:4317"))
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource.NewWithAttributes("service.name", "my-svc")),
)
otel.SetTracerProvider(tp)

Backend: Jaeger / Tempo / Grafana Cloud. Все через OTLP (OpenTelemetry Protocol).

Trace propagationtraceparent HTTP header (W3C standard).

Sampling: не семплируйте 100% в проде — дорого. Tail-sampling (Tempo) или head-sampling 1-10%.

Источник: OTel Go docs

  • API Gateway — единая точка входа, auth/rate-limit/routing. Kong, Traefik, Envoy, Tyk.
  • BFF (Backend For Frontend) — отдельный backend для каждого клиента (mobile, web), агрегирует данные из multiple microservices.

Микросервисы:

  • Pros: независимый деплой, разные команды, разные стеки, scaling per-service.
  • Cons: distributed system complexity (сеть, eventually consistent, traceability), overhead на тривиальной фиче.

Модульный монолит:

  • Один артефакт, чёткие границы внутри (Hexagonal/DDD).
  • Деплоится за секунды, transactional consistency by default.
  • Start here. Разделять на сервисы — когда реально нужно.
  • Сервисы общаются через события (через Kafka/NATS), не через RPC.
  • Pros: loose coupling, replay, audit, scalability.
  • Cons: eventual consistency, debug сложнее (trace через events).

(Outbox и Saga см. раздел 5.)

Inbox pattern:

  • При получении события из Kafka — пишем в inbox (по unique idempotency key) + бизнес-таблицу в одной транзакции.
  • Если событие уже было — skip. Защита от дублей.

Aggregate — кластер entities + value objects, транзакционная граница + invariant’ы.
Пример: Order (root) содержит OrderItem[], ShippingAddress (VO). Любая модификация items идёт через Order.

Bounded Context — модель валидна только в своём контексте. “Customer” в Sales BC ≠ “Customer” в Billing BC.

Ubiquitous Language — единый словарь для команды + домена. Должен быть в коде, БД, доке.

Value Object — immutable, без identity (Money, Address, Email).

Domain Service — операция, не относящаяся к одному агрегату (например, перевод денег между счетами).

Источник: DDD in Go (2025)

Слои:

  • Domain (entities, VO, domain services) — НЕТ зависимостей.
  • Application (use cases, ports) — зависит от Domain.
  • Adapters (HTTP handlers, DB repositories, Kafka consumers) — зависит от Application через интерфейсы.

Структура:

internal/
domain/ # сущности, VO, бизнес-правила
order/
order.go
events.go
application/ # use cases (Service'ы)
order/
create_order.go # OrderService.Create()
ports.go # OrderRepository interface
adapter/
http/ # gin/chi/echo handlers
postgres/ # OrderRepository implementation
kafka/ # event consumers/producers
grpc/
cmd/
server/main.go # wire it all together

Зависимости идут ВНУТРЬ: adapter знает application, application знает domain. Никогда не наоборот.

Стандарт сообщества — golang-standards/project-layout (с оговорками — это не official).

/cmd/<binary>/main.go — entry points
/internal/ — приватный код проекта (нельзя импортить извне)
/pkg/ — публичные библиотеки (если open-source)
/api/ — OpenAPI/proto файлы
/configs/ — конфиги (yaml, default values)
/deployments/ — k8s manifests, helm
/migrations/ — SQL миграции (goose/golang-migrate)
/scripts/ — bash утилиты
/test/ — integration тесты
go.mod, Makefile, Dockerfile, .golangci.yaml

Источник: Go Project Structure (2025)

  • spf13/viper — самый популярный, поддерживает YAML/JSON/TOML/env/etcd/Consul.
  • knadh/koanf — современная альтернатива (легковеснее, чище).
  • envconfig (kelseyhightower) — только env-vars, очень простой.
  • go-playground/validator — валидация структуры конфига.

12-Factor — конфиг через env vars (контейнеры).

  • Local — env var + sync.RWMutex map.
  • Centralized — Unleash (open-source), LaunchDarkly, ConfigCat, GO Feature Flag.
  • Use cases: gradual rollout, A/B, kill switch, canary by user_id mod.

Базовое:

  • Topic → разделён на partitions.
  • Каждая partition — лог, упорядочена.
  • Consumer group — N consumers разделяют партиции (1 partition = 1 consumer внутри группы).
  • Offset — текущая позиция consumer’а в partition.

Retention: время / размер. Стандарт 7 дней. Compacted topics — последнее значение по ключу.

Exactly-once (только внутри Kafka):

  • Producer: enable.idempotence=true (дедуп по producer ID).
  • Consumer-Producer pattern: transactional API (transactional.id), commit offset + write в одной транзакции.
  • При записи в external system — не работает. Решения: idempotent upserts (UPSERT по key), outbox.

Балансировка:

  • Старый: Eager Rebalance → STW для всей группы.
  • Новый (default с Kafka 2.4): Cooperative Sticky — incremental, минимальный disrupt.

Go клиенты:

  • github.com/segmentio/kafka-go — pure Go, простой API.
  • github.com/confluentinc/confluent-kafka-go — обёртка над librdkafka (C), самый фичастый.
  • github.com/twmb/franz-go — pure Go, современный, очень performant.
  • NATS Core — at-most-once, pub/sub, request/reply, fan-out.
  • JetStream — persistent, at-least-once, exactly-once delivery, replay.
  • Производительность — миллионы msg/sec, low latency (<1ms).
  • Простой Go-native client: github.com/nats-io/nats.go.
  • AMQP 0.9.1 (есть и 1.0).
  • Exchanges (direct, topic, fanout, headers) → routing keys → queues.
  • DLQ, TTL, priority queues, delayed messages — встроено.
  • Хорош для per-message ACK, complex routing между сервисами.
Use caseTool
Event log, replay, audit, аналитикаKafka
Микросервисы request/reply, низкая латенсиNATS
Task queue, сложный routing, ACK per messageRabbitMQ
Простой fire-and-forget, embeddedNATS Core
ETL, streaming, analyticsKafka + Kafka Connect

Источник: Kafka vs NATS vs RabbitMQ (2025)


import "github.com/testcontainers/testcontainers-go/modules/postgres"
container, err := postgres.Run(ctx, "postgres:16",
postgres.WithDatabase("test"),
postgres.WithUsername("user"),
postgres.WithPassword("pass"),
testcontainers.WithWaitStrategy(wait.ForLog("database system is ready").WithOccurrence(2)),
)
defer container.Terminate(ctx)
dsn, _ := container.ConnectionString(ctx)
// прогоняем миграции, тестируем

Поддерживает PG, MySQL, Mongo, Redis, Kafka, Localstack (AWS), Selenium и др.

Consumer side:

  • Описывает ожидания: “при GET /users/1 верни {name: …}”
  • Генерирует pact файл (JSON).
  • Публикует в Pact Broker.

Provider side:

  • Скачивает pact’ы из Broker.
  • Запускает provider в тестовом режиме.
  • Прогоняет ожидания: реально ли так отвечает.

Library: github.com/pact-foundation/pact-go.

github.com/leanovate/gopter или pgregory.net/rapid.

rapid.Check(t, func(t *rapid.T) {
xs := rapid.SliceOf(rapid.Int()).Draw(t, "xs")
sorted := Sort(xs)
require.True(t, sort.IntsAreSorted(sorted))
require.ElementsMatch(t, xs, sorted)
})

Генерит рандомные входы, ищет минимальный пример, где свойство нарушается.

github.com/go-gremlins/gremlins — мутирует код (меняет > на <=, etc), запускает тесты, считает coverage поведенческий. Если тесты прошли при мутации — слабые тесты.

k6 — JavaScript-сценарии, можно из Go вызывать.

import http from 'k6/http';
export const options = { vus: 100, duration: '30s' };
export default () => { http.get('http://localhost:8080/api'); };

Vegeta — pure Go, постоянная RPS:

Окно терминала
echo "GET http://localhost:8080/api" | vegeta attack -rate=1000 -duration=30s | vegeta report
  • Chaos Mesh (Kubernetes) — pod kill, network delay, DNS chaos.
  • Toxiproxy — TCP-прокси с искусственной задержкой/loss.
  • gremlin/chaos-toolkit.

Принципы Netflix’s Chaos Monkey: убиваем prod нестабильные сервисы регулярно, чтобы команды строили резистентность.


  • Traces (стабильно), Metrics (стабильно), Logs (beta в Go).
  • OTLP exporter → OTel Collector → Tempo/Jaeger/Loki/Prometheus.
  • Auto-instrumentation для net/http, database/sql, gRPC, Kafka, etc.
  • Pull model — Prometheus сам ходит на /metrics endpoint.
  • TSDB — time series database.
  • PromQL — query language. rate(http_requests_total[1m]), histogram_quantile(0.99, ...).
import "github.com/prometheus/client_golang/prometheus/promhttp"
http.Handle("/metrics", promhttp.Handler())

Стандартные метрики (RED):

  • Rate — RPS
  • Errors — error rate
  • Duration — latency (histogram!)

USE для ресурсов: Utilization, Saturation, Errors.

  • ELK (Elasticsearch + Logstash + Kibana) — мощный full-text, но дорог.
  • Loki (Grafana) — индексирует только labels, blob storage для логов. Дешевле в 10x.

Структурированные логи (JSON):

import "log/slog" // Go 1.21+
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
slog.Info("user logged in", "user_id", 42, "trace_id", traceID)

Альтернативы: zerolog, zap (Uber).

  • /livez — жив ли процесс. Просто 200 OK.
  • /readyz — готов ли принимать трафик. Проверяет downstream (DB ping, kafka ready).
  • /startupz — пока приложение инициализируется (миграции, прогрев кэша).
  • SLI — что меряем (например, latency p99 < 100ms).
  • SLO — целевое значение (99.9% запросов за 100ms).
  • SLA — контракт с клиентом, юридические последствия.

Error budget = 100% - SLO. Если 99.9% SLO, бюджет 0.1% (43 мин/мес). Превысили → стоп фич, фикс надёжности.

Sloth (github.com/slok/sloth) — генератор Prometheus rules для SLO.

Источник: Uptrace SLO 2025


  • Pod — атомарная единица, 1+ контейнеров с общим network/volume.
  • Deployment — управляет ReplicaSet’ом, rolling update, rollback.
  • Service — стабильный endpoint для группы pod’ов (ClusterIP / NodePort / LoadBalancer).
  • ConfigMap / Secret — конфиг и секреты.
  • Ingress / Gateway API — HTTP роутинг снаружи.
  • Job / CronJob — одноразовые / периодические.
  • Templates с Go templating (sprig functions).
  • values.yaml — параметры.
  • helm install, helm upgrade, helm rollback.
  • Альтернативы: Kustomize (overlays без templating), Cue/Timoni.
livenessProbe:
httpGet: { path: /livez, port: 8080 }
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet: { path: /readyz, port: 8080 }
periodSeconds: 5
failureThreshold: 2
startupProbe: # пока initContainer/миграции
httpGet: { path: /readyz, port: 8080 }
failureThreshold: 30
periodSeconds: 10

Graceful shutdown:

sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT)
<-sig
// 1. Mark not ready (failing readiness)
ready.Store(false)
// 2. Wait for LB to update
time.Sleep(5 * time.Second)
// 3. Shutdown server with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
server.Shutdown(ctx)

preStop hooksleep 10, чтобы LB обновился до того, как контейнер примет SIGTERM.

resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2
memory: 2Gi

Влияние на Go:

  • До Go 1.25 / без automaxprocs — GOMAXPROCS = host CPU count → CPU throttling, p99 latency взрывается.
  • Memory limit без GOMEMLIMIT → OOM kill, потому что Go runtime не знает о лимите.

Совет: GOMEMLIMIT = 80-90% от memory limit, GOGC = 100 (default) или 50 (low-latency).

  • HPA (Horizontal Pod Autoscaler) — scale по CPU/memory/custom metrics (через Prometheus Adapter).
  • VPA (Vertical Pod Autoscaler) — изменяет request/limit под нагрузку.
  • KEDA — event-driven scaling (Kafka lag, queue size).

name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.25' }
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
- run: go test -race -coverprofile=cover.out ./...
- run: go vet ./...
- uses: golangci/golangci-lint-action@v6
  • golangci-lint — мета-линтер (govet, staticcheck, errcheck, gosec, gocritic, revive, etc).
  • gosec — security (включается в golangci-lint).
  • govulncheck — известные CVE в зависимостях.
  • trivy — Docker image scan (vulnerabilities в base image).
  • syft — SBOM генерация.

В CI:

- run: govulncheck ./...
- run: trivy image myapp:latest
  • ~/.cache/go-build — кэш скомпилированных пакетов.
  • ~/go/pkg/mod — кэш модулей.
  • В GitHub Actions: actions/cache@v4.

goreleaser.yaml:

builds:
- env: [CGO_ENABLED=0]
goos: [linux, darwin]
goarch: [amd64, arm64]
archives:
- format: tar.gz
dockers:
- image_templates: ["ghcr.io/me/app:{{.Version}}"]

goreleaser release → собирает бинарники под все ОС/arch, делает Docker images, GitHub release, changelog.


  1. Broken Access Control — checks роли на каждый ресурс.
  2. Cryptographic Failures — не самописная крипта.
  3. Injection (SQL, command, etc).
  4. Insecure Design.
  5. Security Misconfiguration — default passwords, debug в проде.
  6. Vulnerable Components — govulncheck.
  7. Authentication Failures — слабые пароли, rate-limit на login.
  8. Software/Data Integrity Failures — supply chain attacks.
  9. Logging/Monitoring Failures.
  10. SSRF — server-side request forgery.

JWT:

  • Claims: iss, sub, aud, exp, iat, nbf, jti.
  • Алгоритмы: RS256/ES256 (asymmetric, лучше для distributed). HS256 — только если secret один.
  • Never trust alg: none. Library должна валидировать алгоритм.
  • Хранить access token ~15 мин, refresh — 7-30 дней.
  • HttpOnly + Secure cookies для refresh, чтобы JS не доступен.

OAuth 2.0 / OIDC:

  • Authorization Code + PKCE — стандарт 2025 (PKCE мандатно для всех flows в OAuth 2.1).
  • state — против CSRF; nonce — против replay (OIDC).
  • Access Token → API; ID Token → клиент.
  • DPoP / mTLS — sender-constrained tokens (предотвращают stolen-token attacks).

Источник: OAuth 2.0 best practices (2025)

  • RBAC — пользователю даём роли, роли = набор permissions.
  • ABAC — атрибутный, например “owner_id == request.user_id AND time < 18:00”.
  • Casbin — Go библиотека, поддерживает ACL, RBAC, ABAC, ReBAC.
  • OpenFGA / Zanzibar — для relationship-based auth (Google Drive-like sharing).
  • bcrypt — стандарт, медленный (12 rounds).
  • argon2id — современный, лучше для GPU resistance. golang.org/x/crypto/argon2.
  • scrypt — есть в stdlib.

Никогда:

  • MD5, SHA1, SHA256 без соли.

SQL:

// ВСЕГДА placeholders
db.QueryRow("SELECT ... WHERE id = $1", id)
// НИКОГДА конкатенация
// db.QueryRow("SELECT ... WHERE id = " + id) // SQL INJECTION!

XSS: html/template (НЕ text/template) для HTML — auto-escape.
CSRF: для cookies-auth — gorilla/csrf middleware (double-submit cookie). Для JWT в header — обычно не страшно.

Окно терминала
govulncheck ./...
# или
go install golang.org/x/vuln/cmd/govulncheck@latest

Использует static analysis — репортит только если уязвимый код достижим из ваших entrypoints (меньше шума).


В РФ на Middle 2 — обычно LLD (low-level design) + базовый HLD (high-level). Senior — глубокий HLD.

Структура ответа (45-60 мин):

  1. Requirements clarification (5 мин) — функциональные, нефункциональные. Цифры (DAU, QPS, storage).
  2. High-level design (10 мин) — компоненты на доске.
  3. Deep dive (20 мин) — выбранный компонент в детали.
  4. Scaling, bottlenecks, trade-offs (10 мин).
  5. Failure modes, observability (5 мин).

URL shortener (Bit.ly):

  • Хеширование: base62(MD5(url + salt))[:7] vs counter base62.
  • Storage: KV (Redis для hot, PG для cold).
  • Read:Write ~1000:1 → агрессивный кэш.
  • Custom URLs → проверка коллизий.
  • Аналитика — отдельный async pipeline (Kafka → ClickHouse).

Chat (WhatsApp / Telegram):

  • WebSocket connection manager (sharded by user_id).
  • Message store: Cassandra (write-heavy, partition by chat_id, sort by timestamp).
  • Presence service — Redis + pub/sub.
  • Push notifications для offline.

Лента (Twitter / Instagram):

  • Fan-out on write — при post автор пишет в feed подписчиков (хорошо для read, плохо для celeb).
  • Fan-out on read — query при заходе пользователя (хорошо для write).
  • Hybrid — fan-out для регулярных юзеров, fan-out on read для celeb.
  • Cache: Redis с feed_id → [post_id1, post_id2].

Такси (Uber):

  • Геопоиск — Redis GEO commands или PostgreSQL PostGIS.
  • Matching driver-rider — отдельный сервис, hot data в Redis.
  • Real-time location updates через WebSocket.
  • Latency: p50, p95, p99, p999. Меряем worst case, не average.
  • Throughput: RPS / TPS. Little’s law: L = λ × W (concurrent users = rate × wait time).
  • Availability: 99.9% = 8.76h downtime/year; 99.99% = 52 мин; 99.999% = 5 мин.
DAU = 100M
Запросов на юзера = 50
Total = 5 × 10^9 / day = ~60K RPS average; peak ×3 = 180K RPS
Storage: 1KB per record, 5B records/day = 5TB/day = 150TB/month
3 replicas → 450TB/month
Pod на 1K RPS → 60 pods × 3 (peak buffer) = 180 pods
  1. Client-side — Cache-Control headers, ETags.
  2. CDN — статика (CloudFront, Cloudflare).
  3. API Gateway / Reverse proxy — Varnish, Nginx.
  4. Application-level — in-memory (groupcache, bigcache, freecache).
  5. Distributed — Redis, Memcached.
  6. Database query cache — PostgreSQL prepared statements, materialized views.

Cache patterns:

  • Cache-aside (lazy) — read: cache miss → DB → cache fill. Write: invalidate.
  • Write-through — пишем сразу в cache + DB.
  • Write-back — пишем в cache, async в DB (риск потери).
  • TTL — у всего, чтобы не залипало.

Thundering herd — миллион запросов на cache miss одновременно → DB перегружена. Решение: singleflight + распределённый lock + jitter в TTL.


15. Типичные вопросы на собеседовании Middle 2 Go (100+ вопросов)

Заголовок раздела «15. Типичные вопросы на собеседовании Middle 2 Go (100+ вопросов)»

1. Опишите GMP модель.
G — goroutine, M — OS thread, P — logical processor. P держит LRQ, GOMAXPROCS = число P.

2. Что такое work stealing?
Когда LRQ P пуст, забирает половину goroutines из LRQ другого P. Уменьшает contention.

3. Как работает netpoller?
Блокирующие network I/O вызовы регистрируются в epoll/kqueue. M освобождается, goroutine паркуется. Когда I/O готов — goroutine кладётся в runqueue.

4. Чем отличается syscall от netpoll?
Network I/O идёт через netpoller (M свободен). Обычный blocking syscall — M залипает; P подбирается другим M.

5. Что такое preemption в Go? Как изменилось в 1.14?
До 1.14 — кооперативная (только на функциональных точках). С 1.14 — асинхронная через SIGURG, прерывает в любой safe point.

6. Сколько стека у новой goroutine?
2KB. Растёт удвоением, при overflow runtime копирует фреймы в больший стек.

7. Что происходит с указателями при stack copy?
Runtime обновляет все указатели на стек. Указатели НА стек, хранимые в heap, не безопасны — но они должны быть на heap escape’нуты.

8. Опишите memory allocator (mcache → mcentral → mheap).
Per-P mcache без лока → mcentral (с локом, на size class) → mheap (с локом, у OS просит арены).

9. Что такое size class? Сколько их?
~67-70 классов до 32KB. Объект округляется до ближайшего класса. >32KB — large object, напрямую mheap.

10. Tiny allocator — для чего?
Объекты ≤16B группируются в один блок 16B. Снижает overhead на крошечных аллокациях.

11. Как работает Go GC?
Concurrent mark-sweep, tri-color, write barrier. STW только на mark start/end (~10µs каждый).

12. Что такое write barrier?
Когда в маркинг-фазе goroutine пишет указатель в объект, runtime интерсептит запись, чтобы не “потерять” живой объект.

13. Что такое pacer? Что он делает?
Pacer регулирует, когда стартовать следующий GC цикл. Цель — закончить mark раньше, чем heap дойдёт до target_size.

14. GOGC — что значит 100?
Target heap = live_heap × 2 (т.е. удвоение). GOGC=50 → target = 1.5× live, чаще GC.

15. GOMEMLIMIT — для чего?
Soft limit на всю память Go runtime. Когда близко — pacer триггерит GC агрессивнее. Полезно в контейнерах.

16. Как настроить Go под контейнер с лимитами?
GOMAXPROCS=cpu_limit (с 1.25 — авто; или uber-go/automaxprocs). GOMEMLIMIT=80-90% от memory limit. GOGC=100 или 50.

17. Open-coded defers — что это?
С Go 1.14 — компилятор инлайнит defer-код прямо в функцию, если ≤8 defer’ов и не в цикле. ~6ns vs 35ns старого.

18. panic и recover — как работают?
panic раскручивает стек, выполняя defer’ы. Если в defer recover() — panic снимается, выполнение продолжается. Иначе процесс падает.

19. Что произойдёт, если panic в одной goroutine не обработан?
Падает весь процесс.

20. Escape analysis — что это?
Компилятор определяет, можно ли переменную на стек, или она “сбегает” в heap. Возврат указателя, interface boxing → heap.

21. В чём разница между make(chan int) и make(chan int, 10)?
Unbuffered блокирует sender пока reader не возьмёт. Buffered ёмкостью 10 — sender блокируется когда буфер полный.

22. Что вернёт чтение из закрытого канала?
Zero-value немедленно, ok=false.

23. Что произойдёт при send в закрытый канал?
Panic: “send on closed channel”.

24. Что произойдёт при close уже закрытого канала?
Panic: “close of closed channel”.

25. Кто должен закрывать канал?
Sender. Если несколько senders → нужен координатор / sync.Once.

26. select с пустым case — что значит default?
default выполняется немедленно, если ни один case не готов. Делает select non-blocking.

27. Расскажите про happens-before гарантии каналов.
N-я отправка happens-before N-го приёма. Закрытие — before приёма zero-value.

28. sync.Mutex vs sync.RWMutex — когда что?
RWMutex — много чтений, мало записей. Иначе Mutex (RWMutex дороже из-за двух счётчиков и contention в write path).

29. Как обнаружить deadlock в Go?
runtime panic “fatal error: all goroutines are asleep - deadlock!” если все горутины спят. Для частичных — pprof goroutine, ищем “chan receive” / “sync.Mutex.Lock” застрявшие.

30. Что такое goroutine leak? Как найти?
Goroutine, которая никогда не завершится (заблокирована на чтении канала, который никто не закроет). Найти: pprof /debug/pprof/goroutine, считать растущее количество.

31. Как написать worker pool на N горутинах?
Создать buffered channel jobs, запустить N goroutines, каждая делает for j := range jobs { process(j) }. close(jobs) для shutdown. WaitGroup.

32. errgroup vs sync.WaitGroup?
errgroup отменяет ctx при первой ошибке, собирает первую ошибку. WaitGroup — просто ждёт всех.

33. singleflight — для чего?
Дедупликация одинаковых параллельных запросов. Один реально выполняется, остальные ждут результата.

34. sync.Pool — race с GC?
Каждый GC цикл частично опустошает Pool (с Go 1.13 — двухуровневый, не полностью).

35. Какие гарантии у sync.Once?
Doсалось гарантированно один раз. Все вызывающие видят результат после возврата (happens-before).

36. Что такое context.Background() и context.TODO()?
Background — корень, никогда не cancel. TODO — placeholder, “пока не знаю, какой контекст нужен”. Семантически идентичны.

37. Как правильно использовать context.WithCancel?
defer cancel() ВСЕГДА. Иначе leak горутин/timer’ов.

38. Что произойдёт, если context.Cancel вызвать дважды?
Ничего — идемпотентен.

39. Зачем нужен sync/atomic, если есть Mutex?
Lock-free, быстрее на низкой contention. Для простых счётчиков, флагов, указателей.

40. Что такое sequential consistency?
Все атомарные операции в программе кажутся выполненными в одном глобальном порядке. С Go 1.19 атомики SC.

41. Race condition vs data race?
Data race — concurrent access к памяти без синхронизации. Race condition — логически неправильный результат из-за порядка операций. Race detector ловит data races; race conditions может только тесты.

42. go test -race — как работает?
Инструментирует доступы к памяти, отслеживает happens-before граф. Overhead ~5-10x CPU, 5-10x memory. No false positives.

43. Когда использовать sync.Map?
Read-heavy (90%+ read), либо disjoint key sets. Иначе RWMutex+map.

44. Что такое spinning в sync.Mutex?
Перед паркингом goroutine крутится несколько раз (active wait). Помогает в коротких lock-ах.

45. Объясните паттерн fan-in / fan-out.
Fan-out: один input → N workers. Fan-in: N channels → один result channel (через select).

46. MVCC в PostgreSQL — что это?
Каждая строка имеет xmin/xmax. Транзакция видит снапшот по своему transaction ID. UPDATE = новая строка + dead tuple.

47. Чем UPDATE отличается от DELETE+INSERT в Postgres?
Логически почти то же — старая строка помечается dead, новая создаётся. UPDATE сохраняет primary key.

48. Что делает VACUUM?
Чистит dead tuples, обновляет статистику. AUTOVACUUM делает это автоматически.

49. VACUUM FULL vs VACUUM?
FULL — переписывает таблицу целиком, EXCLUSIVE LOCK, возвращает место OS. Регулярный VACUUM — concurrent, освобождает место для переиспользования.

50. Что такое index bloat?
Индекс растёт из-за dead tuples, не сжимается автоматически. Решение: REINDEX CONCURRENTLY.

51. Когда B-tree индекс не помогает?
LIKE ‘value%’ — помогает; LIKE ‘%value’ — нет. Не помогает на функциях колонок (если не functional index).

52. Что такое covering index?
INCLUDE дополнительные колонки в листья B-tree → index-only scan без обращения к heap.

53. Объясните EXPLAIN ANALYZE.
Реально выполняет query, показывает план и реальные времена. Cost — оценка планировщика. Rows estimate vs actual — если расходятся → ANALYZE.

54. Что такое query plan? Seq Scan vs Index Scan?
План — как Postgres планирует исполнить. Seq Scan — full table scan. Index Scan — через индекс. Index Only Scan — без обращения к heap. Bitmap Index Scan — для большого набора rows.

55. Connection pooling — почему важен?
Установка соединения в Postgres дорогая (auth + backend fork). Pool переиспользует.

56. PgBouncer modes?
Session (1 backend = 1 client connection), Transaction (1 backend = 1 transaction; нельзя prepared statements), Statement (1 backend = 1 statement).

57. CAP теорема?
Consistency, Availability, Partition tolerance. При partition — выбираем C или A. PACELC уточняет: Else — L (latency) или C.

58. Что такое eventually consistent? Read-your-writes?
Eventually — со временем все реплики согласованы. Read-your-writes — пользователь видит свою запись сразу (через sticky session или read from master).

59. Saga vs 2PC?
2PC блокирующий, single point of failure (coordinator). Saga — sequence of local transactions + compensations, eventually consistent, без локов.

60. Outbox pattern — зачем?
Атомарно записать в БД + опубликовать в Kafka нельзя напрямую. Outbox: пишем в outbox таблицу в той же транзакции, relay читает и публикует.

61. Что такое idempotency key?
Уникальный ID запроса, сервер сохраняет результат → повтор с тем же ключом возвращает тот же результат, не выполняет ещё раз.

62. Как реализовать distributed lock на Redis?
Redlock: SETNX с TTL на N (5) Redis-нод, нужен majority. Используем go-redsync. Caveat: clock drift; для критичного — etcd.

63. Чем отличаются Kafka partition / consumer group?
Partition — упорядоченный лог. Consumer group — N consumers разделяют partitions (1 partition → 1 consumer внутри group).

64. Exactly-once в Kafka — возможно?
Внутри Kafka — да (transactional API). При записи в external — практически нет; используют idempotent upserts.

65. Когда Kafka, когда RabbitMQ, когда NATS?
Kafka — event log, аналитика. RabbitMQ — per-message ACK, complex routing. NATS — low-latency RPC pub/sub.

66. Микросервисы vs монолит — когда что?
Start with монолит, разделять когда: разные команды, разные scaling needs, разные SLA. Не для каждого CRUD.

67. Что такое DDD aggregate?
Кластер entities + value objects под единой transactional границей. Inv’ы валидируются внутри.

68. Bounded Context — пример?
”Customer” в Sales BC (имя, email, история покупок) ≠ “Customer” в Billing BC (имя, счёт, способ оплаты). Разные модели одного концепта.

69. Hexagonal architecture — зачем?
Изолировать бизнес-логику от инфраструктуры. Замена БД / транспорта → меняем только адаптер.

70. Что такое CQRS?
Разделение моделей чтения и записи. Write — нормализованная, для consistency. Read — денормализованная, для performance.

71. Когда CQRS оправдан?
Когда read запросы сильно отличаются от write — отчёты, аналитика, сложные join’ы для UI.

72. Event Sourcing — pros/cons?
Pros: audit, replay, time travel. Cons: сложная schema evolution, debug сложнее.

73. Circuit breaker — состояния?
Closed (норма) → Open (порог ошибок превышен, fail-fast) → Half-Open (пробуем 1 запрос).

74. Bulkhead?
Изоляция ресурсов (thread pools, connection pools) per-downstream — если один deps медленный, не съест всё.

75. Retry с какой стратегией?
Exponential backoff с jitter. Только для idempotent operations. С circuit breaker.

76. Спроектируйте URL shortener (Bit.ly).
См. раздел 14. Counter→base62 vs hash, KV storage + PG, агрессивный кэш, async аналитика.

77. Спроектируйте чат на 100M юзеров.
WebSocket conn manager (sharded), Cassandra для messages, Redis для presence, push для offline, federation между регионами.

78. Лента (как Twitter)?
Fan-out on write для regular, fan-out on read для celeb. Redis для feed cache. Async pipeline для генерации.

79. Rate limiter для API на 1M RPS?
Token bucket per user, in-memory + Redis sync. Sticky sessions к одному инстансу для consistency. Sliding window для точности.

80. Как масштабировать БД на 100K writes/sec?
Sharding по user_id, async repl на read replicas, write batching, queue absorbtion (Kafka), CDC pipeline для аналитики.

81. Какие метрики собирать (RED method)?
Rate (RPS), Errors (rate), Duration (histogram). По каждому endpoint/operation.

82. Что такое SLO error budget?
100% - SLO. Если 99.9% → 0.1% (~43 мин/мес). Сжигаем фичами, восстанавливаем reliability.

83. p50 vs p99 — почему p99 важен?
Average и медиана скрывают tail latency. p99 показывает worst 1% — это user-facing pain.

84. Distributed tracing — как работает?
Каждый запрос → trace_id, каждая операция → span. Контекст пропагируется через HTTP headers (traceparent). Бэкенд (Jaeger/Tempo) строит дерево.

85. Как тестировать concurrent код?
go test -race + stress (count=1000). go.uber.org/goleak для goroutine leaks. testing/synctest (Go 1.24) для детерминизма.

86. Что такое property-based testing?
Генерим рандомные входы, проверяем инвариант. Library: rapid, gopter.

87. testcontainers — для чего?
Поднимать реальные сервисы (PG, Redis, Kafka) в Docker во время integration tests.

88. Pact / contract testing?
Consumer описывает ожидания, генерит pact-файл. Provider читает pact, проверяет что реально так отвечает.

89. k6 vs Vegeta?
k6 — JavaScript scenarios, мощнее. Vegeta — pure Go, простой constant RPS. Vegeta точнее для precision-rate tests.

90. Что такое chaos engineering?
Намеренно ломаем системы (kill pods, network delay) в проде, чтобы команды строили резистентность. Tools: Chaos Mesh, Toxiproxy.

91. Что выведет?

for i := 0; i < 3; i++ {
go func() { fmt.Println(i) }()
}
time.Sleep(time.Second)

В Go ≤1.21 — обычно “3 3 3” (захват по ссылке). В Go ≥1.22 — “0 1 2” (loop variable scoped per iteration).

92. nil interface vs nil concrete pointer?

var p *MyError = nil
var err error = p
err != nil // TRUE! Interface не nil, concrete value nil.

93. Что выведет?

m := map[string]int{}
m["k"]++ // OK?

OK — возвращает zero value 0, инкрементит до 1.

94. range по map — порядок?
Намеренно randomized. Не полагаться на порядок.

95. Когда defer вычисляется vs выполняется?
Аргументы вычисляются при defer. Тело — при возврате.

i := 10
defer fmt.Println(i) // выведет 10
i = 20

96. Чем отличается make от new?
*new(T) — zero value, возвращает T. make — только для slice/map/chan, инициализирует внутреннюю структуру.

97. Slice — append может вернуть тот же slice?
Если cap позволяет — да. Если переполнен → новый underlying array, старый slice указывает на старое.

98. Что такое slice header?
{ptr to array, len, cap}. Передача slice копирует header (но не data).

99. Зачем нужны errors.Is и errors.As?
errors.Is — проверка identity (включая wrapped). errors.As — type assertion через цепочку wrap.

100. Что такое fmt.Errorf("...: %w", err)?
Wrapping (Go 1.13+). Внутренний err доступен через errors.Unwrap, errors.Is, errors.As.

101. Когда использовать pointer receiver vs value receiver?
Pointer — если метод модифицирует структуру, большая структура, или у других методов pointer (для consistency). Value — небольшие immutable VO.

102. Что такое embedding (vs наследование)?
Структура содержит другую без имени поля. Методы внутренней доступны “промоутед”. Это композиция, не наследование.

103. init() — когда вызывается? Порядок?
При import пакета, до main. Один пакет — несколько init в разных файлах, порядок по алфавиту имени файла. Цепочка: импорты → init импортов → init текущего.

104. Generic constraints (Go 1.18+)?

type Number interface { ~int | ~float64 }
func Sum[T Number](xs []T) T { ... }

~ — также type aliases (“underlying type”). | — union.

105. any vs interface{}?
Идентичны. any — alias с Go 1.18.


Stack:

  • Go (HTTP server: chi or echo)
  • PostgreSQL (URL mappings, шардинг по hash)
  • Redis (hot cache for shortcode → URL)
  • Kafka (clicks → analytics)
  • ClickHouse (analytics queries)
  • gRPC между сервисами

Что демонстрирует: distributed system, sharding, кэширование, event streaming, OLAP vs OLTP.

Бонус-фичи: custom URLs, expiration TTLs, QR-code generator, rate limiting per API key.

Stack:

  • Go (WebSocket: gorilla/websocket, coder/websocket)
  • Redis (pub/sub + presence)
  • PostgreSQL (chat history)
  • Kafka (события для downstream)
  • OpenTelemetry traces

Что демонстрирует: WebSocket с тысячами одновременных соединений, sharding connection manager, presence, message ordering, offline delivery.

Stack:

  • Go monorepo (3-4 микросервиса: Order, Payment, Inventory, Notification)
  • PostgreSQL per service (no shared DB)
  • Kafka для events + Outbox pattern
  • Saga orchestrator (или choreography)
  • Идемпотентные API через Idempotency-Key

Что демонстрирует: DDD aggregates, Saga, Outbox, eventual consistency, distributed transactions, observability с tracing.

Stack:

  • Go (без сторонних поиск-движков)
  • Свой inverted index (BoltDB / BadgerDB для persistent)
  • TF-IDF / BM25 ranking
  • HTTP API
  • gRPC для cluster nodes
  • Replication через Raft (hashicorp/raft)

Что демонстрирует: алгоритмы (inverted index, ranking), distributed consensus (Raft), архитектура хранения.

Stack:

  • Go core scheduler
  • PostgreSQL для job store (skip-locked для distributed lock)
  • Redis для leadership election (distributed lock) или etcd
  • HTTP/gRPC API для job submission
  • Web UI (можно простой htmx)
  • Prometheus metrics

Что демонстрирует: distributed leadership, retries with backoff, dead-letter, idempotency, observability.


Обязательное:

  • Designing Data-Intensive Applications (Martin Kleppmann) — 2nd edition (2025). База для distributed systems.
  • The Go Programming Language (Donovan, Kernighan) — стандарт Go.
  • 100 Go Mistakes and How to Avoid Them (Teiva Harsanyi) — для middle++ обязательно.
  • Concurrency in Go (Katherine Cox-Buday) — patterns и подходы.
  • Cloud Native Go (Matthew A. Titmus) — облачные приложения, k8s.

Дополнительно:

  • System Design Interview Vol 1+2 (Alex Xu).
  • Domain-Driven Design with Golang (Matthew Boyle).
  • Distributed Services with Go (Travis Jeffery).
  • Let’s Go / Let’s Go Further (Alex Edwards) — практический web-dev на Go.
  • Practical Go (Dave Cheney).
  • Database Internals (Alex Petrov).
  • Ardan Labs Ultimate Go (Bill Kennedy) — глубокий runtime/internals.
  • JetBrains Academy — Go разделы.
  • Уроки Дмитрия Шевелева на YouTube (RU).
  • Yandex Practicum — Go разработчик (RU).

EN:

RU:

  • tproger Go.
  • Habr Go-хаб.
  • YouTube: каналы “Артём Кашканов”, “GoToBrno” (русскоязычные митапы), Yandex Cloud Meetups.
  • Go Time (Changelog) — еженедельный, must.
  • Cup o’ Go (Shay Nehmad, Jonathan Hall).
  • Backend & Беседы (RU).
  • GopherCon (US, EU, Singapore, India, Africa).
  • GoLab (Italy).
  • Go West (UK).
  • GopherCon Brazil.
  • RU: GolangConf (Москва, осень).

Реалистичный график. Если уже что-то знаешь — пропусти соответствующие недели.

Неделя 1-2:

  • Прочитать раздел про scheduler (Ardan Labs blog series).
  • Изучить GMP, work stealing, netpoller.
  • Поэкспериментировать с GODEBUG=schedtrace=1000 на своём проекте.

Неделя 3-4:

  • Memory allocator (mcache/mcentral/mheap).
  • GC pacer, write barriers.
  • Прочитать Go GC Guide от корки до корки.

Неделя 5-6:

  • Concurrency продвинуто: sync.Pool, sync.Map, errgroup, semaphore, singleflight.
  • Race detector, goleak, testing/synctest.
  • Прочитать Go Memory Model.

Неделя 7-8:

  • Написать простой in-memory кэш с TTL, поддержкой LRU (для тренировки concurrency).
  • Прогнать race + бенчмарки.

Неделя 9-10:

  • pprof endpoints, flame graphs.
  • benchstat, escape analysis.
  • Развернуть Pyroscope локально, попрофилировать свой проект.

Неделя 11-12:

  • PostgreSQL deep: MVCC, EXPLAIN ANALYZE, индексы (B-tree, GIN, partial, covering).
  • Vacuum, autovacuum tuning.
  • Шардирование, partitioning.

Неделя 13-14:

  • Distributed transactions: Outbox, Saga (orchestration + choreography).
  • Идемпотентность, Idempotency-Key pattern.
  • Распределённые блокировки (Redlock, etcd).

Неделя 15-16:

  • Pet-проект 1: URL shortener с шардингом и кэшем (см. раздел 16).

Неделя 17-18:

  • gRPC глубоко: интерсепторы, streaming, deadlines, retries.
  • Protobuf продвинуто: well-known types, custom options.
  • Connect-Go.

Неделя 19-20:

  • OpenTelemetry полностью: traces, metrics, logs (slog).
  • Prometheus + Grafana, развернуть локально.
  • SLO/SLI/SLA — настроить для своего проекта (Sloth).

Неделя 21-22:

  • Kafka deep: partitions, consumer groups, exactly-once, Cooperative Sticky.
  • NATS JetStream базово.
  • Outbox + Debezium CDC локально.

Неделя 23-24:

  • Pet-проект 2: чат на WebSocket с presence (см. раздел 16).

Неделя 25-26:

  • DDD: agregates, bounded context, ubiquitous language.
  • Hexagonal architecture в Go (прочитать, переписать pet-проект).
  • CQRS, Event Sourcing основы.

Неделя 27-28:

  • System Design Interview (Alex Xu) Vol 1 — пройти 5 задач.
  • Решить: URL shortener, лента, чат, такси, search.

Неделя 29-30:

  • DDIA Ch. 1-9 (replication, partitioning, transactions, distributed consensus).

Неделя 31-32:

  • Pet-проект 3: e-commerce с Saga + Outbox (см. раздел 16).

Неделя 33-34:

  • JWT, OAuth 2.0, PKCE.
  • Casbin (RBAC, ABAC).
  • bcrypt, argon2.

Неделя 35-36:

  • Kubernetes: Pod, Deployment, Service, ConfigMap, Secret.
  • Helm charts.
  • HPA с custom metrics через Prometheus.
  • Развернуть свой pet-проект в k8s (kind/minikube).

Неделя 37-38:

  • CI/CD: GitHub Actions для Go.
  • golangci-lint, gosec, govulncheck, trivy.
  • goreleaser.

Неделя 39-40:

  • Резерв / закрытие пробелов.

Неделя 41-44:

  • Решать LeetCode Medium (Go): минимум 100 задач.
  • Concentrate on: graphs, DP, two pointers, sliding window, hash maps, heaps.
  • LeetCode Top Interview 150.

Неделя 45-46:

  • Прорешать список из раздела 15 (100+ вопросов), записать свои ответы.
  • Mock interview: с другом/коллегой/Pramp/Interviewing.io.

Неделя 47-48:

  • 5 system design задач за неделю — по 1 час каждая, рисуя на доске/Excalidraw.
  • Отрепетировать на камеру.

Финал: податься в Yandex G17, Avito Senior, Ozon Middle 2, Tinkoff SDE III, VK Senior.


  • Знаю GMP scheduler, work stealing, netpoller изнутри
  • Знаю memory allocator (mcache/mcentral/mheap) и GC pacer
  • Могу объяснить open-coded defers, panic/recover
  • Умею профилировать с pprof + Pyroscope, читать flame graphs
  • Знаю escape analysis, могу писать zero-alloc код
  • Tuning GOGC, GOMEMLIMIT, GOMAXPROCS в k8s (или automaxprocs)
  • PostgreSQL MVCC, EXPLAIN ANALYZE, индексы (включая partial/covering)
  • Понимаю CAP, eventual consistency, idempotency
  • Реализовал Saga + Outbox в своём pet-проекте
  • gRPC: streaming, interceptors, deadlines, retries
  • OpenTelemetry: traces, metrics, logs (slog)
  • SLO/SLI с error budget
  • Kafka: partitions, consumer groups, exactly-once
  • DDD: aggregates, bounded context
  • Hexagonal architecture в Go
  • JWT, OAuth 2.0 / PKCE, Casbin RBAC/ABAC
  • Kubernetes: probes, graceful shutdown, HPA
  • CI: golangci-lint, gosec, govulncheck, trivy
  • Решил 100+ LeetCode Medium на Go
  • 5+ system design задач (URL, чат, лента, такси, search)
  • 3+ pet-проекта в портфолио, выложены на GitHub

Главные использованные источники (всё 2025-2026):