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

Go Roadmap: Middle 3 / Senior-grade (2025-2026)

Подробный roadmap для подготовки к собеседованию на позицию Middle 3 / Middle+ / Senior Junior Go-разработчика в крупных компаниях РФ и СНГ (Яндекс, Авито, Озон, ВК, Тинькофф, Сбер).

Источники: Habr, Хабр Карьера, vc.ru, Reddit r/golang, dev.to, Medium, официальные блоги (Go Blog, Datadog, Uber, Cloudflare), статьи 2024-2026 гг.

Дата составления: май 2026 (актуально для Go 1.24–1.26).


В 2025-2026 годах Go де-факто стал основным языком для backend в high-load системах в РФ. Яндекс, Авито, Озон, Тинькофф, ВК, Сбер массово используют Go для платформенных сервисов. На уровне Middle 3 от кандидата ждут:

  1. Уверенное владение внутренностями языка и runtime (scheduler, GC, memory model, escape analysis).
  2. Production-опыт с k8s, distributed systems, очередями (Kafka/NATS), Postgres.
  3. Архитектурное мышление — уметь обосновать trade-off, нарисовать систему на 1-10 млн RPS.
  4. Системный дизайн — спокойно проектировать URL shortener, чат, ленту, очередь.
  5. Production debugging — pprof, flamegraph, troubleshooting инцидентов.
  6. Soft skills и менторство — code review, ADR, RFC, рост джунов.

Зарплатный коридор (Москва, 2026, на руки):

  • Middle 2: 250-350k₽
  • Middle 3 / Middle+: 350-500k₽
  • Senior: 500-750k₽ (Озон Банк, Яндекс — выше всех)

Этапы компиляции Go: Lexing → Parsing → Type checking → SSA (Static Single Assignment) → Machine code. На уровне Middle 3 надо понимать что компилятор:

  • Делает escape analysis во время type checking — определяет, может ли переменная жить на стеке.
  • Генерирует SSA-форму — промежуточное представление, где каждая переменная присваивается ровно один раз. SSA позволяет оптимизации: inlining, dead code elimination, common subexpression elimination, bounds check elimination.
  • Применяет PGO (Profile-Guided Optimization) с Go 1.21+ — оптимизация по реальным production-профилям.
Окно терминала
# Посмотреть SSA для функции
GOSSAFUNC=myFunc go build .
# Создаст ssa.html с подробной информацией
# Посмотреть escape analysis
go build -gcflags="-m -m" .
# Посмотреть инлайн-решения
go build -gcflags="-m=2" .

Escape Analysis — это граф-анализ: компилятор строит граф ссылок между переменными, помечает «корни» (возвращённые указатели, глобалы, горутины) и пропагирует марки. Если переменная не доходит до корня — она не escape, остаётся на стеке.

Типичные причины escape:

  • Возврат указателя на локальную переменную
  • Сохранение указателя в глобал
  • Передача в interface{} (часто), в горутину
  • Слишком большой объект (>64KB) — сразу на heap
  • fmt.Println(x)x уходит в interface{} → heap
// Stack:
func sum() int {
arr := [10]int{1, 2, 3}
return arr[0]
}
// Heap (потому что &arr возвращается):
func leak() *int {
arr := 42
return &arr // escape to heap
}

Стек в Go дешевле кучи в ~5x (нет GC pressure, нет атомиков). Грамотный escape analysis может дать 2-10x ускорение и снизить GC overhead на 50-90%.

Источник: Mastering Escape Analysis in Go, Go Optimization Guide — Stack Allocations

Go GC — concurrent tri-color mark-and-sweep на основе Dijkstra. Pacer — пропорциональный регулятор, решающий, когда запустить новый GC цикл. Цель: держать GC проportional к скорости аллокаций.

Формула (упрощённо):

heap_target = heap_marked * (1 + GOGC/100)
  • GOGC=100 (default) — GC стартует, когда heap вырос вдвое относительно живых данных
  • GOGC=200 — GC реже, меньше CPU overhead, больше памяти
  • GOGC=off — GC только при достижении GOMEMLIMIT

Mark Assist: если приложение аллоцирует быстрее, чем GC размечает, runtime «штрафует» горутины — заставляет их помогать GC пропорционально объёму аллокаций.

Источник: GC Pacer Redesign Proposal, Ardan Labs: GC Pacing

  • G (goroutine) — задача, легковесный стек начиная с 2-8 KB (растёт по необходимости).
  • M (machine) — OS thread.
  • P (processor) — логический процессор, владеет local run queue, timers, mcache. Количество = GOMAXPROCS.

Каждая M может выполнять G только если держит P. Локальная очередь P имеет 256 слотов. Work stealing: пустые P крадут половину задач у занятых.

Preemption (с Go 1.14+): asynchronous preemption на основе сигналов (SIGURG на Unix), горутина может быть прервана даже без явных function calls. sysmon (системный monitor-поток) каждые 10ms помечает долгие goroutine как preemptible.

На Linux Go runtime использует futex (fast userspace mutex) для парковки M-потоков. Когда M простаивает — он засыпает в futex, ядро его потом разбудит.

Netpoller: когда горутина делает сетевую операцию (read/write на socket), который не готов:

  1. Runtime регистрирует fd в epoll (Linux) / kqueue (macOS) / IOCP (Windows).
  2. Горутина паркуется через gopark, M освобождается под другие G.
  3. Когда kernel сигнализирует о готовности fd, netpoller возвращает горутину в run queue.

Состояние pdWait — защита от race condition: горутина «в процессе засыпания, ещё не полностью паркована».

Источник: Go Netpoller and Runtime Behavior, Go Scheduler 2025 Deep Dive

Когда писать руками:

  • Тяжёлые криптографические/хеш-операции (SHA-256, AES, CRC32)
  • SIMD (см. ниже)
  • Атомики на нестандартных архитектурах
  • Низкоуровневые системные вызовы
add_amd64.s
// func addASM(a, b int) int
TEXT ·addASM(SB), NOSPLIT, $0-24
MOVQ a+0(FP), AX
MOVQ b+8(FP), BX
ADDQ BX, AX
MOVQ AX, ret+16(FP)
RET

Intrinsics — компилятор автоматически переводит некоторые функции в нативные инструкции CPU: math/bits.LeadingZeros64()LZCNT, math/bits.OnesCount64()POPCNT, sync/atomic.AddInt64()LOCK XADD.

Текущее состояние: SIMD доступен через hand-written assembly (например, segmentio/asm, stuartcarnie/go-simd).

Go 1.26+ изменения: ввели GOEXPERIMENT=simd — пакет simd для AMD64 с архитектурно-специфичными intrinsics. Avoids assembly boundary overhead.

// Hypothetical Go 1.26+ SIMD API
import "simd"
func sumFloats(data []float32) float32 {
var v simd.Float32x8
for i := 0; i+8 <= len(data); i += 8 {
v = v.Add(simd.LoadFloat32x8(data[i:]))
}
return v.HorizontalSum()
}

Источник: SIMD in Go, Go Proposal #73787 SIMD Intrinsics

//go:linkname — директива компилятора, позволяющая «склеить» имя из вашего пакета с private-функцией стандартной библиотеки. Используют для:

  • Доступа к runtime.nanotime() (быстрее time.Now())
  • Доступа к runtime.fastrand()
  • В библиотеках (xxhash, gops)
//go:linkname nanotime runtime.nanotime
func nanotime() int64

⚠️ Опасно: Go team может убрать private-функцию в любой версии. С Go 1.23+ ввели ограничения на linkname для нестандартных пакетов.

Доступно с Go 1.21, production-ready с 1.24. Дает 2-14% улучшение CPU usage без изменения кода.

Workflow:

  1. Соберите CPU profile с production: curl localhost:6060/debug/pprof/profile?seconds=30 > default.pgo
  2. Положите default.pgo рядом с main.go.
  3. go build . — Go автоматически использует профиль для оптимизации (агрессивный inlining, deduplication, layout).

Реальные кейсы:

  • Uber: применили PGO на десятки сервисов, до 7% CPU saving.
  • Cloudflare: интегрировали PGO в pipeline сборки.
  • Google: 5-7% улучшение на internal workloads.

Источник: PGO docs, Uber Boosted Performance with PGO (InfoQ 2025), Datadog Save 14% CPU with PGO

Memory Ballast — старый трюк (популяризован Twitch ~2019): при старте аллоцировать большой byte slice (например, 1 GiB), чтобы heap target GC был высоким и GC реже срабатывал.

// Старая практика (НЕ ИСПОЛЬЗОВАТЬ):
ballast := make([]byte, 10<<30) // 10 GiB
_ = ballast

❌ Проблемы:

  • Не portable между ОС (Linux vs macOS overcommit разный).
  • Тяжело подобрать правильный размер.
  • Зависит от внутренностей GC, которые могут поменяться.

✅ С Go 1.19+ используют GOMEMLIMIT:

GOMEMLIMIT — мягкий лимит для runtime, говорит GC «не давай хипу превысить N байт».

Правильная настройка в k8s:

env:
- name: GOMEMLIMIT
valueFrom:
resourceFieldRef:
resource: limits.memory
# ИЛИ через soft limit (90% от k8s limit):
- name: GOMEMLIMIT
value: "900MiB" # если k8s limit = 1Gi
resources:
limits:
memory: "1Gi"

Best practices 2025:

  • Установите GOMEMLIMIT в ~90% от k8s memory limit (10% запас на goroutine stacks, cgo, off-heap).
  • GOGC=off + GOMEMLIMIT=900MiB — GC только при достижении лимита (для memory-bound сервисов).
  • Для high-throughput: GOGC=200, GOMEMLIMIT=... — реже GC, больше throughput.
  • Сочетайте с GOMAXPROCS (Uber automaxprocs читает cgroups).

Источник: Ardan Labs: Kubernetes Memory Limits and Go, Weaviate GOMEMLIMIT Game Changer

import (
"runtime/debug"
"runtime/metrics"
)
// Set memory limit programmatically
debug.SetMemoryLimit(900 * 1024 * 1024)
debug.SetGCPercent(200)
// FreeOSMemory — попросить runtime вернуть память ОС (после большого batch-job)
debug.FreeOSMemory()
// Стектрейс для диагностики паник
debug.PrintStack()
// runtime/metrics — структурированный доступ к метрикам runtime
samples := []metrics.Sample{
{Name: "/sched/latencies:seconds"},
{Name: "/gc/cycles/total:gc-cycles"},
{Name: "/memory/classes/heap/objects:bytes"},
}
metrics.Read(samples)

С Go 1.25+ добавили FlightRecorder — облегчённая альтернатива полному tracing для production.

type Map struct {
mu Mutex
read atomic.Value // содержит readOnly{m: map[any]*entry, amended: bool}
dirty map[any]*entry
misses int
}

Принцип: два внутренних map.

  • read — lock-free (читается через atomic), но не содержит новых ключей.
  • dirty — защищён мьютексом, содержит свежие записи.

Жизненный цикл:

  1. Запись нового ключа → идёт в dirty (с lock).
  2. Чтение существующего ключа → lock-free из read.
  3. Чтение ключа, которого нет в read, но есть в dirtymisses++.
  4. Когда misses >= len(dirty)promotion: dirty становится новым read, старый dirty = nil.

Когда использовать:

  • ❌ Не подходит для произвольных нагрузок (map+RWMutex часто быстрее).
  • ✅ Хорош при read-heavy + редкие записи (например, кэш конфигурации).
  • ✅ Когда ключи практически не пересекаются между горутинами (disjoint keys).
var cache sync.Map
// Store
cache.Store("key", "value")
// Load
v, ok := cache.Load("key")
// CompareAndSwap (Go 1.20+)
cache.CompareAndSwap("key", "old", "new")
// LoadAndDelete — atomic
v, ok := cache.LoadAndDelete("key")

Источник: Inside sync.Map, VictoriaMetrics: sync.Map Right Tool

type SliceHeader struct {
Data uintptr
Len int
Cap int
}

Правила роста (с Go 1.18+, в Go 1.20 уточнились):

  • Если cap < 256: новый cap = 2 * old cap (двойной рост).
  • Если cap >= 256: новый cap = old cap + (old cap + 3*256) / 4 (примерно +25%).

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

  • append может (а может и нет) выделить новый underlying array → мутации через старый slice могут «потеряться».
  • Sub-slice держит ссылку на весь underlying array → memory leak.
  • При s = s[1000:] GC не освободит начало массива.
// Memory leak:
big := make([]byte, 10<<20) // 10 MiB
small := big[0:3]
// small держит ссылку на весь big
runtime.GC()
// big не освобождён
// Fix:
small := make([]byte, 3)
copy(small, big[0:3]) // независимая копия

Источник: Tricky Golang interview: Slice Header

Pre-Go 1.24 (старая bucket-based реализация):

  • Map = array из buckets, каждый держит 8 key-value.
  • Когда bucket переполнен — создаётся overflow bucket (linked list).
  • При load factor > 6.5 запускается evacuation: создаётся buckets array вдвое больше, элементы инкрементально перемещаются.

Go 1.24+: Swiss Tables:

  • Принципиально новая реализация: extendible hashing + table splitting.
  • 8-slot groups, SIMD-friendly metadata.
  • Когда таблица достигает 128 групп → splits на две таблицы (~1024 entries копируется).
  • Нет overflow buckets, нет долгих evacuation pauses.

Реальный кейс (Datadog, 2025): миграция на Swiss Tables сэкономила сотни ГБ памяти и снизила P99 latency для больших maps.

// Хорошие практики:
m := make(map[string]User, 1000) // pre-allocate если знаете размер
// vs:
m := make(map[string]User) // будут многократные resize
// Опасно (для read-heavy):
m := make(map[K]V)
go func() { m["x"] = v }() // ⚠️ data race!
// Решение: sync.Map ИЛИ map+RWMutex

Источник: How Go 1.24’s Swiss Tables saved Datadog GBs, Go Blog: Faster maps with Swiss Tables


type hchan struct {
qcount uint // элементов в очереди
dataqsiz uint // размер кольцевого буфера
buf unsafe.Pointer // массив
elemsize uint16
closed uint32
elemtype *_type
sendx uint // позиция записи
recvx uint // позиция чтения
recvq waitq // очередь ожидающих читателей (sudog)
sendq waitq // очередь ожидающих писателей (sudog)
lock mutex
}

sudog (“suspended goroutine”) — структура для парковки горутины, ожидающей на канале:

type sudog struct {
g *g
next *sudog
prev *sudog
elem unsafe.Pointer // куда положить/откуда взять данные
c *hchan
// ...
}
  1. Lock канала.
  2. Если есть ждущий receiver в recvq → напрямую копируем элемент в его sudog.elem, разблокируем G, unlock, return.
  3. Если в buffered канале есть место → копируем в buf[sendx], sendx++, unlock, return.
  4. Иначе создаём sudog, кладём в sendq, паркуем горутину (gopark).
  5. Когда receiver разбудит — продолжаем выполнение.
  • Маркирует closed=1.
  • Будит ВСЕ горутины из recvq (они получат zero value, ok=false).
  • Будит ВСЕ горутины из sendq (они получат panic: send on closed channel).

⚠️ Известная гонка: «sender может ли узнать, что канал закрыт?» — нет универсального способа без race. Закрывать должен только sender, и только если он единственный.

// ОК — один producer, один consumer
go func() {
defer close(ch)
for x := range source {
ch <- transform(x)
}
}()
// ОК — fan-out: один producer закрывает, fan-in закрывает aggregated
out := make(chan int)
var wg sync.WaitGroup
for _, in := range inputs {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for v := range c {
out <- v
}
}(in)
}
go func() {
wg.Wait()
close(out)
}()

Источник: Go Channels: Runtime Internals Deep Dive (Gabor Koos, 2025)

Mutex в Go = гибрид spinlock + semaphore.

type Mutex struct {
state int32 // битовое поле: locked|woken|starving|waiterCount
sema uint32 // семафор для парковки
}

Состояния:

  • Normal mode: новая горутина пытается захватить через CAS + короткий spin (если CPU > 1 и P не загружен), потом паркуется через runtime_SemacquireMutex.
  • Starvation mode (с Go 1.9): если goroutine ждёт > 1ms — флаг starving, mutex передаётся первой в очереди (FIFO).

Mutex profiling:

Окно терминала
go test -mutexprofile=mutex.out -mutexprofilefraction=1
go tool pprof mutex.out
// Включить в коде:
runtime.SetMutexProfileFraction(1) // 1 = сэмплировать всё
runtime.SetBlockProfileRate(1)

Multi-Producer Single-Consumer на atomic CAS:

type Node struct {
next atomic.Pointer[Node]
value any
}
type MPSC struct {
head atomic.Pointer[Node] // только consumer пишет head
tail atomic.Pointer[Node] // producers пишут tail
}
func (q *MPSC) Push(v any) {
n := &Node{value: v}
prev := q.tail.Swap(n)
prev.next.Store(n) // линкуем после swap
}
func (q *MPSC) Pop() (any, bool) {
head := q.head.Load()
next := head.next.Load()
if next == nil {
return nil, false
}
q.head.Store(next)
return next.value, true
}

Готовые библиотеки:

  • cyub/ringbuffer — SPSC/SPMC/MPSC/MPMC ring buffer
  • hedzr/go-ringbuf — MPMC lock-free generic
  • LENSHOOD/go-lock-free-ring-buffer
  • Lock-free: хотя бы один поток гарантированно завершит операцию за конечное число шагов.
  • Wait-free: КАЖДЫЙ поток завершит за конечное число шагов (сильнее).

В Go wait-free алгоритмы редки — большинство «lock-free» из-за CAS-loop.

Когда CAS видит «то же значение», но между двумя проверками значение успело смениться и вернуться. Классическая проблема lock-free stack.

Решение в Go:

  • Tagged pointer (atomic счётчик в верхних битах указателя).
  • unsafe.Pointer + version counter.
  • Использовать sync/atomic.Value с immutable снапшотами.

В Go из-за GC ABA встречается реже (GC сохраняет память), но всё ещё актуальна в low-level structures.

CPU кэш-линия = 64 байта (обычно). Если две переменные на разных ядрах лежат в одной линии — invalidation pingpong, performance drop в 5-10x.

// Плохо:
type Counters struct {
A int64
B int64 // лежит в той же кэш-линии
}
// Хорошо:
type Counters struct {
A int64
_ [56]byte // padding до 64 bytes
B int64
_ [56]byte
}

Benchmark (реальные числа): 45ns/op → 7ns/op (6.4x speedup) при contention. Но: padding увеличивает память, имеет смысл только в hot path с high contention.

В Go 1.21+ есть runtime.CPUCacheLineSize() (experimental).

Источник: False Sharing in Go (Genchi Lu), 100 Go Mistakes #92

import (
"golang.org/x/sync/errgroup"
"golang.org/x/sync/singleflight"
)
// errgroup: запускаем N задач, возвращаем первую ошибку, отменяем остальных
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 {
log.Error(err)
}
// singleflight: предотвращаем дубликаты запросов (cache stampede)
var group singleflight.Group
result, err, shared := group.Do("user:42", func() (any, error) {
return db.GetUser(42)
})
// shared=true → этот вызов разделил результат с другими

Применение singleflight:

  • Cache miss → миллион параллельных запросов в БД? Только один реальный, остальные ждут результат.
  • TLS handshake deduplication.
  • Кэш warming.

“Concurrency is dealing with lots of things at once. Parallelism is doing lots of things at once.”

  • Concurrency — структура программы (несколько independent flows).
  • Parallelism — выполнение (одновременно на разных ядрах).

Go-программа может быть concurrent но не parallel (GOMAXPROCS=1).

Принципы:

  1. Каналы не возвращаются из публичных API (кроме узких случаев — pipelines).
  2. Context — первый аргумент, всегда ctx context.Context.
  3. Передавайте read-only каналы (<-chan T) если получатель не должен писать.
  4. Обработка graceful shutdown через context.Done() + завершение горутин.
  5. Bounded concurrency через worker pool / semaphore.
// Хороший API:
type Service struct {
workers int
}
func (s *Service) Process(ctx context.Context, items []Item) error {
sem := make(chan struct{}, s.workers)
g, ctx := errgroup.WithContext(ctx)
for _, item := range items {
item := item
sem <- struct{}{}
g.Go(func() error {
defer func() { <-sem }()
return s.handleOne(ctx, item)
})
}
return g.Wait()
}

Источник: Three Dots Labs, Encore Advanced Go Concurrency


Инструменты:

  • Pyroscope (Grafana) — open-source, удобный, scrape-style.
  • Parca — еще один CNCF проект, базируется на eBPF.
  • Datadog Continuous Profiler — managed.
  • Polar Signals (Conprof origin).
// Pyroscope SDK integration
pyroscope.Start(pyroscope.Config{
ApplicationName: "my.app",
ServerAddress: "http://pyroscope:4040",
ProfileTypes: []pyroscope.ProfileType{
pyroscope.ProfileCPU,
pyroscope.ProfileAllocObjects,
pyroscope.ProfileAllocSpace,
pyroscope.ProfileInuseObjects,
pyroscope.ProfileInuseSpace,
pyroscope.ProfileGoroutines,
pyroscope.ProfileMutexCount,
pyroscope.ProfileMutexDuration,
pyroscope.ProfileBlockCount,
pyroscope.ProfileBlockDuration,
},
})

Overhead pprof в production: ~1-2% CPU. Goroutine dump — STW ~1ms на 10к горутин.

Окно терминала
# Heap profile (память сейчас alloc'd)
curl -o heap.out http://localhost:6060/debug/pprof/heap
go tool pprof -http=:8080 heap.out
# Allocations за всё время (для поиска hot allocators)
curl -o allocs.out http://localhost:6060/debug/pprof/allocs
go tool pprof -http=:8080 -alloc_space allocs.out
# Diff двух heap'ов
go tool pprof -base heap_before.out heap_after.out

Стандартные команды в pprof:

  • top — топ функций по памяти
  • list <funcName> — построчная аннотация
  • web — graphviz call graph
  • peek <regex> — кто вызывает функцию

Чек-лист:

  1. Используйте sync.Pool для часто аллоцируемых объектов.
  2. Pre-allocate slices/maps через make([]T, 0, N).
  3. bytes.Buffer / strings.Builder вместо +=.
  4. fmt.Sprintf дорогой — используйте strconv.AppendInt.
  5. Не передавайте мелкие структуры по указателю (escape to heap).
  6. Interface conversions аллоцируют — избегайте в hot path.
  7. []bytestring конверсии копируют — используйте unsafe.String/SliceData (Go 1.20+).
sync.Pool
var bufPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
func format(s string) string {
buf := bufPool.Get().(*bytes.Buffer)
defer bufPool.Put(buf)
buf.Reset()
buf.WriteString("[INFO] ")
buf.WriteString(s)
return buf.String() // здесь будет копия
}

SoA vs AoS:

// Array of Structs (cache-unfriendly для batch обработок одного поля):
type Entity struct {
X, Y, Z float64
Velocity Vector
Color color.RGBA
}
entities := make([]Entity, 1_000_000)
// Struct of Arrays (cache-friendly):
type Entities struct {
X, Y, Z []float64
Velocity []Vector
Color []color.RGBA
}

При обработке только координат — SoA в разы быстрее (cache hit rate ~100%).

Окно терминала
# Минимальный binary
go build -ldflags="-s -w" -trimpath -o app .
# -s removes symbol table
# -w removes DWARF debug info
# С Go 1.20+ method dead code elimination
go build -ldflags="-checklinkname=0" .
# UPX compression (не рекомендуется для prod)
upx --best --ultra-brute app

Datadog кейс 2025: уменьшили размер Agent binaries на 77% за счёт устранения reflective method calls и tree-shaking.

Источник: Datadog: Reduced Agent Go binaries by 77%

Для serverless / Lambda / Cloud Run:

  • Использовать -buildmode=pie НЕЛЬЗЯ (медленнее старт).
  • Lazy init глобалов (init() блоки замедляют старт).
  • Использовать embed для статики вместо file system reads.
  • Готовый PGO профиль → меньше первичной компиляции в runtime.
db, err := sql.Open("pgx", dsn)
db.SetMaxOpenConns(25) // зависит от Postgres max_connections
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(time.Hour) // защита от утечек на стороне Postgres
db.SetConnMaxIdleTime(10 * time.Minute)

Правило: MaxOpenConns * число_pod ≤ pg_max_connections * 0.8 (оставить запас).

Если используете PgBouncer transaction mode — не используйте prepared statements (или PgBouncer 1.21+ с поддержкой).


4. Дизайн крупных систем (микросервисные паттерны)

Заголовок раздела «4. Дизайн крупных систем (микросервисные паттерны)»

Распределённая транзакция = серия локальных транзакций + compensating actions.

Два варианта:

  1. Choreography (хореография): сервисы общаются через events, каждый сам решает.

    OrderCreated → InventoryReserved → PaymentCharged → OrderConfirmed
    ↓ (если отказ)
    InventoryReservationCancelled (compensation)
  2. Orchestration (оркестрация): центральный orchestrator (например, Temporal) управляет потоком.

    // Temporal Saga в Go
    func OrderWorkflow(ctx workflow.Context, order Order) error {
    var saga Saga
    saga.AddCompensation(cancelInventory)
    if err := workflow.ExecuteActivity(ctx, ReserveInventory, order).Get(ctx, nil); err != nil {
    return saga.Compensate(ctx)
    }
    saga.AddCompensation(refundPayment)
    if err := workflow.ExecuteActivity(ctx, ChargePayment, order).Get(ctx, nil); err != nil {
    return saga.Compensate(ctx)
    }
    return workflow.ExecuteActivity(ctx, ConfirmOrder, order).Get(ctx, nil)
    }

Разделение моделей read и write:

  • Commands меняют состояние (без возврата данных) → доменная модель + write-side DB.
  • Queries читают (без побочных эффектов) → денормализованные read models + read-side DB / cache.

Применять, когда read и write нагрузки сильно различаются.

Хранить события, а не текущее состояние. Текущее состояние = свёртка событий.

✅ Плюсы: полный аудит, time travel, легко добавлять новые проекции. ❌ Минусы: сложность, eventual consistency, миграции данных.

Проблема: невозможно атомарно записать в БД И отправить сообщение в Kafka (dual write).

Outbox Pattern:

  1. В одной транзакции пишем в бизнес-таблицы И в outbox таблицу.
  2. Отдельный процесс (Debezium / собственный poller) читает outbox и публикует в Kafka.
  3. CDC через WAL (PostgreSQL logical replication) → events в Kafka.
CREATE TABLE outbox (
id UUID PRIMARY KEY,
aggregate_type TEXT,
aggregate_id UUID,
event_type TEXT,
payload JSONB,
created_at TIMESTAMPTZ DEFAULT now()
);

Inbox Pattern: на стороне consumer — таблица inbox для дедупликации обработанных событий.

// При получении сообщения
exists, err := tx.QueryRow("SELECT 1 FROM inbox WHERE id = $1", msg.ID).Scan(&one)
if exists {
return nil // already processed
}
// бизнес-логика...
tx.Exec("INSERT INTO inbox (id, processed_at) VALUES ($1, now())", msg.ID)
tx.Commit()

Источник: Outbox Pattern with Debezium, Saga Pattern with Examples in Go (2025)

Состояния: Closed → Open → Half-Open → Closed.

  • При N подряд ошибках → Open (запросы не идут к нижестоящему).
  • После timeout → Half-Open (пускает один запрос). Успех — Closed, провал — Open.

Библиотеки: sony/gobreaker, failsafe-go, afex/hystrix-go.

import "github.com/sony/gobreaker"
cb := gobreaker.NewCircuitBreaker(gobreaker.Settings{
Name: "PaymentService",
MaxRequests: 3,
Interval: 10 * time.Second,
Timeout: 30 * time.Second,
ReadyToTrip: func(counts gobreaker.Counts) bool {
return counts.ConsecutiveFailures > 5
},
})
result, err := cb.Execute(func() (any, error) {
return paymentClient.Charge(amount)
})

«Переборка корабля» — изоляция ресурсов между классами запросов. Реализация: отдельные пулы горутин/connection pools/thread pools для разных downstream сервисов. Если один сервис «зависнет», он не отъест все ресурсы.

type Bulkhead struct {
sem chan struct{}
}
func (b *Bulkhead) Execute(fn func() error) error {
select {
case b.sem <- struct{}{}:
defer func() { <-b.sem }()
return fn()
case <-time.After(100 * time.Millisecond):
return ErrBulkheadFull
}
}
import "github.com/cenkalti/backoff/v4"
err := backoff.Retry(func() error {
return makeRequest()
}, backoff.WithContext(
backoff.NewExponentialBackOff(),
ctx,
))

Правила:

  • Retry только идемпотентных операций (или с идемпотентным ключом).
  • Exponential backoff: 100ms, 200, 400, 800…
  • Full jitter: random(0, base) — предотвращает thundering herd.
  • Cap на max retries и total timeout.

Клиент посылает Idempotency-Key: <uuid> → сервер хранит результат первого вызова, при повторных с тем же ключом возвращает закэшированный ответ. Stripe-style.

func (h *Handler) CreatePayment(w http.ResponseWriter, r *http.Request) {
key := r.Header.Get("Idempotency-Key")
if cached, ok := h.idempotencyCache.Get(key); ok {
json.NewEncoder(w).Encode(cached)
return
}
result := h.processPayment(...)
h.idempotencyCache.Set(key, result, 24*time.Hour)
json.NewEncoder(w).Encode(result)
}
  • 2PC (Two-Phase Commit): coordinator + participants → блокирующий, single point of failure. Используется в XA транзакциях.
  • 3PC: добавляет pre-commit phase → меньше блокировок, но больше сообщений.
  • TCC (Try-Confirm-Cancel): каждая операция имеет 3 фазы. Бизнес-уровень.
  • Saga: см. выше — отсутствие глобальных блокировок, eventual consistency через compensation.

В современных микросервисах 2PC практически не используется — слишком дорог в latency и доступности.

Redis Redlock (Antirez vs Kleppmann):

Kleppmann (2016, до сих пор актуально) критикует:

  1. Clock skew: Redlock полагается на синхронизированное время — это нарушение асинхронной модели.
  2. GC pause / OS scheduler pause: процесс может «заснуть» дольше TTL, лок «истёк», но процесс об этом не знает → два процесса в критической секции.
  3. Нет fencing tokens: рандомное значение не монотонно — downstream не может отвергнуть stale locks.

Что делать:

  • Для advisory locks (защита от случайного двойного запуска cron): Redlock ОК.
  • Для корректности (защита денег): использовать fencing tokens через consensus (etcd, ZooKeeper) + проверять на стороне resource.
  • Альтернативы: etcd lease, PostgreSQL advisory locks (pg_advisory_lock), Apache ZooKeeper recipes.
// PostgreSQL advisory lock — простая и надёжная альтернатива:
_, err := db.ExecContext(ctx, "SELECT pg_advisory_lock($1)", lockID)
defer db.ExecContext(ctx, "SELECT pg_advisory_unlock($1)", lockID)
// ... critical section

Источник: Martin Kleppmann: How to do distributed locking, Antirez: Is Redlock safe?

Raft (понятнее Paxos, более популярен):

  • Leader election (terms, votes).
  • Log replication (leader пишет → majority подтверждает → commit).
  • Safety: term + index гарантируют linearizability.

Используется в: etcd, Consul, CockroachDB, TiKV, YDB, Hazelcast.

В Go есть production-grade библиотека: go.etcd.io/raft/v3 — используется в etcd, k8s, Docker Swarm, CockroachDB, TiDB.

// Минималистичный пример (псевдокод etcd raft)
node := raft.StartNode(config, peers)
for {
select {
case rd := <-node.Ready():
saveToStorage(rd.HardState, rd.Entries, rd.Snapshot)
send(rd.Messages)
for _, entry := range rd.CommittedEntries {
apply(entry)
}
node.Advance()
}
}

Paxos: более старый, сложнее реализовать. Используется в Google Chubby, Megastore.

  • Kafka Streams (JVM-only) — DSL для transform → join → aggregate над Kafka topics.
  • Apache Flink — stateful stream processing, поддержка exactly-once, window operations, event time processing.
  • Materialize — Postgres-совместимая streaming database.

Для Go: нет нативных эквивалентов, но есть goka (Lovoo) — облегчённая Kafka Streams-подобная библиотека на Go.

  • Data Lake: raw данные в object storage (S3, GCS) в форматах Parquet/ORC/Avro.
  • Lakehouse: data lake + ACID-транзакции + schema enforcement. Технологии: Delta Lake, Apache Iceberg, Apache Hudi.
  • Запросы через Trino/Presto, Spark, Databricks.

В России: Яндекс DataLens + S3 + Trino + ClickHouse — типичный стек.

Debezium — стандарт де-факто для CDC:

  • PostgreSQL: через logical replication slots (wal2json, pgoutput).
  • MySQL: через binlog.
  • MongoDB: через oplog.
  • Публикует change events в Kafka.

Альтернативы: Maxwell, Striim, AWS DMS.

Кейсы:

  • Outbox pattern (как описано выше).
  • Реплицировать Postgres → ClickHouse для аналитики.
  • Sync микросервисных БД (избегая sync RPC).
  • Build search index (Elasticsearch) из Postgres.

Формат (Michael Nygard):

# ADR-007: Use Outbox Pattern for Event Publishing
## Status
Accepted
## Context
We need to publish events to Kafka after DB commits.
Direct calls cause dual-write inconsistency.
## Decision
Use Outbox table + Debezium for CDC.
## Consequences
+ Atomicity guaranteed
- Latency +50ms (acceptable)
- Extra infra: Debezium + Kafka Connect

Хранить в docs/adr/NNNN-title.md в репозитории. Tool: adr-tools CLI.

4 уровня документации:

  1. Context — система и её внешние акторы.
  2. Container — приложения, БД, сервисы.
  3. Component — модули внутри container.
  4. Code — class diagram (опционально).

Инструменты: Structurizr, C4-PlantUML, draw.io с C4-шаблонами.

Strategic DDD:

  • Bounded Context — изолированная модель домена.
  • Context Map — отношения между bounded contexts: Partnership, Shared Kernel, Customer-Supplier, Conformist, Anti-Corruption Layer, Open Host Service, Published Language.
  • Ubiquitous Language — словарь, общий для бизнеса и кода.

Эвристики для границ:

  • Один Bounded Context = одна команда (Conway’s Law).
  • Один BC = одна модель «Order», даже если в логистике и продажах разные смыслы.
  • Don’t share DB schema между BC.

Три подхода:

  1. URL versioning: /v1/users, /v2/users — самый простой.
  2. Header versioning: API-Version: 2, Accept-Version: 2.
  3. Content-type versioning: Accept: application/vnd.company.user+json; version=2.

Для gRPC: в proto файле выделять major-версию в пакет: myapi.v1, myapi.v2.

Protobuf:

  • Не удалять field numbers (mark as reserved).
  • Не менять types.
  • Не переименовывать enum values.
  • Wire format гарантирует совместимость (unknown fields игнорируются).

JSON:

  • Можно добавлять fields (опциональные).
  • Не удалять, не менять types существующих.
  • Опасно полагаться на field order.

Confluent Schema Registry — централизованное хранилище схем (Avro/Protobuf/JSON Schema) для Kafka. Producers/consumers получают schema по ID.

Compatibility levels:

  • BACKWARD — новые consumers могут читать старые данные.
  • FORWARD — старые consumers могут читать новые данные.
  • FULL — оба.
  • *_TRANSITIVE — для всех предыдущих версий.

Karapace — open-source альтернатива Confluent (Aiven). Совместим API. Используется в Российских инсталляциях.

3 стратегии:

  1. Pool model: общая БД, tenant_id в каждой таблице. Самый дешёвый, но риск утечки данных.
  2. Bridge model: shared cluster, separate schemas per tenant.
  3. Silo model: отдельные БД на tenant. Максимальная изоляция, дорого.

Чаще всего: silo для enterprise, pool для self-serve.

Постепенное замещение функциональности монолита микросервисами. Между клиентом и монолитом ставят facade/proxy, который маршрутизирует часть запросов на новый сервис. Со временем монолит «обернут» и можно убить.

Шаги:

  1. Identify seam (модуль/feature для извлечения).
  2. Build new service with same API.
  3. Route writes/reads через facade.
  4. Migrate data (dual-write или CDC).
  5. Decommission old code.

Workshop-техника от Alberto Brandolini:

  • Стикеры orange — domain events (“OrderPlaced”).
  • Стикеры blue — commands (“PlaceOrder”).
  • Стикеры yellow — aggregates (“Order”).
  • Стикеры pink — external systems.
  • Стикеры purple — policies/reactions.

Цель: разметить временную линию домена → выявить bounded contexts.

Основные ось trade-offs:

  • Consistency vs Availability (CAP) — в partition: что выбрать?
  • Consistency vs Latency (PACELC) — даже без partition: ждать quorum или вернуть быстрее?
  • Performance vs Cost — кэш vs больше CPU vs больше disk.
  • Time-to-market vs Quality — MVP vs proper engineering.
  • Sync vs Async — простота vs масштабируемость.
  • Centralized vs Distributed — operational simplicity vs scale.

На интервью: никогда не давайте однозначный ответ. Сначала уточняйте контекст, затем озвучивайте trade-offs.


  • Cost-based optimizer на основе статистики (pg_statistic).
  • EXPLAIN ANALYZE BUFFERS — must-have для оптимизации.
  • pg_stat_statements — топ-запросы по cumulative time.
  • B-tree: default, equality/range, sorted.
  • Hash: только equality, реже используется.
  • GIN (Generalized Inverted): для arrays, JSONB, full-text search (tsvector).
  • GiST (Generalized Search Tree): geometric data, ranges, KNN.
  • SP-GiST: space-partitioned (quadtree, kd-tree).
  • BRIN (Block Range): для огромных таблиц с естественной сортировкой (time-series).
  • Bloom: для multi-column equality, extension.
CREATE INDEX idx_active_users ON users (last_login)
WHERE deleted_at IS NULL;
-- Индекс охватывает только 1% активных пользователей,
-- но используется в 90% запросов.

Реальный кейс: query time с 4.3s → 5ms за счёт partial index.

CREATE INDEX idx_orders_user_status ON orders (user_id, status) INCLUDE (total);
-- Index-only scan: не нужно лезть в heap для total

Patroni (по типу Stolon, Zalando) — лидер для PostgreSQL HA:

  • Использует etcd/Consul/ZooKeeper для leader election.
  • Автоматический failover.
  • Streaming replication + sync standby.

Repmgr (2ndQuadrant): альтернатива, более «ручная», но проще установка.

Архитектура production:

[App] → [PgBouncer] → [Patroni primary]
↓ streaming repl
[Standby 1]
[Standby 2]
↓ async repl
[Reporting replica]
ModeКогда возвращается в poolПодходит для
SessionПосле disconnect клиентаLong-lived sessions, prepared statements
TransactionПосле commit/rollbackСамый частый выбор для микросервисов
StatementПосле statementОчень редко, ломает многие фичи Postgres

В transaction mode НЕ работают:

  • LISTEN/NOTIFY
  • Session-level SET (используйте SET LOCAL)
  • Prepared statements (PgBouncer 1.21+ это починил частично)
  • Temporary tables
-- Топ запросов по cumulative time
SELECT query, calls, total_exec_time, mean_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
-- Текущая активность
SELECT pid, usename, application_name, state, query, query_start
FROM pg_stat_activity
WHERE state != 'idle';
-- Блокировки
SELECT blocked_locks.pid AS blocked_pid, blocking_locks.pid AS blocking_pid
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_locks blocking_locks
ON blocking_locks.locktype = blocked_locks.locktype
WHERE NOT blocked_locks.granted;

С Postgres 10+: publication/subscription, не вся БД, а отдельные таблицы.

-- На источнике
CREATE PUBLICATION my_pub FOR TABLE orders, users;
-- На приёмнике
CREATE SUBSCRIPTION my_sub
CONNECTION 'host=source dbname=mydb'
PUBLICATION my_pub;

Применения:

  • Online migration major version.
  • CDC через Debezium (на основе logical replication).
  • Multi-region read replicas.

PostgreSQL использует MVCC: при UPDATE/DELETE старая версия (tuple) остаётся, помечается dead.

  • VACUUM — освобождает место под новые tuples (в той же странице).
  • VACUUM FULL — переписывает таблицу (блокирующая операция).
  • pg_repack — non-blocking аналог VACUUM FULL.

Autovacuum tuning:

autovacuum_vacuum_scale_factor = 0.05 # default 0.2 — слишком поздно для больших таблиц
autovacuum_vacuum_cost_delay = 0 # больше нагрузка — но быстрее
autovacuum_max_workers = 6 # default 3

На горячих таблицах ставьте per-table:

ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.01,
autovacuum_vacuum_cost_limit = 2000
);

Источник: Snowflake: Postgres Vacuum Explained, Percona: Tuning Autovacuum

CockroachDB:

  • PostgreSQL wire compatible.
  • Raft-based replication.
  • Sharded by primary key ranges.
  • Geo-distributed.
  • Strong consistency (serializable).

YDB (Yandex):

  • Open source с 2022.
  • Кастомный YQL (SQL-подобный).
  • Hybrid: OLTP + OLAP.
  • Используется в Yandex.Cloud, в Яндекс Такси, Лавке, Маркете.
  • В РФ — основной выбор для distributed SQL.

Сравнение:

СвойствоCockroachDBYDBTiDBYugabyteDB
WirePostgreSQLYDB SDK / YQLMySQLPostgreSQL
ConsensusRaftdistributedRaft (TiKV)Raft
HTAPнетдадачастично

Источник: YDB.tech, CockroachDB vs TiDB vs YugabyteDB 2025

In-memory DB + app server, Lua-скриптинг + WAL персистентность.

  • Очень высокая пропускная: до 1M RPS на одном ядре.
  • Используется в VK / Mail.ru: профили, ленты, кэши.
  • Реплицируется master-master или master-replica.

В 2025-2026 (VK Tech): релиз Tarantool DB 3.0, открытое ядро + enterprise edition.

Go драйвер: tarantool/go-tarantool/v2.

conn, err := tarantool.Connect(ctx, dialer, tarantool.Opts{})
data, err := conn.Do(tarantool.NewSelectRequest("space").Index("primary").Key([]any{42})).Get()

Column-oriented analytical DB:

  • Hibernate/MergeTree engine: данные хранятся колоночно, в part’ах (immutable файлы).
  • Vectorized execution: SIMD-friendly, batch processing колонок.
  • Partition by date обычно.
  • Не для OLTP! Нет ACID, нет точечных UPDATE/DELETE (хотя есть ALTER TABLE ... UPDATE — но не для частых операций).
  • Идеален для аналитики, логов, метрик. Используется в Яндекс, Авито, Cloudflare.
CREATE TABLE events (
timestamp DateTime,
user_id UInt64,
event_type LowCardinality(String),
payload String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (event_type, user_id, timestamp);

Go драйверы: clickhouse-go/v2 (нативный binary protocol), chproxy.

Топ-3 в 2025:

  • pgvector — extension к Postgres, простой, бесплатный, хорош до 10M векторов.
  • Qdrant (написан на Rust, есть Go SDK) — purpose-built, отлично для production.
  • Milvus — масштабируется лучше, сложнее operate.
-- pgvector
CREATE EXTENSION vector;
CREATE TABLE embeddings (id INT, vec vector(1536));
CREATE INDEX ON embeddings USING ivfflat (vec vector_cosine_ops);
SELECT id FROM embeddings ORDER BY vec <=> query_vec LIMIT 10;

Применение: семантический поиск, RAG (Retrieval-Augmented Generation), рекомендации.


  • Topic = именованный лог, partitioned, replicated.
  • Partition — единица параллелизма; messages внутри partition упорядочены.
  • Replication Factor обычно 3.
  • ISR (In-Sync Replicas) — реплики, актуальные относительно лидера.

В Kafka 2.x: один из брокеров — controller (выбирается через ZooKeeper). В Kafka 3.0+: KRaft mode — Kafka использует свой собственный Raft для metadata, без ZooKeeper. С Kafka 4.0 (2025) — KRaft стал default.

  • Acks: 0 (fire-and-forget), 1 (leader подтвердил), all/-1 (все ISR подтвердили).
  • idempotent producer: enable.idempotence=true — у producer есть PID, sequence number → broker отвергает дубликаты при retry.
  • Transactional producer: transactional.id, методы beginTransaction(), send(), commitTransaction(). Группирует записи в атомарную транзакцию.
  • Producer: idempotence + transactions.
  • Consumer: isolation.level=read_committed (видит только commit’нутые транзакции).
  • Consumer offsets коммитятся в той же транзакции что и output writes (“read-process-write” exactly-once).
  • Kafka 4.0+ значительно ускорил transaction commits в KRaft mode.
  • Партиции распределяются между consumers группы.
  • Rebalance при добавлении/удалении consumer (или sticky rebalance — меньше движений).
  • Static membership (group.instance.id) — не делать rebalance при кратковременном disconnect.
  • Producer пишет с schema ID (через Schema Registry).
  • Consumer читает по schema ID + актуальная для consumer схема → автоматическое converting.

Источник: Confluent: Exactly-Once Semantics

Сравнение (актуально 2025):

БиблиотекаProsCons
franz-go (twmb/franz-go)Самая быстрая, feature-complete, pure Go, поддержка ВСЕХ Kafka featuresAPI более низкоуровневый
segmentio/kafka-goПростой API, чистый Go, хорошие production-historyНе все фичи (transactions неполные)
sarama (IBM/sarama)Самый старый, много примеровПадающая поддержка, не поддерживает context
confluent-kafka-goЛучшая поддержка от ConfluentCGo dependency (librdkafka), кросс-компиляция боль

Современный выбор: franz-go для новых проектов, kafka-go если нужен простой API.

JetStream = persistent layer над NATS Core.

js, _ := jetstream.New(nc)
stream, _ := js.CreateStream(ctx, jetstream.StreamConfig{
Name: "ORDERS",
Subjects: []string{"orders.>"},
Storage: jetstream.FileStorage,
Replicas: 3,
})
consumer, _ := stream.CreateConsumer(ctx, jetstream.ConsumerConfig{
Durable: "order-processor",
AckPolicy: jetstream.AckExplicitPolicy,
MaxDeliver: 5,
AckWait: 30 * time.Second,
FilterSubject: "orders.created",
})
// Pull-consumer (рекомендуется)
msgs, _ := consumer.Fetch(100, jetstream.FetchMaxWait(5*time.Second))
for msg := range msgs.Messages() {
process(msg)
msg.Ack()
}

Особенности:

  • KV store + Object Store как абстракции над streams.
  • Cluster (Raft).
  • Хорош для low-latency messaging внутри одного DC.
  • Хуже Kafka для high-throughput (>100k/sec) и для очень больших ретеншенов.

Apache Pulsar — конкурент Kafka:

  • Topic = subscription model (несколько типов: exclusive, shared, key_shared, failover).
  • Storage tier (Apache BookKeeper) отделён от broker.
  • Geo-replication из коробки.
  • Tiered storage (S3 archive старых данных).

Go: apache/pulsar-client-go. В РФ почти не используется (Kafka + NATS доминируют).

Проблема: producer быстрее consumer → накапливаются сообщения → OOM.

Решения:

  • Bounded channels/buffers.
  • Rate limiting producer’а (golang.org/x/time/rate).
  • Pause/resume polling consumer’а (Kafka: poll более редко).
  • Drop strategies (для метрик): отбрасывать старые / выборочно.
  • Reactive Streams pattern (request-N model).

Dead Letter Queue — топик/очередь для сообщений, которые не удалось обработать после N попыток.

func handleMessage(ctx context.Context, msg Message) error {
err := process(msg)
if err != nil {
msg.RetryCount++
if msg.RetryCount >= 5 {
return sendToDLQ(ctx, msg, err)
}
return retryWithBackoff(ctx, msg)
}
return nil
}

Best practices:

  • DLQ — для непреодолимых ошибок (поломанный payload).
  • Алерт на любое попадание в DLQ.
  • Tool для re-drive (повторная попытка после фикса) DLQ → main topic.
  • Сохранять контекст ошибки (last error, attempt count, stack).

Kubebuilder — официальный SDK для написания operators на Go.

Окно терминала
kubebuilder init --domain example.com --repo example.com/my-operator
kubebuilder create api --group apps --version v1 --kind MyApp

Сгенерированная структура:

  • api/v1/myapp_types.go — CRD spec/status.
  • internal/controller/myapp_controller.go — Reconcile loop.
  • config/ — Kustomize манифесты.
func (r *MyAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var app appsv1.MyApp
if err := r.Get(ctx, req.NamespacedName, &app); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Ensure Deployment matches spec
desired := buildDeployment(app)
if err := ctrl.SetControllerReference(&app, desired, r.Scheme); err != nil {
return ctrl.Result{}, err
}
if err := r.Patch(ctx, desired, client.Apply, client.ForceOwnership, client.FieldOwner("myapp-operator")); err != nil {
return ctrl.Result{}, err
}
// Update status
app.Status.Phase = "Running"
if err := r.Status().Update(ctx, &app); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}

Принципы:

  • Idempotency — Reconcile может вызываться сколько угодно, результат тот же.
  • Level-based, not edge-based — реагируем на desired state, не на events.
  • Status subresource — отделяется от Spec для оптимизации.
  • Finalizers — для cleanup перед удалением.

Best practices:

  1. Версионирование: v1alpha1 → v1beta1 → v1.
  2. Validating webhook: проверять CR на admission.
  3. Conversion webhook: для миграции между версиями.
  4. OpenAPI schema в CRD — для валидации в k8s API.
  5. Status conditions — стандартный pattern из k8s API.
status:
conditions:
- type: Ready
status: "True"
lastTransitionTime: "2026-05-21T10:00:00Z"
reason: Reconciled
message: "All resources created"

Validating admission: валидирует объекты при create/update. Mutating admission: модифицирует (например, добавляет sidecars, default values).

Реализация на Go через sigs.k8s.io/controller-runtime/pkg/webhook.

func (m *MyAppValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
app := obj.(*appsv1.MyApp)
if app.Spec.Replicas < 1 {
return nil, fmt.Errorf("replicas must be >= 1")
}
return nil, nil
}

Istio:

  • Polyglot service mesh.
  • Envoy sidecar в каждом pod.
  • Control plane: istiod.
  • Фичи: mTLS, traffic shifting, observability, policy.

Linkerd:

  • Простой, lightweight.
  • Собственный proxy (linkerd2-proxy) на Rust.
  • Меньше features, но проще operate.

Ambient mesh (Istio 2024+):

  • Sidecarless: ztunnel (per node) + waypoint proxies (опционально).
  • Меньше resources overhead.
  • Постепенный uptake — пока что не все features работают.

3 уровня:

  1. Aggregated API Server — добавить свой API server (например, metrics-server).
  2. CRDs + Operator — самый частый путь.
  3. CSI/CNI/CRI plugins — для storage/networking/container runtime.
  • Kubefed (deprecated)KCP / Cluster API.
  • Argo CD + GitOps для multi-cluster deploy.
  • Submariner — network connectivity между clusters.
  • Multi-cluster Services API — стандарт для cross-cluster service discovery.

Для микросервисов: чаще проще активный-пассивный регион + DNS failover, чем active-active multi-region.


Три pillar’а:

  • Traces — distributed traces со span’ами.
  • Metrics — числовые временные ряды.
  • Logs — структурированные логи (с TraceID для correlation).
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
)
exp, _ := otlptracegrpc.New(ctx)
tp := trace.NewTracerProvider(
trace.WithBatcher(exp),
trace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName("my-service"),
semconv.ServiceVersion("v1.2.3"),
)),
)
otel.SetTracerProvider(tp)
defer tp.Shutdown(ctx)
// Использование
tracer := otel.Tracer("my-package")
ctx, span := tracer.Start(ctx, "operation")
defer span.End()

Стандартизированные имена attributes:

  • http.method, http.status_code, http.url
  • db.system, db.statement, db.operation
  • messaging.system, messaging.destination.name
  • rpc.system, rpc.service, rpc.method

В Go runtime (через runtime/metrics):

  • go.memory.used, go.memory.limit, go.gc.duration, go.goroutine.count

Baggage — propagated key-value через distributed trace. Не путать с span attributes (только на spaн’е).

bag, _ := baggage.New(baggage.NewKeyValueProperty("tenant_id", "42"))
ctx = baggage.ContextWithBaggage(ctx, bag)
// Передаётся через HTTP/gRPC headers (W3C Baggage)

⚠️ Не клади много данных в baggage — это вес каждого outgoing request.

Exemplar — связь между метрикой и конкретным trace ID. Когда видишь spike в P99 latency → клик → попадаешь в тот самый trace.

// Prometheus + OTel exemplars
hist := prometheus.NewHistogramVec(...)
hist.With(labels).(prometheus.ExemplarObserver).ObserveWithExemplar(
duration.Seconds(),
prometheus.Labels{"trace_id": traceID},
)

Определения:

  • SLI: квантитативная метрика (% successful requests).
  • SLO: target value (99.9% over 30 days).
  • SLA: контракт с пользователем (с финансовыми последствиями).
  • Error Budget = 100% - SLO.

Пример:

  • SLI: sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))
  • SLO: 99.9% за 30 дней.
  • Error budget: 0.1% × N запросов = бюджет ошибок.

Burn rate alerting (multi-window, multi-burn-rate):

  • 2% бюджета за 1 час (быстро жжём) → P1 alert.
  • 10% бюджета за 6 часов (медленно жжём, но накапливается) → P2 alert.

Error budget policy:

  • Бюджет не исчерпан → можно деплоить.
  • Бюджет исчерпан → стоп фич, только bug fixes / reliability work.

Источник: Google SRE Workbook: Implementing SLOs

RED (для request-driven сервисов):

  • Rate — requests/sec
  • Errors — errors/sec
  • Duration — latency distribution

USE (для resources: CPU, disk, network):

  • Utilization
  • Saturation (queue depth)
  • Errors

Дополняют друг друга: RED — взгляд снаружи (как клиент), USE — взгляд изнутри.

Blameless postmortem:

  1. Timeline (точные временные метки).
  2. Impact (затронутые SLO, пользователи, $).
  3. Root cause (5 whys).
  4. Detection (как заметили?).
  5. Action items (с owner и deadline).

Tooling: Notion, Confluence, Linear, специализированные incident.io.

Чек-лист на проде:

  1. Метрики: что выросло/упало? (Grafana, Datadog)
  2. Логи: ищем ERROR в окне инцидента (Loki, ELK).
  3. Tracing: смотрим failing traces (Jaeger, Tempo, Datadog APM).
  4. Profiling: live pprof endpoint (если безопасно).
  5. Goroutine dump: curl /debug/pprof/goroutine?debug=2.

Частые проблемы:

  • Goroutine leak → NumGoroutine() растёт линейно.
  • Connection leak → max_open_conns исчерпан.
  • Cache stampede → singleflight forgotten.
  • Memory leak → heap profile diff (before vs after).
  • Slow queries → pg_stat_statements.

Принципы (Netflix Chaos Manifesto):

  1. Hypothesize about steady state.
  2. Vary real-world events.
  3. Run in production.
  4. Automate experiments.
  5. Minimize blast radius.

Инструменты:

  • Chaos Mesh (CNCF) — для k8s, удобный UI, fault injection (network, pod, IO, time).
  • LitmusChaos (CNCF) — Hub предопределённых экспериментов, удобно для CI/CD.
  • Gremlin — commercial.
# Chaos Mesh: убиваем 30% pod'ов сервиса
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
name: pod-failure-example
spec:
action: pod-failure
mode: fixed-percent
value: "30"
duration: "60s"
selector:
namespaces:
- default
labelSelectors:
"app": "my-service"

В 2025: LitmusChaos выпустили MCP Server для интеграции с AI ассистентами.

Источник: Chaos Engineering 2025 Guide


mTLS: обе стороны (client и server) предоставляют certs.

SPIFFE (Secure Production Identity Framework For Everyone) — стандарт workload identity (CNCF). SPIRE — reference implementation SPIFFE.

spiffe://example.org/ns/default/sa/my-service

Workflow:

  1. SPIRE Agent (на каждой ноде) аттестует workload (через k8s labels, container image hash, etc.).
  2. Выдаёт SVID (SPIFFE Verifiable Identity Document) — короткоживущий X.509 cert или JWT.
  3. Auto-renewal до expiry.

Go SDK: go-spiffe/v2.

source, _ := workloadapi.NewX509Source(ctx)
defer source.Close()
tlsConfig := tlsconfig.MTLSClientConfig(source, source, tlsconfig.AuthorizeAny())
client := &http.Client{Transport: &http.Transport{TLSClientConfig: tlsConfig}}

Используют: Bloomberg, Pinterest, Uber, Square. В РФ — внедряется на крупных платформах.

HashiCorp Vault — лидер. Acquired by IBM в 2025. SOPS (Mozilla) — для GitOps (зашифрованные YAML/JSON в Git). Sealed Secrets (Bitnami) — k8s native, encrypted в кластере. External Secrets Operator — bridge между Vault/AWS Secrets Manager и k8s Secrets.

Best practices:

  1. Never hardcode secrets.
  2. Short-lived dynamic credentials (Vault DB roles).
  3. Rotation automation.
  4. Audit logs.
  5. Encryption at rest + in transit.
  6. File-mounted secrets > environment variables (env видны в /proc).

Syft (Anchore) — генерация SBOM из контейнеров, директорий, jar.

Окно терминала
syft my-image:latest -o cyclonedx-json > sbom.json

Grype (Anchore) — поиск vulnerabilities по SBOM.

Окно терминала
grype sbom:./sbom.json

Стандартные форматы: CycloneDX, SPDX.

Sigstore — keyless code signing для open-source supply chain.

  • Fulcio: short-lived certificates (OIDC).
  • Rekor: transparency log (tamper-evident).
  • Cosign: CLI для signing/verification.
Окно терминала
# Sign image
cosign sign --yes my-registry/my-image:v1.0
# Verify
cosign verify --certificate-identity-regexp '.*' --certificate-oidc-issuer-regexp '.*' my-registry/my-image:v1.0

SLSA (Supply chain Levels for Software Artifacts) framework:

  • L1: документированный build process.
  • L2: tamper-resistant build platform.
  • L3: trusted, verifiable provenance.
  • L4: highest assurance.

Минимальный pipeline 2025:

  1. CI: build → run tests → generate SBOM (Syft).
  2. Sign artifact (cosign).
  3. Create SLSA provenance.
  4. Push to registry.
  5. At deploy: verify signature + provenance.

Spoofing — подмена identity. Tampering — изменение данных. Repudiation — отрицание действий. Information disclosure — утечка. Denial of service. Elevation of privilege.

Сессии threat modeling: рисуем data flow diagram → ищем для каждого компонента STRIDE угрозы → mitigations.

AWS KMS / GCP Cloud KMS / Yandex KMS:

  • Envelope encryption: данные шифруем DEK (data encryption key), DEK шифруем KEK (key encryption key) в KMS.
  • Auto-rotation KEK.
  • Audit log использования.

Go: cloud.google.com/go/kms, github.com/aws/aws-sdk-go-v2/service/kms.

client, _ := kms.NewClient(ctx)
ciphertext, _ := client.Encrypt(ctx, &kmspb.EncryptRequest{
Name: "projects/p/locations/l/keyRings/k/cryptoKeys/key",
Plaintext: plainData,
})

Принципы:

  1. Дай задачу с границами — джун должен понимать “что”, иметь свободу в “как”.
  2. Pair programming — 30 мин в неделю с каждым.
  3. Code review как обучение, не как gatekeeping.
  4. Спрашивай “почему”, а не указывай “что”.
  5. Регулярные 1:1 — статус, рост, проблемы.

Чек-лист:

  1. Корректность: решает ли PR задачу?
  2. Архитектура: вписывается в существующий код?
  3. Concurrency: data races, goroutine leaks, deadlock?
  4. Производительность: hot path аллокации, N+1 queries?
  5. Тесты: покрытие, edge cases, table-driven?
  6. Безопасность: SQL injection, secrets in code, weak crypto?
  7. Читаемость: naming, комментарии для “почему”, не “что”.

Тон:

  • ✅ “What do you think about extracting this into a separate function?”
  • ❌ “This is wrong. Rewrite.”

Структура design doc:

  1. Problem statement — что решаем?
  2. Goals & Non-goals — границы.
  3. Proposed Solution — текущий вариант.
  4. Alternatives Considered — что отбросили и почему.
  5. Trade-offs — что теряем.
  6. Migration Plan — как развернуть без даунтайма.
  7. Open Questions — что неясно.

Длина: 1-3 страницы для small change, до 10-15 для крупной системы.

Принципы:

  • Power of 2: оценивай в 1, 2, 4, 8, 16… — точность ниже, чем кажется.
  • 3-point estimation: best / nominal / worst (PERT).
  • Buffer 20-50% на «unknown unknowns».
  • Estimates не дедлайны — отдельные понятия.
  • Decompose до задач ≤ 1 неделя.
  • Product Managers: говори про business value, не про tech detail.
  • Designers: эмпатия, не «это нельзя».
  • Other Teams: write things down (ADR/RFC), документация — лучший diplomat.
  • CTO / VP: говори в SLI/SLO/$ языке.

Иерархия:

  1. CHANGELOG — что изменилось.
  2. README — как запустить.
  3. CONTRIBUTING — как контрибутить.
  4. docs/architecture — high-level.
  5. docs/adr/ — решения.
  6. docs/runbook/ — на случай инцидента.

См. Раздел 17-18.


Каждая задача — 45-60 минут, кандидат должен:

  1. Уточнить требования (5-10 мин): RPS, размеры, SLA.
  2. Estimate capacity (5 мин).
  3. High-level design (15 мин): нарисовать boxes & arrows.
  4. Deep dive (15-20 мин): один-два компонента детально.
  5. Trade-offs, edge cases (5-10 мин).

Требования: 100M URLs/day, redirect P99 < 50ms, 100:1 read:write, custom aliases.

Capacity:

  • 100M/day = ~1200 writes/sec.
  • 100:1 → 120k reads/sec.
  • 5 лет storage = 100M × 365 × 5 ≈ 180B URLs.
  • Длина short URL: 7 символов base62 = 62^7 = 3.5T — хватит.

Architecture:

[CDN]
[API Gateway + Rate Limit]
[App Server (Go)] —→ [Redis cluster] (cache, TTL 24h)
—→ [Database (Postgres / Cassandra)]
[Counter Service (Zookeeper)]
для unique IDs

Алгоритм short-code:

  • Подход A: счётчик + base62 → коллизий 0, но предсказуемо.
  • Подход B: hash(url) первые 7 байт base64 → коллизии возможны → check + retry.
  • Подход C: pre-generated unused IDs в БД, app server бронирует пачку.

Edge cases:

  • Custom alias collision → 409.
  • Same long URL → возвращать существующий short.
  • Bot crawling → rate limit + CAPTCHA.

Требования: < 1ms latency, 10TB capacity, eventual consistency.

Архитектура:

  • Consistent hashing для шардирования (с virtual nodes для уменьшения rebalance).
  • Replication: каждый ключ — primary + 2 replicas.
  • Eviction: LRU / LFU / TTL.
  • Cache patterns: cache-aside, write-through, write-behind, refresh-ahead.

Cache stampede protection:

  • Singleflight: только один request за key.
  • Probabilistic early refresh: refresh с вероятностью, растущей при приближении TTL.
  • Lock + busy-loop ожидания.

Требования: push + email + SMS, 1M notifications/sec, user preferences, idempotency.

Архитектура:

[Sender API] → [Kafka topic per channel]
[Push worker] → [APNs/FCM]
[Email worker] → [SES]
[SMS worker] → [Twilio]
[Tracking DB]
[DLQ]

Особенности:

  • User Preferences Service: rate limits, quiet hours, opt-outs.
  • Templating Service: шаблоны с подстановкой.
  • Retry with exponential backoff.
  • Bounce handling (для email).

Алгоритмы:

  • Fixed window: counter за минуту → проблема burst на границе окна.
  • Sliding window log: sorted set с timestamps → точно, но дорого.
  • Sliding window counter: counter текущего + предыдущего окна, взвешенно.
  • Token bucket: пополнение N токенов/сек, расходуем при request.
  • Leaky bucket: queue с фиксированной скоростью выхода.

Distributed implementation:

  • Redis с Lua scripting (атомарность).
  • Локальное счёт + periodic sync (для очень высокого throughput).
  • Sliding window log в Redis sorted set:
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, now - window)
local count = redis.call('ZCARD', KEYS[1])
if count < tonumber(ARGV[3]) then
redis.call('ZADD', KEYS[1], now, now)
return 1
end
return 0

См. раздел 7. Ключевые точки:

  • Partitioning (хэш ключа).
  • Replication (leader + followers, ISR).
  • Persistence (commit log на диск).
  • Consumer offsets — где остановились.
  • Ordering guarantee — внутри partition.
  • Delivery semantics: at-most-once, at-least-once, exactly-once.

Архитектура:

  • Indexing pipeline: writes → CDC → Kafka → Indexer → Elasticsearch.
  • Inverted index: term → list of doc IDs.
  • Sharding by doc ID hash.
  • Replication: replica для read availability.
  • Scoring: TF-IDF / BM25 + bizness rules + ML re-ranking.

Особенности:

  • Eventual consistency (latency 1-10s от записи до index).
  • Full-text + filter + facets.
  • Autocomplete: prefix trie / edge n-grams.

Требования: 100B msgs/day, real-time delivery, end-to-end encryption optional.

Архитектура:

[Client] ←-WebSocket--→ [Gateway (sticky session)]
[Chat Service (Go)]
[Cassandra/ScyllaDB messages] + [Redis presence]
↓ on offline
[Push Notification Service]

Ключевые решения:

  • WebSocket для real-time + reconnection.
  • Message ID generation: Snowflake (timestamp + machine + seq).
  • Storage: wide-column (Cassandra) для conversations.
  • Read receipts: пишем при delivery + read.
  • Group chats: fanout на write (deliver к каждому участнику).
  • Encryption: Signal protocol для E2EE (опционально).

Подходы:

  • Pull (lazy): при открытии ленты собираем посты от подписок. Просто, но медленно для users с тысячами подписок.
  • Push (fanout-on-write): при post — рассылаем в Redis ленты подписчиков. Быстро read, но дорого write для celebrities (10M подписчиков).
  • Hybrid: pull для celebrities + push для обычных пользователей.

Архитектура:

[Post Service] → [Fanout Service] → [Redis Timeline per user]
[ML Ranker (рекомендации)] → final feed

Ключевые принципы:

  1. Idempotency (идемпотентные ключи на любой операции).
  2. Double-entry bookkeeping (для аудита: каждая транзакция — две записи).
  3. Strong consistency (часто SQL DB, не NoSQL).
  4. Audit log — append-only, immutable.
  5. Reconciliation — daily/hourly: сверка наших данных с внешними системами.
  6. Saga для multi-step transactions.
  7. PCI DSS compliance — не хранить карточные данные, только tokens.

Архитектура:

  • MQTT для приёма телеметрии (миллионы курьеров).
  • Stream processing (Flink/Kafka Streams) для агрегации.
  • Geo-spatial DB: PostGIS, Elasticsearch geo_point, или специализированные (Tile38).
  • Map matching — снапим GPS к дорожной сети.
  • ETA prediction — ML модель + actuals from history.

  1. Two Pointers — sorted array, в т.ч. 3sum.
  2. Sliding Window — substring проблемы.
  3. Fast/Slow Pointers (Floyd) — linked list cycles.
  4. Merge Intervals — overlapping intervals.
  5. Cyclic Sort — array с числами в диапазоне.
  6. In-place Reversal of Linked List.
  7. Tree BFS/DFS — level order, path sum.
  8. Topological Sort — DAG.
  9. Union Find — connected components.
  10. Binary Search — sorted/almost-sorted.
  11. Heap (Top K) — priority queue.
  12. Backtracking — permutations, combinations.
  13. DP — knapsack, LCS, edit distance.
  14. Greedy — interval scheduling.
  15. Bit Manipulation — XOR tricks.
  1. Concurrent Map: sync.RWMutex vs sync.Map.
  2. Worker Pool with graceful shutdown (см. ниже).
  3. Rate Limiter (token bucket).
  4. Channel-based semaphore.
  5. Producer-Consumer с bounded buffer.
  6. Pipeline pattern (stage1 → stage2 → stage3).
  7. Fan-out/Fan-in для параллельной обработки.
  8. Reader-Writer lock от руки.
  9. Barrier для N горутин.
  10. Implementing context.Context от руки.

Классическая задача на собесе Авито/Яндекс:

type WorkerPool struct {
workers int
tasks chan Task
wg sync.WaitGroup
}
func NewWorkerPool(workers int) *WorkerPool {
return &WorkerPool{
workers: workers,
tasks: make(chan Task),
}
}
func (p *WorkerPool) Start(ctx context.Context) {
for i := 0; i < p.workers; i++ {
p.wg.Add(1)
go func(id int) {
defer p.wg.Done()
for {
select {
case <-ctx.Done():
return
case task, ok := <-p.tasks:
if !ok {
return
}
task.Run(ctx)
}
}
}(i)
}
}
func (p *WorkerPool) Submit(t Task) error {
select {
case p.tasks <- t:
return nil
default:
return ErrQueueFull
}
}
func (p *WorkerPool) Shutdown() {
close(p.tasks)
p.wg.Wait()
}
  1. LRU Cache — на двусвязном списке + map. O(1) get/put.
  2. LFU Cache — сложнее: 2 hashmap + sorted linked list.
  3. Skip List — реализовать.
  4. Bloom Filter — реализовать.
  5. Consistent Hashing ring.
  6. Trie — для autocomplete.
  7. Token Bucket Rate Limiter.
  8. Concurrent Queue (lock-free).
  9. TimerWheel — для миллионов таймеров.
  10. B+ Tree (для DB engine).
type LRUCache struct {
capacity int
cache map[int]*list.Element
list *list.List
}
type kv struct{ k, v int }
func NewLRU(cap int) *LRUCache {
return &LRUCache{
capacity: cap,
cache: make(map[int]*list.Element),
list: list.New(),
}
}
func (c *LRUCache) Get(k int) (int, bool) {
if e, ok := c.cache[k]; ok {
c.list.MoveToFront(e)
return e.Value.(*kv).v, true
}
return 0, false
}
func (c *LRUCache) Put(k, v int) {
if e, ok := c.cache[k]; ok {
e.Value.(*kv).v = v
c.list.MoveToFront(e)
return
}
if c.list.Len() == c.capacity {
oldest := c.list.Back()
c.list.Remove(oldest)
delete(c.cache, oldest.Value.(*kv).k)
}
e := c.list.PushFront(&kv{k, v})
c.cache[k] = e
}

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

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

1. Расскажи про GMP-модель Go scheduler. Что такое work stealing?

G — goroutine, M — OS thread, P — processor (логический). Каждый P имеет local run queue (256). M выполняет G только когда держит P. Когда P пуст — он крадёт половину задач из чужого P (work stealing). Глобальная очередь — fallback. С Go 1.14 — async preemption через сигналы (SIGURG), долгие goroutines прерываются sysmon-потоком каждые 10ms.

2. Что такое sysmon? Какая у него функция?

Системный монитор-поток, не привязан к P. Каждые 10ms проверяет: blocked syscalls (отнимает P), preemptable goroutines (sets stack guard), netpoll события, форс GC если давно не было.

3. Что произойдёт при GOMAXPROCS=1 и двух CPU-bound горутинах?

Они будут выполняться по очереди на единственном P через preemption (async с 1.14). До 1.14 — одна могла монополизировать CPU если не делала функциональных вызовов.

4. Как работает сборщик мусора в Go? Какие фазы?

Concurrent tri-color mark-and-sweep на основе Dijkstra. Фазы: STW prep (микросекунды) → Concurrent mark (с write barrier, mark assist) → STW mark termination → Concurrent sweep. Pacer решает когда стартовать на основе пропорции скорости аллокаций.

5. Что такое write barrier?

Код, вставляемый компилятором перед каждой записью указателя в objects во время concurrent mark phase. Помечает старый указатель как достижимый, чтобы GC не пропустил при concurrent изменении графа объектов.

6. Расскажи про escape analysis. Когда переменная попадает в кучу?

Компилятор анализирует, может ли время жизни переменной выйти за рамки функции. Если да — heap. Причины: возврат указателя, передача в interface, в горутину, слишком большой объект (>64KB), сохранение в глобал. go build -gcflags="-m" показывает решения.

7. Что такое GOGC и GOMEMLIMIT?

GOGC — процент роста heap для запуска GC (default 100, т.е. удвоение). GOMEMLIMIT (с 1.19) — soft memory limit, GC старается не превысить. В контейнерах: GOMEMLIMIT=90% от limit, GOGC=100 или 200.

8. Почему советуют GOGC=off + GOMEMLIMIT?

Для memory-bound сервисов: GC будет запускаться только при достижении GOMEMLIMIT (а не при удвоении). Меньше CPU overhead, эффективнее память. Подходит для long-lived сервисов без бурных скачков аллокаций.

9. Что такое PGO? Какой профит в production?

Profile-Guided Optimization (Go 1.21+). Подаёшь компилятору CPU-профиль с production → агрессивный inlining/devirtualization для горячих функций. 2-14% улучшение CPU. Uber, Datadog, Google используют.

10. Как работает мап в Go?

До 1.24: открытая адресация по бакетам (по 8 элементов), коллизии → overflow buckets. Resize при load factor > 6.5. С 1.24: Swiss Tables — SIMD-friendly metadata, extendible hashing с table splitting вместо incremental evacuation.

11. Что произойдёт если в map писать из двух горутин без синхронизации?

Race condition. С -race детектируется. В runtime есть проверка concurrent writes — программа упадёт с fatal error: concurrent map writes.

12. Slice — что внутри? Что происходит при append?

Slice — struct {data ptr, len, cap}. append: если len < cap → пишем в существующий array. Если len == cap → новый array (с роста 2x до 256, потом ~1.25x), копируем. Возвращается новый slice header.

13. Почему s := make([]int, 0, 1000) — это хорошая практика?

Pre-allocate capacity → нет повторных allocations при append, меньше GC pressure, predictable performance.

14. Что такое sync.Map? Когда использовать?

Concurrent map с двумя внутренними map (read + dirty). Read — lock-free через atomic.Value, dirty — под mutex. Promotion dirty → read когда misses достигают len(dirty). Хорош для read-heavy + disjoint keys (разные горутины обращаются к разным ключам). Часто map+RWMutex быстрее для смешанных нагрузок.

15. Чем отличается buffered от unbuffered канала?

Unbuffered — синхронный (sender блокируется до приёма). Buffered — асинхронный до cap, потом блокируется. Unbuffered полезен для synchronization point, buffered — для smoothing burst.

16. Что произойдёт при чтении из закрытого канала?

Получаем zero value + ok=false. Для уже находящихся в буфере значений — сначала их, потом zero. Запись в закрытый канал — panic.

17. Что такое sudog?

Suspended goroutine — структура в runtime, представляет горутину, ожидающую на канале. Хранит указатель на g, элемент данных, prev/next для очереди. Кладётся в hchan.recvq/sendq.

18. Как работает select со стандартными default и timeout?

Runtime пытается каждый case в случайном порядке (pseudo-random для fairness). Если ни один не готов и есть default — выполнит default. С timeout — case <-time.After(d): создаёт канал, который сработает через d.

19. Утечка time.After — что это и как избежать?

time.After создаёт *Timer который остаётся в runtime до срабатывания. Если в hot path → миллионы Timer. Решение: timer := time.NewTimer(d); defer timer.Stop().

20. Что такое context.Context? Какие виды?

Интерфейс для cancellation, deadline, values. Реализации: emptyCtx (Background, TODO), cancelCtx (WithCancel/WithCancelCause), timerCtx (WithDeadline/WithTimeout), valueCtx (WithValue). Должен быть первым аргументом функций.

21. Что не нужно класть в context.Value?

Optional параметры функции, конфигурацию. Только request-scoped данные: trace ID, user ID, tenant ID. Иначе теряется type safety и явность API.

22. Что такое linkname?

//go:linkname myname runtime.functionName — компиляторская директива, позволяет вызывать private функцию из стандартной библиотеки. Опасно, может сломаться в новой версии Go.

23. Как профилировать Go-программу?

Импортировать net/http/pprof, открыть :6060/debug/pprof/. Профили: CPU, heap (in-use), allocs (total), goroutine, mutex, block. go tool pprof -http=:8080 profile.out — UI. С 1.21+ есть PGO интеграция.

24. Чем отличается heap profile от allocs?

Heap — что сейчас alloc’ed в куче (live objects). Allocs — все аллокации за всё время работы программы (включая GC’нутые). Для поиска hot allocators — allocs. Для memory leak — heap diff.

25. Что такое runtime/trace? Когда использовать?

Покажет goroutine timeline: что когда выполнялось, syscalls, GC pauses, scheduler events. Тяжелее pprof. runtime/trace.Start(file) + смотреть go tool trace trace.out. Для расследования latency проблем.

26. Реализуй worker pool с graceful shutdown через context.

См. пример в разделе 13.2.

27. Что такое fan-in / fan-out?

Fan-out: один producer → N workers через распределение задач. Fan-in: N продюсеров → один aggregated канал. Часто комбинируют для пайплайна.

28. errgroup — что делает и когда использовать?

golang.org/x/sync/errgroup — group горутин с context cancellation и сбором первой ошибки. Первая ошибка отменяет context для остальных. Удобно для concurrent requests с правильной отменой.

29. Что такое singleflight?

golang.org/x/sync/singleflight — дедупликация одинаковых in-flight операций. Только один вызов идёт, остальные ждут результат. Защита от cache stampede.

30. Как реализовать рейт-лимитер?

Token bucket: канал размера N, тикер пополняет. Или golang.org/x/time/rate (стандарт). Для distributed — Redis (sorted set / counter with TTL).

31. Что такое starvation в Go mutex?

Если goroutine ждёт > 1ms — mutex входит в starvation mode: lock передаётся FIFO ждущим, новые тред-в спин блокируются. Защита от голодания. С Go 1.9+.

32. Какие race condition могут быть с map?

Concurrent read + write → panic. Concurrent only-reads → ОК. Concurrent writes → panic. Решение: sync.Map или RWMutex.

33. Goroutine leak — как обнаружить?

runtime.NumGoroutine() мониторить во времени. pprof goroutine endpoint → смотреть стектрейсы. С Go 1.25+ есть synctest, с 1.26+ — goroutineleak профиль.

34. Почему defer в цикле может быть проблемой?

Все defer накапливаются и выполнятся только при return функции. Если в цикле открываешь файлы и делаешь defer f.Close() — все файлы открыты до конца функции. Решение: выносить в отдельную функцию.

35. Channel или mutex — что выбрать?

“Share memory by communicating” — channel для передачи owner-ship данных. Mutex — для защиты доступа к shared state. Не догма: счётчики проще через atomic/mutex, передача события — через channel.

36. Что такое spinlock в Go?

Активное ожидание (CPU крутит loop, проверяя условие). Используется в runtime mutex для коротких блокировок (пара десятков циклов). В user space — runtime.Gosched() или ждать через channel.

37. False sharing — что и как избежать?

Две переменные на разных горутинах попадают в одну CPU cache line (64B) → invalidation pingpong. Решение: padding _[56]byte между ними. Актуально для счётчиков в high-contention сценариях.

38. Реализуй concurrent queue (lock-free).

Через atomic.Pointer (Go 1.19+). MPSC проще: head пишет только consumer, tail — producers через Swap. См. раздел 2.3.

39. ABA-проблема — что это?

CAS видит “то же значение”, но между чтением и CAS оно поменялось и вернулось. В Go GC снижает риск (память не освобождается), но в low-level structures — tagged pointers / version counters.

40. Реализуй semaphore через channel.

type Semaphore chan struct{}
func New(n int) Semaphore { return make(Semaphore, n) }
func (s Semaphore) Acquire() { s <- struct{}{} }
func (s Semaphore) Release() { <-s }

41. Чем отличаются isolation levels в Postgres?

Read Uncommitted (Postgres = Read Committed по факту), Read Committed (default), Repeatable Read (snapshot isolation на старте транзакции), Serializable (SSI — Serializable Snapshot Isolation). REPEATABLE READ предотвращает non-repeatable read, SERIALIZABLE еще и phantom read.

42. Что такое MVCC?

Multi-Version Concurrency Control. UPDATE не перезаписывает, а создаёт новую версию. У каждого tuple — xmin (transaction inserted), xmax (deleted/updated by). Readers видят snapshot на момент start (Repeatable Read) или на момент statement (Read Committed). Не блокируют writers.

43. Что такое VACUUM и почему нужен autovacuum?

MVCC оставляет dead tuples (старые версии). VACUUM освобождает место для повторного использования. Без autovacuum — таблица растёт, deteriorates. На крупных таблицах — кастомные настройки (scale_factor=0.05).

44. Какие типы индексов в Postgres?

B-tree (default, sorted/range), Hash, GIN (arrays, JSONB, FTS), GiST (geo, range, KNN), SP-GiST, BRIN (для отсортированных больших таблиц).

45. Когда использовать partial index?

Когда индексируем только подмножество строк (например, WHERE deleted_at IS NULL). Уменьшает размер индекса в десятки раз, ускоряет writes (меньше обновлять индекс).

46. EXPLAIN ANALYZE — на что смотреть?

Actual vs Estimated rows (если планер сильно ошибается → ANALYZE), Buffers (logical/physical reads), Loops × per-row cost. Seq Scan на больших таблицах — обычно проблема.

47. PgBouncer — какие режимы?

Session (после disconnect), Transaction (после commit/rollback — самый частый), Statement (после statement, ломает много фич). В transaction mode не работают prepared statements (до 1.21).

48. Replication: какие виды в Postgres?

Physical (streaming): репликация WAL, реплика — точная копия. Logical (10+): pub/sub на уровне таблиц/действий, можно фильтровать, разные версии.

49. Distributed transactions — какие подходы?

2PC (блокирующий, deprecated), Saga (хореография/orchestration с compensations), TCC (Try-Confirm-Cancel), Outbox для async events.

50. Что такое Saga? В чём отличие хореографии и оркестрации?

Saga = последовательность local transactions с компенсаторами. Хореография: сервисы подписаны на events друг друга, сами решают. Оркестрация: центральный orchestrator (Temporal) управляет потоком.

51. Outbox pattern — зачем нужен?

Чтобы атомарно записать в БД и отправить событие в Kafka. В одной транзакции пишем в outbox таблицу + бизнес-таблицу. Debezium (CDC) читает WAL и публикует в Kafka. Гарантирует consistency.

52. Идемпотентность — как обеспечить?

Idempotency-Key header → сохраняем результат первого запроса с этим key. Повторы возвращают тот же результат. Хранить с TTL (24h обычно).

53. Distributed lock — на чём реализовать?

Redis Redlock (с оговорками Kleppmann — clock skew, GC pauses), etcd lease (consensus-based, надёжнее), Postgres advisory locks (pg_advisory_lock), ZooKeeper recipes.

54. CAP-теорема — что это?

Consistency, Availability, Partition tolerance. В partition можно выбрать только 2. На практике partition неизбежен → выбираем между CP (etcd, MongoDB strong) и AP (Cassandra, DynamoDB). Современная формулировка PACELC добавляет latency vs consistency в normal mode.

55. Что такое quorum?

Большинство (N/2 + 1) узлов. Для writes — нужно подтверждение от quorum. Для reads — quorum или sloppy quorum. Гарантирует, что любой write пересечётся с любым read.

56. Raft consensus — расскажи на пальцах.

Лидер избирается голосованием (terms, votes). Лидер реплицирует log entries на followers. Когда majority подтвердила — commit. При partition: только partition с majority может продолжать writes. etcd, Consul, CockroachDB используют.

57. Kafka exactly-once — как работает?

Idempotent producer (PID + sequence number, broker отвергает дубликаты). Transactional producer (writes в несколько partitions + offset commits атомарно). Consumer с isolation.level=read_committed. Read-process-write exactly-once.

58. Kafka ISR — что это?

In-Sync Replicas. Реплики, актуальные относительно leader. acks=all ждёт подтверждения от всех ISR. Если реплика отстаёт > replica.lag.time.max.ms → выкидывается из ISR.

59. Чем отличается Kafka от RabbitMQ?

Kafka — лог, immutable, partitioned, для high-throughput streaming. RabbitMQ — message queue, push модель, гибкая роутинг (exchanges), для task distribution. Kafka — для events, RabbitMQ — для tasks.

60. NATS JetStream vs Kafka?

JetStream — lightweight, in-DC, простой operate, KV/Object store как бонус. Kafka — heavyweight, для huge throughput (>100k/sec), ecosystem (Connect, Streams), exactly-once.

61. DDD — что такое bounded context?

Граница, внутри которой term имеет определённое значение. Внутри BC — единая модель. Между BC — explicit translations (через ACL). Каждая команда обычно владеет одним BC.

62. CQRS — когда применять?

Когда read и write нагрузки сильно различаются или модели семантически расходятся. Read-side можно денормализовать, кэшировать, использовать другую БД. Не для каждой системы — overhead.

63. Event Sourcing — плюсы и минусы?

Плюсы: полный audit, time travel, новые проекции легко добавлять. Минусы: сложность, eventual consistency, схемы события эволюционируют (нужны upcasters), нет простых query — материализуй проекции.

64. Strangler Fig — как распиливать монолит?

Facade перед монолитом. Часть запросов начинает идти на новый сервис. Постепенно расширяем зону покрытия. Старый код удаляется когда покрытие 100%. Dual-write или CDC для данных.

65. Backward / Forward compatibility — как обеспечить?

Не удалять/менять fields (Proto: mark reserved). Только добавлять optional. Schema Registry для контроля. В JSON — игнорировать unknown fields на consumer.

66. API versioning — какие подходы?

URL (/v1/, /v2/) — самый простой. Header (API-Version: 2). Content-type. Для gRPC — пакеты с major-версией в имени.

67. Schema Registry — зачем?

Централизованное хранение схем (Avro/Proto/JSON Schema). Producer сериализует с schema ID, consumer десериализует по нему. Гарантирует compatibility (BACKWARD, FORWARD, FULL).

68. Что такое Anti-Corruption Layer?

Слой-адаптер на границе bounded context, переводит модели чужой системы в свою. Изолирует от изменений внешней системы.

69. Circuit Breaker — состояния и зачем?

Closed (нормально), Open (отказ — не пускаем запросы), Half-Open (пробуем). Защищает от cascading failures: когда downstream упал, не добиваем его retry’ями.

70. Bulkhead pattern?

Изолированные пулы ресурсов (threads, connections) для разных downstream. Если один умер — остальные работают.

71. Idempotency vs At-Least-Once vs Exactly-Once?

At-least-once: возможны дубликаты, надо идемпотентно обрабатывать. At-most-once: возможна потеря. Exactly-once: дорого, обычно эмулируется через at-least-once + идемпотентность.

72. SLI vs SLO vs SLA?

SLI — индикатор (% успешных). SLO — внутренний таргет (99.9%). SLA — контракт с клиентом, с финансовыми санкциями.

73. Что такое error budget?

100% - SLO = доля ошибок, которые мы можем позволить. Если бюджет исчерпан — стоп фич, только bug fixes. Если есть — можно деплоить новое.

74. Multi-tenant architecture — какие модели?

Pool (shared DB, tenant_id), Bridge (shared cluster, separate schemas), Silo (отдельные БД). Выбор зависит от requirements isolation и стоимости.

75. Спроектируй URL shortener. См. раздел 12.1. 76. Спроектируй rate limiter (distributed). См. раздел 12.4. 77. Спроектируй чат-приложение. См. раздел 12.7. 78. Спроектируй ленту (Twitter feed). См. раздел 12.8. 79. Спроектируй платёжную систему. См. раздел 12.9. 80. Спроектируй distributed cache. См. раздел 12.2. 81. Спроектируй поиск. См. раздел 12.6. 82. Спроектируй систему уведомлений. См. раздел 12.3. 83. Спроектируй распределённую очередь. См. раздел 12.5. 84. Спроектируй GPS-трекинг. См. раздел 12.10.

85. Сервис «течёт» по памяти — твои действия?

  1. Подтвердить через мониторинг (RSS растёт линейно). 2) Снять heap profile + diff через час. 3) Посмотреть top allocators. 4) Проверить goroutine leak. 5) Проверить connection pools (не закрытые соединения). 6) Если непонятно — runtime/trace.

86. P99 latency вырос в 5 раз — что делать?

  1. Что изменилось? (deploys, traffic, downstream). 2) Какой компонент медленный? (traces, db queries). 3) GC pause spike? (runtime/metrics). 4) CPU profile в моменте. 5) Локально воспроизвести при нагрузке.

87. Goroutine leak — как найти?

runtime.NumGoroutine() мониторинг. /debug/pprof/goroutine?debug=2 → стектрейсы. Ищем повторяющиеся — где блокировка. Часто — забытый context cancel, channel без receiver.

88. Service medvedi подаёт 502, downstream вернулся в норму. Почему?

Возможно: connection pool забит (idle conns живые, но broken). Решение: SetConnMaxLifetime. Или: circuit breaker не сбросился. Или: DNS кэш устарел.

89. БД медленно отвечает — куда смотреть?

  1. pg_stat_statements топ запросов. 2) pg_stat_activity — текущие. 3) Locks (pg_locks). 4) EXPLAIN ANALYZE на проблемных. 5) Bloat (pgstattuple). 6) IO: pg_stat_io. 7) Network: latency между app и db.

90. Goroutine deadlock в проде — как разобрать?

kill -QUIT pid или pprof goroutine. Смотрим: 1) на чём заблокированы. 2) кто держит lock. 3) есть ли circular wait. Профилактика: всегда defer Unlock(), single-direction lock acquisition.

91. OpenTelemetry — три pillar’а?

Traces (spans), Metrics (counters/histograms/gauges), Logs (структурированные с TraceID).

92. RED vs USE?

RED — для request-driven (Rate, Errors, Duration). USE — для resources (Utilization, Saturation, Errors). Дополняют.

93. Что такое burn rate alerting?

Алерт на скорость съедания error budget. Multi-window (1h fast burn, 6h medium, 24h slow) с разной чувствительностью. От Google SRE.

94. Sampling в tracing — head vs tail?

Head: решение sample/drop на старте запроса (всегда стабильно по trace). Tail: после завершения trace, по факту (можем seleсtивно сохранить slow/error).

95. Что хранить в логах? Что выкинуть?

Структурированно, JSON. TraceID, span ID, request ID. User ID (если можно по PII политике). Никогда: secrets, PCI-данные, JWT целиком. Уровень: INFO в проде, DEBUG отключён.

96. mTLS — как работает?

Обе стороны (client + server) предоставляют сертификаты. Каждая верифицирует другую по CA. SPIFFE/SPIRE — рекомендованный способ автоматизации в Kubernetes.

97. SBOM — что и зачем?

Software Bill of Materials — список всех dependencies артефакта. Generate’им через Syft, сканируем уязвимости через Grype. Часть supply chain security.

98. SQL injection в Go — как избежать?

Всегда db.Query("SELECT $1", arg), не fmt.Sprintf. Используем placeholders. ORM сами это делают. Никогда не interpolировать user input.

99. JWT — какие риски?

Незашифрованные claims (читаются всеми, кто перехватил). Алгоритм none (примут unsigned). Длительный TTL (нельзя отозвать). Решения: refresh tokens, revocation list, short TTL.

100. Расскажи о сложном code review — как ты его провёл?

Конкретный пример. Тон: empathy не gatekeeping. Фокус: корректность > архитектура > стиль. Spotted issues: race conditions, missing tests, security.

101. Как ты ментораешь джунов?

Регулярные 1:1, парное программирование, постановка задач с границами (что — чёткое, как — свобода). Code review как обучение (вопросы, не команды).

102. Расскажи про сложный технический conflict в команде.

Конкретный пример. Подход: ADR с trade-offs, обсуждение в группе, ответственное решение. Важно: разделять идею и носителя.

103. Как ты оцениваешь задачи?

Декомпоз до < 1 недели. Power of 2 (1, 2, 4, 8 дней). Buffer 30%. Three-point estimation (best/nominal/worst). Estimates ≠ commitments.

104. Расскажи о провале — что научило?

Конкретный пример (incident, deploy gone wrong, missed requirement). Lessons learned: process, tooling, communication. Постмортем без поиска виноватых.

105. Как ты документируешь архитектурные решения?

ADR в репозитории (markdown). Структура: Context, Decision, Consequences. Регулярные tech design reviews. C4 диаграммы для high-level.


  1. Distributed Cache (Redis-like) — consistent hashing, replication, eviction. Запушить в open-source с benchmarks vs Redis.

  2. Lightweight Service Mesh — gRPC proxy с mTLS, circuit breaker, retry, observability. Альтернатива Linkerd для маленьких проектов.

  3. Time Series Database — append-only, partitioning, compression (delta-of-delta, gorilla). Сравнить с VictoriaMetrics.

  4. Kubernetes Operator для какого-нибудь stateful application (например, PostgreSQL HA или Redis cluster).

  5. Distributed Job Scheduler — типа Celery/Sidekiq, но на Go. Redis backend, retry, priorities, scheduling.

  6. Real-time Analytics Pipeline — Kafka → Stream Processor → ClickHouse/Pinot → API.

  7. Blockchain From Scratch — PoW consensus, P2P networking, transaction validation. Хорошо для понимания распределённых систем.

  8. HTTP/2 Reverse Proxy — типа Caddy, но фокус на хайлоад. TLS termination, rate limit, cache.

  9. Custom DBMS — собственный embedded DB на Go: B+ tree, WAL, MVCC, SQL parser. Курс CMU 15-445 в помощь.

  10. Container Runtime — простой OCI-compatible runtime на Go. Чтобы понять как работают cgroups, namespaces.

Стратегия:

  1. Начни с good first issue / help wanted labels.
  2. Документация / тесты — easy wins.
  3. Bug fixes — следующий шаг.
  4. Features — только после погружения.

Топ проектов:

  • kubernetes/kubernetes — огромный, но есть кучка sigs (storage, scheduling, etc.).
  • etcd-io/etcd — core distributed system.
  • cockroachdb/cockroach — отличная codebase, можно учиться.
  • prometheus/prometheus — metrics ecosystem.
  • grafana/loki, grafana/tempo — observability.
  • istio/istio — service mesh.
  • vitessio/vitess (YouTube/PlanetScale) — MySQL шардинг.
  • influxdata/influxdb — TSDB.
  • hashicorp/consul, hashicorp/nomad.
  • kubedge-io/kubeedge, k3s-io/k3s — edge k8s.
  • dapr/dapr — sidecar для микросервисов.
  • temporalio/temporal, temporalio/sdk-go — workflow engine.

Российские проекты:

  • YDB-Platform/ydb-go-sdk — SDK для YDB.
  • VictoriaMetrics/VictoriaMetrics — TSDB, активно ищет contributors.
  • avito-tech/normalize и другие OSS от Авито.
  • ozontech/file.d — log forwarder.

Distributed Systems:

  • Designing Data-Intensive Applications (DDIA) — Martin Kleppmann. Second Edition в 2026 (с AI/vector data). Главная книга.
  • Database Internals — Alex Petrov.
  • Designing Distributed Systems — Brendan Burns.
  • Site Reliability Engineering + The SRE Workbook — Google. Бесплатно.
  • Building Microservices — Sam Newman. Второе издание 2021.

Go:

  • The Go Programming Language — Donovan & Kernighan. Классика.
  • 100 Go Mistakes and How to Avoid Them — Teiva Harsanyi. Обязательно.
  • Concurrency in Go — Katherine Cox-Buday.
  • Domain-Driven Design with Golang — Matthew Boyle.
  • Network Programming with Go — Adam Woodbeck.
  • Cloud Native Go — Matthew Titmus.
  • Let’s Go + Let’s Go Further — Alex Edwards (web apps на Go).

Architecture / Design:

  • Domain-Driven Design — Eric Evans (Big Blue Book, фундаментальная).
  • Implementing Domain-Driven Design — Vaughn Vernon (более practical).
  • Patterns of Enterprise Application Architecture — Martin Fowler.
  • Building Evolutionary Architectures — Neal Ford.

Performance / Production:

  • Systems Performance — Brendan Gregg. Главная книга по performance.
  • BPF Performance Tools — Brendan Gregg.
  • MIT 6.824 Distributed Systems — Robert Morris. На YouTube, лекции + лабы на Go (свой Raft, KV store). Топовый курс.
  • CMU 15-445 Database Systems — Andy Pavlo. Лучший курс по БД, бесплатно.
  • CMU 15-721 Advanced Database Systems — для глубокого погружения.
  • Stanford CS244B Distributed Systems — Diego Ongaro (автор Raft).
  • Coursera: Cloud Computing Specialization — University of Illinois.
  • edX: Reliable Distributed Systems — Cornell.

Российские курсы:

  • Yandex Practicum: Go-разработчик — middle курс.
  • Balun Courses (Олег Козырев): Микросервисы на Go, Алгоритмическое собеседование, Golang интервью.
  • Курс «Микросервисы на Go» от olezhek28 (Sebastian, ex-Avito): https://olezhek28.courses/microservices
  • HSE «Разработка микросервисов на Go».
  • GopherCon — главная Go-конференция. Talks на YouTube.
  • dotGo — европейская Go-конференция.
  • GoLab — Italian Go conference.
  • KubeCon + CloudNativeCon — для cloud native.
  • QCon — software architecture.
  • SREcon — для SRE perspective.
  • PGCon / PGConf.EU — для PostgreSQL.
  • GoWayFest (РФ/СНГ, до 2022).
  • Highload++ — российская hi-load конференция.

Companies:

  • Cloudflare blog — performance, networking.
  • Uber engineering — большие Go-кейсы (Jaeger, M3DB, Cadence).
  • Discord engineering — Go (раньше) → Rust.
  • DropBox tech blog.
  • Datadog engineering — много про Go.
  • Reddit r/programming top monthly.
  • Three Dots Labs — DDD/Clean Architecture на Go.
  • Ardan Labs blog — Bill Kennedy.
  • Dave Cheney’s blog.
  • VictoriaMetrics blog — performance в Go.

Российские:

  • Хабр Хабы: Go, Программирование, Системное администрирование, Базы данных.
  • vc.ru — tech статьи.
  • Авито Tech, Озон Tech, Yandex — engineering blogs.
  • Антон Желтов, Олег Козырев, Никита Соболев — Telegram-каналы.
  • Go Time (Changelog) — еженедельный про Go.
  • GoLab Podcast.
  • The Cloudcast — cloud architecture.
  • Software Engineering Daily.
  • Software Engineering Radio.
  • Talking Kotlin (даже для Go relevant — JVM ideas).

Российские:

  • Подлодка — Andrey Boyar.
  • Хекслет подкаст.
  • Rob Pike talks — основоположник Go.
  • Russ Cox talks — current Go lead.
  • GoSF — recorded meetups.
  • Brendan Gregg — performance.
  • Hussein Nasser — backend engineering.
  • ByteByteGo — system design.

Реалистичный план по неделям. Предполагается 10-15 часов в неделю на учёбу + текущая работа в качестве Middle 2.

  • Неделя 1-2: «100 Go Mistakes» — прочитать целиком, выписать неочевидное. Решить упражнения.
  • Неделя 3: GMP scheduler deep dive. Прочитать GoTech blogs, source code runtime/proc.go. Написать пост в блог/Habr.
  • Неделя 4: GC pacer math. Прочитать GC pacer proposal. Эксперименты с GOGC, GOMEMLIMIT локально.
  • Неделя 5: Channel internals — прочитать runtime/chan.go. Реализовать аналог канала с нуля.
  • Неделя 6: Memory model. Race detector. Atomic primitives. Реализовать lock-free MPSC.
  • Неделя 7: sync.Mutex, sync.Map internals. Реализовать собственный RWMutex.
  • Неделя 8: Profiling deep dive. pprof, flamegraphs, runtime/trace. Профилировать свой pet-проект.
  • Неделя 9: Postgres internals — MVCC, WAL, planner.
  • Неделя 10: Индексы — B-tree, GIN, GiST, BRIN. Эксперименты с EXPLAIN.
  • Неделя 11: HA — Patroni setup локально. PgBouncer modes.
  • Неделя 12: ClickHouse, MergeTree. YDB или CockroachDB — что-то одно для эксперимента.
  • Неделя 13: DDIA главы 5-7 (Replication, Partitioning, Transactions).
  • Неделя 14: DDIA главы 8-9 (Trouble, Consistency, Consensus). Прочитать Raft paper.
  • Неделя 15: Lab MIT 6.824 — Raft на Go. Это большая инвестиция времени, но крайне ценная.
  • Неделя 16: Прочитать про Kafka в DDIA + Confluent docs. Sagas.
  • Неделя 17: Sam Newman «Building Microservices». DDD strategic.
  • Неделя 18: Pattern catalog — Saga, Outbox, Inbox, Circuit Breaker, Bulkhead, Retry. Реализовать в pet-проекте.
  • Неделя 19: gRPC deep dive. Streaming, interceptors. Schema evolution.
  • Неделя 20: Kafka deep dive — exactly-once, transactions. Реализовать на franz-go.
  • Неделя 21-22: ByteByteGo / Alex Xu System Design Vol 1 — прорешать все 15 задач письменно.
  • Неделя 23: System Design Vol 2 — ML, search, etc.
  • Неделя 24: Mock system design интервью (с другом / на YouTube).
  • Неделя 25: Kubernetes basics — если уже знаешь, пропустить. Иначе KodeKloud / Kelsey Hightower «Kubernetes the Hard Way».
  • Неделя 26: Operators — Kubebuilder tutorial. Написать простой operator.
  • Неделя 27: Service Mesh basics — Istio или Linkerd hands-on.
  • Неделя 28: Helm, Kustomize, ArgoCD.
  • Неделя 29: OpenTelemetry — instrument свой pet-проект fully (traces + metrics + logs).
  • Неделя 30: Prometheus, Grafana — set up dashboards, alerts.
  • Неделя 31: SRE Workbook — implement SLOs для pet-проекта.
  • Неделя 32: Chaos Engineering — Chaos Mesh на k8s, эксперименты.
  • Неделя 33: mTLS, SPIFFE/SPIRE basics.
  • Неделя 34: Vault — secrets management. SBOM generation, Sigstore.
  • Неделя 35: PGO в действии. Optimize hot paths в реальном коде.
  • Неделя 36: Brendan Gregg «Systems Performance» — главы 1-5.
  • Неделя 37-39: Top 100 LeetCode questions for Go (см. NeetCode 150). По 5-10 в день, фокус на патернах.
  • Неделя 40: Конкурентные задачи — Worker Pool, Rate Limiter, LRU, etc.
  • Неделя 41: Прокачать GitHub профиль — README, pinned projects.
  • Неделя 42: Написать 2-3 технических статьи в Habr / Medium про что узнал.
  • Неделя 43: Mock system design + Mock coding (interviewing.io, pramp).
  • Неделя 44: Доработать pet-проект — добавить observability, тесты, README.
  • Неделя 45-48: Apply в Яндекс / Авито / Озон / Тинькофф / Сбер. Параллельно — мок собесы.

После каждого собеседования (даже неудачного): записать вопросы, разобрать пробелы.


АспектMiddle 3 / Middle+Senior
Объём знанийГлубоко знает Go, БД, k8s, концепции distributed systemsКругозор шире: ОС, networking, security, multiple languages
СамостоятельностьДелает фичи end-to-end, но в рамках уже спроектированной системыПроектирует системы с нуля, decompose требования в технические задачи
АрхитектураПонимает trade-offs, предлагает корректные решения для проблемДрайвит архитектурные решения уровня системы, влияет на roadmap
Code reviewДелает качественные ревью на уровне модуляДелает кросс-командные ревью, видит system-level проблемы
МенторингМенторит джуновМенторит middles, формирует engineering culture
Кросс-командаРаботает в основном внутри командыРаботает с другими командами, делает технические переговоры
Tech debtВидит и фикситПринимает решения о приоритетах tech debt vs features
ХаосЭффективно работает с понятными требованиямиЭффективно работает с ambiguity, выдумывает направление
ВлияниеНа командуНа org / company-wide
ПостмортемыУчаствует, предлагает action itemsЛидирует расследование, обеспечивает follow-through
Tech radarЗнает текущий стек глубокоПостоянно сканирует индустрию, оценивает новые tech для adoption
HiringУчаствует в технических секцияхПринимает решения о найме, прокачивает интервью-процесс
Зона ответственностиService / модульPlatform / domain

Что нужно делать на Middle 3, чтобы расти в Senior

Заголовок раздела «Что нужно делать на Middle 3, чтобы расти в Senior»
  1. Брать неопределённые задачи — где нет чёткого ТЗ, надо самому декомпозировать.
  2. Писать ADR / RFC регулярно, даже для своих решений.
  3. Менторить — это ускоряет твой рост.
  4. Участвовать в hiring — научишься оценивать.
  5. Лидировать инцидент — это меняет уровень понимания production.
  6. Влиять за пределами команды — talks внутри компании, OSS contributions.
  7. Углубиться в один домен — стать “the” эксперт по чему-то (например, distributed systems / db internals / observability).
  8. Развивать коммуникацию — Senior 50% времени общается / пишет, а не кодит.

Минимум, который нужно знать на Middle 3 интервью

Заголовок раздела «Минимум, который нужно знать на Middle 3 интервью»
  1. Внутренности Go: GMP, GC, escape analysis, map/slice/channel internals, sync primitives — на уровне «могу нарисовать на доске».
  2. Concurrency: реализовать worker pool, fan-in/out, rate limiter, LRU cache с нуля без подсказок. Понимать context, errgroup, singleflight.
  3. БД: PostgreSQL deep (MVCC, индексы, EXPLAIN), pgbouncer modes, isolation levels, ClickHouse basics.
  4. Очереди: Kafka deep (exactly-once, transactions, consumer groups). NATS JetStream basics.
  5. Distributed Systems: CAP, Raft, Saga, Outbox, Idempotency, distributed locks (с осторожностью).
  6. Архитектура: DDD bounded contexts, микросервисные паттерны, trade-off мышление.
  7. System Design: уверенно проектировать 10-15 классических систем.
  8. Production skills: pprof, OpenTelemetry, SLO/SLI, postmortems, debugging.
  9. Kubernetes: пользователь-плюс. Operators — бонус.
  10. Soft skills: code review, ADR, менторство джунов.

Чего ждут конкретные компании (наблюдения)

Заголовок раздела «Чего ждут конкретные компании (наблюдения)»
  • Яндекс: внутренности Go, алгоритмы (на уровне medium-hard LeetCode), system design, продуктовое мышление. Несколько секций.
  • Авито: ОЧЕНЬ много concurrency (worker pool with graceful shutdown — классика). Архитектура. Practical Go patterns.
  • Озон / Ozon Tech: алгоритмы, БД, system design. Знание Kafka — must.
  • Тинькофф / Т-Банк: глубокий Go, БД, надёжность (платёжная вертикаль). Архитектурная секция для middle+.
  • ВК: Tarantool, in-memory, performance. Свои нюансы legacy.
  • Сбер: enterprise-style — больше про процессы, security, integrations.

Источники, которые упоминались (для углубления)

Заголовок раздела «Источники, которые упоминались (для углубления)»

Удачи на собеседованиях!

Если есть отклик — допиши свой опыт пройденных собесов: какие вопросы задавали, какие мест были unexpectedly сложными. Это поможет docs обновлять под реальный рынок.