Middle Go-разработчик в команду рекламных технологий: биллинг, финансы, ОРД и высоконагруженный бэкенд на стыке рекламы и монетизации
Фишка: Инженерная культура с Tech Design Review и код-ревью: решения обсуждаются открыто, идея важнее статуса. Команда ADV работает с нагрузкой 750K RPM на выдачу цен и миллиардами событий в день — ждут понимания trade-off между надёжностью, консистентностью и масштабом.
| Этап | Длительность | Что проверяют |
|---|---|---|
| HR / скрининг рекрутера | 30–45 мин | Мотивация, релевантный опыт на Go, ожидания по формату работы (удалёнка/гибрид), зарплатные ожидания, готовность к дежурствам второй линии |
| Техническое интервью | 60–90 мин | Go: горутины, каналы, sync-примитивы, контекст, обработка ошибок. SQL: транзакции, индексы, JOIN. Разбор кода или live-coding на слайсы, указатели, конкурентность. Вопросы по прошлым проектам |
| System Design | 60 мин | Проектирование сервисов: биллинг, event-driven архитектура, идемпотентность платежей, согласованность данных, интеграции между микросервисами. Учитывают Tech Design Review культуру Авито |
| Финал с командой | 45–60 мин | Fit в команду Авито Реклама, понимание домена (реклама, аукцион, ОРД), продуктовое мышление, ценности из Манифеста Авито, вопросы кандидата |
Обязательный минимум
Плюсом будет
Что такое горутина?
Легковесный поток (~2 KB стека), планируется Go scheduler (G-M-P). Не OS-thread: тысячи goroutines на несколько потоков. Запуск: go func(){}. Отличие от thread — кооперативная мультиплексированная модель.
Какие есть типы каналов в Go?
Направление: chan T, <-chan T (read-only), chan<- T (write-only). Буферизация: unbuffered (синхронный handoff) vs buffered (cap>0, неблокирующая запись пока буфер не полон). nil-канал блокирует навсегда.
В чём разница между буферизованным и небуферизованным каналом?
Unbuffered: отправитель блокируется до receive. Buffered: отправка не блокирует, пока len < cap. Закрытый канал: чтение возвращает zero value + ok=false; запись — panic.
Что такое select?
Мультиплексор каналов: ждёт первый готовый case. default — неблокирующий режим. Часто используют с context.Done() для отмены.
Что такое WaitGroup?
sync.WaitGroup: Add(n), Done(), Wait(). Счётчик активных goroutines. Нельзя Add после Wait. Передавать по указателю в goroutines.
Что такое Mutex?
sync.Mutex — эксклюзивная блокировка. Lock()/Unlock(). RWMutex для read-heavy: RLock/RUnlock + Lock/Unlock. Всегда defer mu.Unlock(). Deadlock при повторном Lock одной goroutine.
Можно ли читать данные из закрытого канала?
Да: получаешь оставшиеся значения, затем zero value и ok=false. Запись в закрытый канал — panic. Закрывать должен отправитель, не получатель.
Что происходит при записи в закрытый канал?
Panic. Закрывать канал может только отправитель. Получатели читают остаток и получают ok=false.
Как работает обработка ошибок в Go?
errors — values, не exceptions. return fmt.Errorf("...: %w", err). errors.Is/As для unwrap. panic только для programmer errors. В goroutines — recover или errgroup.
Как реализовано ООП в Go?
Композиция вместо наследования. Интерфейсы неявные (structural typing). Embedding для делегирования. Инкапсуляция через lowercase/Uppercase. Нет классов — struct + methods.
Можно ли передать функцию в качестве параметра другой функции?
Да, функции — first-class values. Используются для callbacks, middleware, strategy pattern. type Handler func(w, r).
Какая алгоритмическая сложность доступа по ключу в Map?
O(1) амортизированная. Хеш-таблица с bucket overflow. Не thread-safe — нужен sync.Map или Mutex. Итерация — случайный порядок.
Ловушка: Классическая ловушка: указатель на переменную цикла range — все указатели указывают на одну переменную. Решение: v := v или индекс second[i] = &first[i].
Совет: На техническом часто дают «что выведет программа» на слайсы, defer, указатели и каналы — прорешай 5–7 задач из internal базы перед собесом.
Фишка: В Авито Go — основной язык бэкенда ADV-кластера. Ждут уверенного владения конкурентностью — команда работает с real-time аукционом и биллингом.
Как append влияет на слайс при передаче в функцию?
Слайс — descriptor (ptr, len, cap). Передаётся по значению. append может переаллоцировать — изменения видны снаружи только если cap позволяет in-place. mutate по индексу — виден снаружи.
Как устроена строка в Go?
Immutable byte sequence. Header: pointer + length. Конкатенация создаёт новую строку. range по строке — runes (UTF-8). string ↔ []byte через conversion (копирование).
Ловушка: defer захватывает аргументы в момент вызова defer, не в момент выполнения — для defer printInt(i) значение фиксируется сразу.
Ловушка: Два слайса с общей ёмкостью (append от одного base) — изменение одного может затронуть другой.
Для чего нужна транзакция?
ACID: Atomicity, Consistency, Isolation, Durability. Группирует операции в единую единицу. BEGIN/COMMIT/ROLLBACK. В биллинге — списание + запись в ledger атомарно.
Зачем нужны индексы в БД?
Ускоряют SELECT/WHERE/JOIN за счёт B-tree (или GIN/GiST). Минус: замедляют INSERT/UPDATE, занимают место. EXPLAIN ANALYZE для проверки.
Какие знаешь виды индексов?
B-tree (default), Hash, GIN (JSONB, full-text), GiST (geo), BRIN (большие таблицы). Partial index, composite index (a,b). UNIQUE constraint = unique index.
В чём разница между WHERE и HAVING?
WHERE фильтрует строки до GROUP BY. HAVING фильтрует группы после агрегации. HAVING может использовать агрегатные функции.
Что такое триггер?
PL/pgSQL функция, срабатывающая на INSERT/UPDATE/DELETE. BEFORE/AFTER. Используют для audit log, denormalization. Минус: скрытая логика, сложность отладки.
Зачем нужен триггер?
Audit trail, автоматическое обновление derived tables, валидация на уровне БД. В финансовых системах — логирование изменений баланса.
Какие знаешь агрегатные функции?
COUNT, SUM, AVG, MIN, MAX, ARRAY_AGG, STRING_AGG. С GROUP BY или как window functions (OVER).
Фишка: Вакансия явно требует PostgreSQL + ClickHouse: PG для OLTP (биллинг, транзакции), ClickHouse для аналитики и статистики рекламных кампаний.
Ловушка: Read-modify-write без транзакции и блокировки = race condition. На собесе часто дают задачу «безопасное списание с баланса» — нужен SELECT FOR UPDATE или атомарный UPDATE ... WHERE balance >= amount.
Что такое gRPC?
RPC на HTTP/2 + Protocol Buffers. Unary, streaming (server/client/bidi). Быстрее REST JSON, строгие контракты (.proto). Используется для inter-service communication в Avito.
Какие плюсы и минусы микросервиса?
Плюсы: независимый деплой, масштабирование, изоляция отказов. Минусы: distributed complexity, latency, eventual consistency, observability overhead, дублирование данных.
В чём разница между TCP и UDP?
TCP: connection-oriented, guaranteed delivery, ordering, flow control. UDP: connectionless, no guarantee, lower latency. TCP — HTTP/gRPC, UDP — DNS, streaming, gaming.
Что такое TCP протокол?
3-way handshake (SYN, SYN-ACK, ACK). Reliable byte stream. Congestion control, retransmission. Порт + IP = socket.
В чём разница между HTTP и HTTPS?
HTTPS = HTTP + TLS. Шифрование, аутентификация сервера (сертификат). Порт 443 vs 80. TLS 1.2/1.3.
В чём разница между HTTP/1.1 и HTTP/2?
HTTP/2: multiplexing (несколько streams на одном TCP), header compression (HPACK), binary framing, server push. Меньше head-of-line blocking.
Что такое Docker?
Контейнеризация: изоляция процессов через namespaces + cgroups. Image = layers. Dockerfile → build → run. Легче VM, общее ядро хоста.
В чём разница между контейнеризацией и виртуализацией?
VM: полная ОС + hypervisor, тяжелее. Container: shared kernel, изоляция процессов, быстрый старт, меньше overhead.
Какие знаешь типы метрик?
Counter (monotonically increasing), Gauge (up/down), Histogram (distribution + buckets), Summary (quantiles). Prometheus/VictoriaMetrics в стеке Avito ADV.
Фишка: ADV-кластер: 750K RPM на выдачу цен, 99.9%+ availability. Ждут понимания, как проектировать сервисы под такую нагрузку.
Совет: Изучи manifesto.avito.com — на финале могут спросить про ценности и культуру открытого обсуждения.
Как обеспечить идемпотентность платёжной операции?
Idempotency key от клиента. Unique constraint в БД. Status machine (pending → completed). Retry-safe API design.
Как проектировать сервис биллинга?
Ledger pattern (double-entry). Event sourcing для аудита. Разделение authorization/capture. Async reconciliation. Saga для распределённых транзакций.
Что такое ОРД в контексте рекламы?
Operator of Advertising Data — оператор рекламных данных. Регуляторная отчётность по маркировке рекламы в РФ. Интеграция с ERIR, передача данных о кампаниях и договорах.
Фишка: Команда развивает постоплату, промокоды, сторнирования, возвраты, интеграцию с Adriver — готовь примеры работы с финансовыми транзакциями и edge cases.
Совет: На финале полезно показать интерес к домену: аукцион, ML-ранжирование, Public API для рекламодателей, биллинг postpaid.
Безопасное списание с баланса пользователя
Есть таблица user с полем balance и функция withdrawBalance(user_id, amount). Код: SELECT balance → проверка → UPDATE. Определи проблемы и предложи исправление для продакшена.
Проблемы: race condition (два параллельных списания), нет транзакции, TOCTOU. Решение: ```sql BEGIN; UPDATE user SET balance = balance - $2 WHERE id = $1 AND balance >= $2 RETURNING balance; -- если 0 rows — недостаточно средств COMMIT; ``` Или SELECT ... FOR UPDATE внутри транзакции. В Go: pgx transaction + context timeout. Добавить idempotency key для retry.
Сложность: O(1) при индексе по id
Поведение append и передачи слайса
slice := make([]int64, 0, 4); append 1,2; append1(slice,3); mutate1(slice,3,4). Что выведет fmt.Println после каждого шага?
После append 1,2: [1 2]. После append1(slice,3): [1 2] — append1 не меняет outer slice (переприсваивание локальной копии descriptor). После mutate1(slice,3,4): panic index out of range [3] — len=2, индекс 3 недоступен. Если бы mutate1(slice,1,4): [1 4] — in-place изменение видно снаружи.
Сложность: O(1) per append amortized
Указатели в цикле range
first := []int{10,20,30,40}; second := make([]*int, len(first)); for i,v := range first { second[i] = &v }; fmt.Println(*second[0], *second[1])
Выведет: 40 40. Переменная v переиспользуется на каждой итерации — все указатели указывают на одну ячейку памяти с финальным значением 40. Исправление: `v := v` внутри цикла или `second[i] = &first[i]`.
Сложность: O(n)
Чтение из канала с тайм-аутом
Гoroutine пишет 0..4 в канал с sleep 1s. Main loop: select case v:=<-ch / case <-time.After(3s): timeout. Что выведет?
0, 1, timeout, timeout, timeout... break в select выходит только из select, не из for — бесконечный timeout после 3 секунды. Исправление: return/label break после timeout, или context.WithTimeout, или использовать timer.Stop().
Сложность: O(n) сообщений
Каркас ответа
3 дня
7 дней
14 дней
| Блок | Готов, если... |
|---|---|
| Go: конкурентность | можешь объяснить G-M-P, разницу buffered/unbuffered channel, написать worker pool с context cancellation |
| Go: слайсы и указатели | можешь предсказать вывод задач на append, range+pointer, defer без подсказок |
| PostgreSQL | можешь написать атомарное списание с баланса и объяснить, зачем SELECT FOR UPDATE |
| Микросервисы | можешь описать gRPC vs REST, saga/outbox для биллинга, плюсы/минусы микросервисов |
| System Design | можешь за 45 мин спроектировать billing service с idempotency, audit log и async stats pipeline |
| Домен Авито Реклама | понимаешь, чем занимается команда: биллинг, ОРД, аукцион, postpaid, и можешь задать 3 осмысленных вопроса |
| Behavioral | есть 2 STAR-истории про сложную задачу и работу в команде, рассказ об опыте укладывается в 5 минут |
В день собеседования