Программа буткемпа Node.js по неделям
20 учебных недель, которые поток проходит за 12 календарных: обычно по две за календарную неделю. Все недели строят один сервис, backend проката инструментов: от импорта каталога и HTTP API до оплаты без двойных списаний, очередей, Kafka, аналитики на ClickHouse, нагрузочных замеров и защиты проекта.
Ниже по каждой неделе: какую задачу решает сервис проката, чему вы научитесь и какие вопросы по теме задают на собеседованиях, всего 164 вопроса. Текст взят из методичек курса. Ответы на вопросы разбираются в методичках и на занятиях. Как устроены встречи, сдача работ и защита, описано на странице «Как проходит обучение».
Календарь потока
| Даты | Недели | Что изучаем на проекте |
|---|---|---|
| с 6 февраля | 0 | Подготовка до старта: рабочее место, Git, Docker, основы SQL |
| 13-14 февраля | 1-2 | Как устроен Node.js: цикл событий, потоки, ошибки. HTTP API на Fastify |
| 21 и 23 февраля | 3-4 | PostgreSQL: модель данных, транзакции, миграции. NestJS |
| 27-28 февраля | 5-6 | Вход и права доступа. Оплата без двойных списаний |
| 6-7 марта | 7-8 | Redis и кэш. RabbitMQ и Outbox: события без потерь |
| 13-14 марта | 9-10 | Сбои внешних сервисов. Выделение отдельного сервиса |
| 20-21 марта | 11-12 | PostgreSQL глубже: поиск, JSONB, планы запросов. Тарифы и подписки для SaaS |
| 27-28 марта | Запасная неделя без новой темы: долги, исправления по ревью, консультации | |
| 3-4 апреля | 13-14 | Kafka: журнал событий, сбои и повторное чтение |
| 10-11 апреля | 15-16 | ClickHouse: событийные таблицы, корректные отчёты и сверка |
| 17-18 апреля | 17 | Нагрузка и system design: измерения, метрики, логи и трассировка запросов |
| 24-25 апреля | 18-19 | Резервные копии и восстановление, безопасность релиза, итоговая доработка |
| 5 и 6 мая | 20 | Защита проекта: встречи вечером после майских праздников, защиты по записи до 14 мая |
Неделя 0. Подготовка
Терминал, Git, Docker и основы SQL на данных проката
Задача недели
Прежде чем писать сервис проката, нужно рабочее место, на котором он запускается одинаково у всех. Ученик, у которого не поднимается база, на третьей неделе потеряет вечер на настройку вместо работы над задачей.
Чему научитесь
- Проверить компьютер скриптом курса и исправить типичные проблемы с окружением.
- Работать в терминале: задавать переменные окружения, читать коды выхода, соединять команды в конвейер и останавливать процессы сигналами.
- Делать изменение в отдельной ветке, открывать merge request, отвечать на ревью новыми коммитами и доводить до зелёного цвета проверки проекта: типы, линтер и тесты.
- Запускать сервисы в Docker Compose, смотреть их логи и понимать, где хранятся данные.
- Писать простые SQL-запросы: выбирать, соединять и группировать строки, работать с NULL и транзакциями.
Вопросы собеседования
- Что происходит при
git mergeи приgit rebase? Когда что выбирать? - Как вы передаёте конфигурацию и секреты в сервис, запущенный в контейнере?
- Чем отличаются
docker stopиdocker kill? - Что такое первичный и внешний ключ? Зачем ограничения в базе, если есть проверки в коде?
- Как найти записи без пары в связанной таблице?
- Что гарантирует транзакция и что произойдёт при
ROLLBACK?
Неделя 1. Среда выполнения Node.js
Импорт каталога проката: потоки, проверка данных и понятные сообщения об ошибках
Задача недели
Пункт проката инструментов переезжает со старой учётной системы на новый сервис. Старая система умеет только выгружать данные в файл JSONL, где каждая строка содержит одну позицию каталога.
Чему научитесь
- Объяснить, почему долгая синхронная работа задерживает все запросы процесса, и измерить эту задержку.
- Объяснить, в каком порядке выполняются синхронный код, микрозадачи, таймеры и
setImmediate, и на какой порядок полагаться нельзя. - Обработать файл любого размера, не занимая лишней памяти: читать его построчно и писать с учётом обратного давления.
- Считать внешние данные
unknown, проверять их схемой во время выполнения и писать такие сообщения об ошибках, чтобы сотрудник сразу нашёл неверную строку. - Хранить деньги целым числом копеек и отклонять дробные суммы.
- Заменять файл атомарно, по коду выхода различать ошибку в данных и сбой чтения или записи и закреплять каждый такой случай тестом на
node:test. - Запустить HTTP-сервер на
node:httpи останавливать его по SIGTERM так, чтобы начатые запросы успели завершиться. - Пользоваться ИИ-ассистентом так, чтобы решение оставалось вашим: проверять его предложения запуском и тестами и писать в MR, где он заметно помог.
Вопросы собеседования
- Опишите фазы цикла событий Node.js. В какой фазе выполняются колбэки
setImmediate, а где колбэки промисов? - Как найти в работающем сервисе код, который блокирует цикл событий?
- Что такое обратное давление в потоках и как оно проявляется в продакшене?
- Чем
pipelineлучше цепочки.pipe()? - Как безопасно заменить файл, который читают другие процессы? Какие у этого способа границы?
- Как хранить денежные суммы в JavaScript и почему не в
numberс дробной частью? - Что должно происходить в Node.js-сервисе между получением SIGTERM и выходом?
- Чем проверка живости отличается от проверки готовности?
Неделя 2. HTTP API на Fastify
Каталог проката по HTTP: контракт, ошибки, OpenAPI и запуск в Docker
Задача недели
Каталог проката импортирован и проверен. Теперь его должны увидеть клиенты: команда фронтенда делает витрину, где клиент листает инструменты по 20 штук, выбирает категорию «Дрели» и открывает карточку с характеристиками и залогом.
Чему научитесь
- Описать HTTP-контракт ресурса: адреса, параметры, коды ответов, формат денег и ошибок. Отличать ошибку клиента от сбоя сервера.
- Проверять параметры пути и строки запроса схемой zod на входе в сервис так, чтобы схема не меняла значения молча.
- Отдавать список постранично, с ограниченным размером страницы и одним и тем же порядком при каждом запросе.
- Сообщать об ошибках в формате RFC 9457 и по идентификатору запроса находить в логе запись, которая объясняет сбой.
- Получать документ OpenAPI из тех же схем, которыми проверяется запрос, и тестом проверять, что настоящие ответы ему соответствуют.
- Тестировать HTTP-слой без сети через
injectи на запущенном процессе. - Собрать Docker-образ, который работает не от root, не содержит секретов и правильно останавливается по SIGTERM, и запускать сервис вместе с подготовкой данных одной командой Compose.
Вопросы собеседования
- Чем отличаются безопасные и идемпотентные методы HTTP? Приведите примеры.
- Как вы спроектируете постраничную выдачу для списка из миллиона записей?
- Как сообщать об ошибках в REST API, чтобы клиент мог их обработать?
- Как связать ответ об ошибке, который видит пользователь, с записью в логе?
- Как поддерживать документацию API в соответствии с кодом?
- Что происходит с процессом Node.js в контейнере после
docker stop? - Как уменьшить время пересборки образа Docker для Node.js-сервиса?
- Чем проверка живости отличается от проверки готовности, и что будет, если их перепутать?
Неделя 3. PostgreSQL: каталог и брони
Как не выдать одну дрель двум клиентам: транзакции, блокировки, ограничения и миграции
Задача недели
В пункте две одинаковые дрели Bosch GSB 13 RE с инвентарными номерами DR-001 и DR-002. Клиент выбирает даты на витрине и бронирует конкретный свободный экземпляр. Пока он оплачивает, дрель держится за ним 15 минут. Это временный резерв. Если клиент не оплатил, резерв истекает, и дрель снова свободна. Оплата появится в следующих методичках: сейчас бронь создаётся резервом, а подтверждённые брони приходят из начальных данных.
Чему научитесь
- Спроектировать таблицы каталога, экземпляров, клиентов и броней так, чтобы бронь одной организации не могла сослаться на экземпляр или клиента другой, даже если в коде ошибка.
- Менять схему миграциями, которые применяются к пустой базе, ничего не ломают при повторном запуске и не правятся после применения.
- Передавать значения в SQL только параметрами и получать из драйвера деньги, даты и счётчики без искажений.
- Объяснить, почему проверка «сначала SELECT, потом INSERT» пропускает двойную бронь, и запретить двойную бронь ограничением исключения и блокировкой строки.
- Различать три ответа: экземпляр занят (409), ссылка на чужие данные (422) и сбой базы (5xx).
- Настроить пул соединений с пределами на ожидание и на время запроса, возвращать соединение в пул при любом исходе и закрывать пул при остановке сервиса.
- Загружать начальные данные одной транзакцией так, чтобы повторный запуск ничего не менял.
- Проверять гонку настоящими одновременными запросами через разные соединения и два процесса сервиса, а отказ базы проверять, приостановив её контейнер командой
docker pause.
Вопросы собеседования
- Какие уровни изоляции есть в PostgreSQL, какой используется по умолчанию и какие аномалии он допускает?
- Как исключить двойное бронирование номера в гостинице? Сравните несколько решений.
- Что такое SQL-инъекция и как от неё защищаются? Можно ли передать параметром имя таблицы?
- Как работает пул соединений и как выбрать его размер?
- Чем пессимистическая блокировка отличается от оптимистической?
- Что такое взаимоблокировка, как PostgreSQL её обнаруживает и как её избежать?
- Как выкатывать миграции схемы без простоя сервиса?
- Как хранить в базе деньги, даты и моменты времени?
Неделя 4. NestJS: модули и жизненный цикл
Переносим сервис проката на NestJS: модули, внедрение зависимостей, ошибки и остановка
Задача недели
За три недели у сервиса появились каталог, брони, база и проверки здоровья. Точка входа собирает всё руками: читает конфигурацию, создаёт пул, источник каталога, сервис броней и передаёт их в функцию сборки приложения. На следующей неделе добавятся пользователи и сессии, потом оплата, кэш и очереди. Каждая новая зависимость меняет сигнатуры по цепочке. Например, чтобы маршрут брони получил проверку прав, её надо протащить через main.ts и функцию сборки.
Чему научитесь
- Разбить сервис на модули NestJS так, чтобы зависимости шли в одну сторону, и объяснить, что модуль импортирует, предоставляет и экспортирует.
- Внедрять зависимости по классу и по токену и объяснить через метаданные декораторов, почему интерфейс без токена внедрить нельзя.
- Перенести маршруты Fastify в контроллеры NestJS так, чтобы контракт не изменился: та же проверка zod, ошибки RFC 9457 и документ OpenAPI.
- Управлять запуском и остановкой приложения: проверять конфигурацию до сборки модулей, вызывать хуки остановки в нужном порядке, закрывать пул соединений последним и выходить с кодом 0 по SIGTERM.
- Тестировать модули, подменяя провайдеры, и прогонять контрактные тесты прошлых недель на новой сборке.
- Собирать TypeScript с декораторами в
distи делать образ в две стадии, в котором нет компилятора и пакетов типов.
Вопросы собеседования
- Что такое внедрение зависимостей, и какую задачу решает контейнер?
- Как в NestJS внедрить реализацию интерфейса и почему нельзя просто указать тип параметра?
- Как устроена обработка ошибок в NestJS, и как привести все ошибки к одному формату?
- Опишите жизненный цикл приложения NestJS при остановке. Где освобождать ресурсы?
- Как перенести существующий HTTP API на другой фреймворк и доказать, что контракт не изменился?
- Как вы найдёте и устраните циклическую зависимость между модулями?
- Зачем многостадийная сборка образа для сервиса на TypeScript?
- Что должно быть готово в сервисе, прежде чем его можно выкладывать на платформу с автоматическими перезапусками?
Неделя 5. Пользователи и изоляция организаций
Вход по паролю, сессии и роли, и как не показать пользователю данные чужой организации
Задача недели
В организации demo работают три человека. Ольга, владелица, меняет цены, нанимает сотрудников и увольняет их. Сергей, сотрудник пункта, оформляет брони по телефону на клиентов, у которых нет учётной записи, и видит все брони пункта. Анна, клиентка, сама бронирует дрель из дома и видит только свои брони. По соседству работает прокат sever со своим владельцем Глебом.
Чему научитесь
- Хранить пароли так, чтобы утёкшая таблица пользователей их не раскрыла: медленная функция хеширования с солью, параметры в строке хеша, сравнение за постоянное время.
- Отвечать на вход с неизвестным адресом и с неверным паролем одинаково и примерно за одно и то же время.
- Выдавать, проверять и отзывать сессии, которые хранятся в базе: токен случайный, в базе лежит только его хеш, срок считается по часам базы, и одна сессия работает на нескольких экземплярах сервиса.
- Настроить cookie сессии (
HttpOnly,Secure,SameSite,Path,Max-Age, префикс__Host-), защитить изменяющие запросы CSRF-токеном и объяснить, почему CORS не заменяет проверку прав. - Описать права ролей
owner,staffиcustomerтаблицей и проверять их в guard'ах NestJS, читая членство в организации из базы при каждом запросе. - Определять организацию только по проверенному членству и не отдавать чужие данные, если в запросе подменили
bookingId,unitIdилиcustomerId. - Выбирать между ответами 401, 403 и 404 и объяснять, что каждый из них скрывает.
- Проверять доступ тестами на запрет: подменённый идентификатор, отозванное членство, истёкшая сессия, запрос без CSRF-токена, второй экземпляр сервиса.
Вопросы собеседования
- Как правильно хранить пароли? Чем bcrypt, scrypt и Argon2 отличаются от SHA-256?
- Сессии на сервере или JWT: когда что выбрать? Как отозвать JWT?
- Что такое CSRF и XSS, и какие атрибуты cookie от чего защищают?
- Что такое CORS и от чего он не защищает?
- Как устроить права в многоарендном сервисе так, чтобы пользователь одной организации не видел данные другой?
- Что такое IDOR? Как его найти тестами?
- Когда отвечать 401, когда 403, а когда 404?
- Что такое атака по времени, и где она возможна в сервисе входа?
Неделя 6. Оплата, выдача и возврат
Оплата, уведомления провайдера, выдача и возвраты так, чтобы деньги не терялись и не списывались дважды
Задача недели
Анна бронирует дрель DR-002 на два дня. Цена 450 ₽ в сутки, залог 5000 ₽, поэтому к оплате 2 × 450 + 5000 = 5900 ₽, в копейках 590000. Аренда и залог идут одним платежом, так что вернуть залог значит вернуть часть этого платежа.
Чему научитесь
- Считать сумму к оплате на сервере по цене, сохранённой в брони, и объяснить, почему клиент не присылает ни сумму, ни отметку «оплачено».
- Сохранять платёж в базе до обращения к провайдеру, вызывать провайдера вне транзакции, с таймаутом и своим ключом идемпотентности, а когда исход неизвестен, отвечать 502 и принимать повтор с тем же ключом.
- Сделать ключ идемпотентности для клиента: у ключа есть область действия и отпечаток запроса, повтор получает тот же ответ, а другой запрос с тем же ключом получает 422.
- Принимать уведомления провайдера по Standard Webhooks: считать подпись от исходных байтов тела, проверять метку времени, поддерживать несколько подписей и сравнивать их за постоянное время. В примере про SMS всё это написано вручную, в проекте используется готовый файл курса. Сколько бы раз ни пришло одно уведомление, сработать оно должно один раз.
- Не доверять содержимому даже подлинного уведомления: находить платёж по ID провайдера, сверять сумму и отвечать таким кодом, чтобы провайдер повторял уведомление, только когда это нужно.
- Переводить бронь между состояниями (оплата, подтверждение, выдача, возврат вещи, отмена) под блокировкой строки брони и держать ограничения базы как запасную защиту на случай ошибки в коде.
- Считать возвраты так, чтобы в сумме они не превысили полученные деньги: остаток под блокировкой платежа, залог после ручных возвратов, оплата, которая пришла уже после истечения резерва.
- Замечать ловушку READ COMMITTED, когда присоединённая таблица читается в одном запросе с
FOR UPDATE, и писать тесты гонок и отказов: одновременные выдачи, отмена во время выдачи, повторные уведомления, потерянный ответ.
Вопросы собеседования
- Как интегрироваться с платёжным провайдером, если ответ на создание платежа может потеряться?
- Что такое ключ идемпотентности? Где его хранить, сколько, и что делать с тем же ключом и другим телом?
- Как проверить подлинность webhook, и от каких атак защищает метка времени?
- Webhook пришёл дважды или раньше, чем вы сохранили ID объекта. Что делать?
- Почему нельзя хранить деньги во
float, и как вы считаете возвраты по одному платежу? - Что видит транзакция READ COMMITTED после ожидания блокировки строки? Чем это отличается на REPEATABLE READ?
- Что такое проблема двойной записи (dual write), и какие у неё решения?
- Как вы протестируете гонку «отмена против выдачи» так, чтобы тест ловил ошибку стабильно, а не через раз?
Неделя 7. Redis и кэш
Кэш каталога в памяти процесса и в Redis, ограничение попыток входа и работа сервиса, когда Redis недоступен
Задача недели
Субботнее утро. Покупатели листают каталог проката: открывают карточки, переходят по страницам, смотрят, свободна ли дрель на выходные. Одни и те же популярные дрели и пилы открывают в десятки раз чаще остальных позиций. Сейчас каждый такой просмотр превращается в запрос к PostgreSQL, хотя ответ на него не менялся с прошлой недели.
Чему научитесь
- Строить кэш в два уровня, в памяти процесса (L1) и в Redis, по схеме cache aside и объяснять, от чего защищает каждый уровень и чего он стоит.
- Составлять ключи кэша так, чтобы в них входили организация и все параметры запроса, и ограничивать память: TTL у каждого ключа Redis, предел записей L1 с вытеснением тех, что давно не читали,
maxmemoryи политика вытеснения Redis. - Решить, насколько свежими должны быть данные в кэше, и соблюдать это правило: менять версию каталога вместо удаления ключей, сбрасывать L1 через номер эпохи, ограничивать срок записей L1 в остальных процессах. Объяснять гонку, при которой старое значение попадает в кэш уже после удаления.
- Объединять одновременные промахи по одному ключу в одну загрузку из базы и объяснять, как возникает лавина промахов.
- Продолжать работу, когда Redis недоступен: свой таймаут на ответ, отказ без очереди запросов, пауза после отказа, запуск, остановка и проверка готовности без Redis.
- Ограничивать частоту попыток входа атомарным счётчиком в Redis, с запасным счётчиком в памяти процесса, и отвечать 429 с
Retry-After. - Показывать пользу кэша метриками и замером на одинаковых данных и проверять тестами свежесть данных, лимит входа и отказ Redis.
Вопросы собеседования
- Как вы построите кэш перед базой, и как будете держать его согласованным с базой?
- Одна запись входит в тысячи закэшированных страниц. Как вы их инвалидируете?
- Что такое cache stampede, и какие способы борьбы с ним вы знаете?
- Чем кэш в памяти процесса отличается от Redis, и когда нужен каждый?
- Redis недоступен. Что делает ваш сервис, и как вы это проверили?
- Как сделать rate limiting для нескольких экземпляров сервиса? Сравните фиксированное окно, скользящее окно и token bucket.
- Как выбрать
maxmemory-policy, и что будет, если не задатьmaxmemory? - Как доказать, что кэш помогает? Какие метрики и какой замер вы покажете?
Неделя 8. RabbitMQ и Transactional Outbox
Событие о подтверждении брони доходит до клиента даже при сбоях: outbox, RabbitMQ, повторы и очередь недоставленных сообщений
Задача недели
Суббота, утро. Анна оплачивает бронь дрели «Дрель ударная Bosch GSB 13 RE» на выходные. Провайдер платежей присылает уведомление об успешной оплате, сервис подтверждает бронь: это работа недели 6. Теперь Анна должна получить сообщение «Бронь подтверждена: Дрель ударная Bosch GSB 13 RE, с … до …». Отправляет его провайдер уведомлений, внешний сервис. Он бывает медленным, отвечает 503 или принимает запрос, а ответ теряется по дороге.
Чему научитесь
- Объяснять, почему запись в базу и публикация в брокер без общей транзакции теряют события или публикуют то, чего не было, и записывать событие в outbox в той же транзакции, что и само изменение.
- Написать relay: забирать записи outbox себе на время через
FOR UPDATE SKIP LOCKED, публиковать с подтверждением брокера и флагомmandatory, ждать подтверждения не дольше своего срока, отмечать запись отправленной только после подтверждения и повторять с растущей паузой. - Объявлять схему RabbitMQ: topic-обменник, quorum-очереди, привязки, dead-letter. Объяснять, что гарантирует каждое подтверждение: подтверждение издателю, возврат по
mandatoryи ack потребителя. - Обрабатывать доставку «хотя бы один раз» так, чтобы клиент не получил уведомление дважды: inbox в базе и ключ идемпотентности у провайдера. Объяснять, почему это всё ещё не «ровно один раз».
- Повторять временные отказы провайдера с растущей паузой, отправлять в DLQ с указанием причины сообщения, которые невозможно обработать («яд»), и сообщения, у которых кончились попытки, и возвращать их в работу командой redrive.
- Отбрасывать событие, если уже обработана более новая версия брони.
- Продолжать работу без брокера: запускаться без RabbitMQ, восстанавливать соединение, следить за ним через heartbeat и останавливаться с пределом ожидания.
- Показывать в метриках очередь outbox, повторы и DLQ и проверять тестами откат транзакции, падения relay и потребителя, отказы провайдера и брокера.
Вопросы собеседования
- Что такое Transactional Outbox, и какие альтернативы вы знаете?
- At-most-once, at-least-once, exactly-once: что реально достижимо при отправке SMS или письма?
- Как сделать потребителя сообщений идемпотентным?
- Чем подтверждения издателя отличаются от подтверждений потребителя, и зачем нужен флаг
mandatory? - Что такое DLQ, когда сообщение туда попадает, и как вы возвращаете сообщения в работу?
- Classic- и quorum-очереди RabbitMQ: чем отличаются и когда что выбрать?
- Как гарантировать порядок обработки событий одного агрегата?
- Как мониторить outbox и очереди: какие метрики и на что ставить оповещения?
Неделя 9. Сбои внешних сервисов и сверка платежей
Что делать, когда платёжный провайдер молчит или отказывает: таймауты, повторы, Circuit Breaker и сверка платежей
Задача недели
Суббота, утро. Анна оплачивает дрель на выходные. Сервис отправляет платёж провайдеру, а тот пять секунд молчит. С кодом недели 6 Анна получает 502, то есть «ошибка», хотя платёж у провайдера, возможно, уже создан. Если приложение Анны не повторит запрос с тем же ключом, платёж так и останется без исхода: кроме Анны, его никто не доведёт.
Чему научитесь
- Различать по ответу провайдера три исхода: запрос не обработан, исход неизвестен, отказ. Не считать таймаут отказом: платёж получает статус
unknown, клиент получает 202, повтор идёт только с тем же ключом. - Повторять только те запросы, которые провайдер не обработал. Пауза между попытками растёт и получает случайный разброс,
Retry-Afterзадаёт её минимум, а число попыток и общее ожидание ограничены. - Отключать вызовы провайдера, который раз за разом отказывает, с помощью Circuit Breaker: порог ошибок, время отключения, одна пробная попытка и ответ 503 с
Retry-After. - Доводить платежи и возвраты с неизвестным исходом сверкой с провайдером по исходному ключу, не создавая нового списания. Строки для сверки берутся через
SKIP LOCKED, а интервал до следующей проверки растёт. - Применять результат уведомления и сверки одним и тем же кодом. Уведомление, пришедшее поздно или повторно, не откатывает состояние. Если оплата прошла уже после истечения брони, деньги возвращаются полностью, а бронь не подтверждается.
- Не допускать, чтобы возвраты превысили полученную сумму, когда исход части из них ещё неизвестен.
- Считать срок возврата вещи по часовому поясу пункта выдачи, находить просрочки и ставить сотрудникам задачи без дублей.
- Проверять отказы тестами: сбои симулятора провайдера для одной конкретной брони, сдвиг дат в базе, метрики незавершённых операций.
Вопросы собеседования
- Чем отличаются таймаут, отказ и «не обработано» при вызове платёжного API, и как вы обрабатываете каждый?
- Как устроены экспоненциальная пауза и разброс, и зачем они нужны?
- Что такое Circuit Breaker, какие у него состояния и параметры?
- Как вы сверяете состояние платежей с провайдером?
- Что делать с вебхуком, который пришёл поздно или дважды?
- Как гарантировать, что возвраты не превысят платёж при параллельных запросах?
- Как хранить и сравнивать время с учётом часовых поясов в PostgreSQL?
- Как фоновой задаче работать в нескольких экземплярах сервиса без двойной обработки?
Неделя 10. Отдельный сервис уведомлений
Уведомления уходят в отдельный сервис со своей базой, а события меняются так, чтобы не сломать получателей
Задача недели
Пятница, вечер. Ольга хочет поменять текст уведомления: клиенты возвращают вещи к закрытию пункта, а не к 10:00, и в понедельник у Сергея три задачи return_overdue. Срок возврата в тексте нужен давно, но уведомления живут в процессе ядра. Чтобы поменять одну строку текста, нужно выкатить весь сервис проката, а он прямо сейчас принимает оплаты. Любая ошибка в коде уведомлений, которая роняет процесс, роняет и приём оплат.
Чему научитесь
- Вынести модуль в отдельный процесс, который выкатывается сам по себе: своя точка входа, конфигурация, база и роль. Проверять границу в трёх местах: в импортах, в правах базы и в переменных окружения.
- Разделять данные сервисов внутри одного PostgreSQL: у каждого сервиса своя база и роль-владелец без прав суперпользователя, право
CONNECTуPUBLICотозвано. Понимать, что увидит чужая роль, если этого не сделать. - Сделать так, чтобы остановка сервиса уведомлений не мешала ядру: брони подтверждаются, события ждут в очереди, а после запуска каждое даёт одно уведомление, даже при дублях и падении посреди отправки. Доказывать это тестами.
- Менять контракт события, не ломая получателей: добавлять поле без новой версии, читать события, не падая на незнакомых полях, выкатывать потребителя раньше издателя и узнавать изменения, для которых нужна новая версия.
- Передавать correlation ID от HTTP-запроса через outbox и брокер до провайдера уведомлений и добавлять данные операции в каждую строку журнала через
AsyncLocalStorage, а не через переменную модуля. - Прослеживать путь одного события по журналам и метрикам двух процессов и не допускать секретов в журнале.
- Записывать архитектурное решение в ADR, вести CHANGELOG и ставить тег выпуска.
Вопросы собеседования
- Как вы решаете, какой модуль выделять в отдельный сервис, и какие у этого издержки?
- Как изолировать данные сервисов, если у вас одна установка PostgreSQL?
- Как менять схему базы без простоя, когда старая и новая версии работают одновременно?
- Как менять контракт события, чтобы не сломать потребителей?
- Что такое correlation ID, чем он отличается от request ID и как его передавать между сервисами через брокер?
- Как в Node.js добавить контекст запроса в каждую строку журнала?
- Как убедиться, что падение сервиса уведомлений не мешает основному сценарию?
- Что вы пишете в ADR и зачем?
Неделя 11. PostgreSQL: поиск по каталогу
Быстрый поиск по каталогу в PostgreSQL: планы запросов, индексы, JSONB и полнотекстовый поиск на русском
Задача недели
Прокат вырос. У небольших организаций сотни позиций, у сетевой десятки тысяч, и все они в одной базе. Анна ищет «перфораторы hilti» на выходные. Каталог умеет фильтровать только по категории и отдаёт страницы по 20 позиций. Позиция называется «Перфоратор Hilti TE 30», и слово «перфораторы» её не находит. Свободна ли позиция в субботу, можно узнать, только открыв её карточку.
Чему научитесь
- Читать план
EXPLAIN (ANALYZE, BUFFERS): узлы, ожидаемое и фактическое число строк, прочитанные страницы. Находить тяжёлые запросы сервиса вpg_stat_statementsи оценивать запрос по прочитанным страницам, а не по миллисекундам. - Подбирать под конкретный запрос составной, частичный, покрывающий индекс или индекс по выражению, ставить организацию первым столбцом и объяснять, во что индекс обходится при записи и по месту на диске.
- Делать постраничную выдачу по ключу (keyset), в которой строки не теряются и не повторяются при вставках, с курсором, привязанным к запросу, и проверять это тестом со вставкой между страницами.
- Хранить характеристики разных типов в JSONB, принимая только ключи из справочника, искать по ним через индекс GIN, выбирать класс операторов и сравнивать числа без ошибок приведения типов.
- Делать полнотекстовый поиск с учётом русских словоформ:
tsvector,websearch_to_tsquery, индекс под выражение запроса, ранжирование. Знать, чего такой поиск не умеет. - Искать по началу строки с помощью индекса, не подставляя ввод пользователя в шаблон, и находить свободные даты через уже существующий индекс броней.
- Проверять фильтры на входе и при ошибке отвечать 400 с указанием параметра, а не ошибкой базы. Доводить до конца расширение и сжатие схемы и записывать в ADR, когда встроенного поиска перестанет хватать.
Вопросы собеседования
- Как вы ищете причину медленного запроса в PostgreSQL? Что смотрите в
EXPLAIN (ANALYZE, BUFFERS)? - Как выбрать порядок столбцов составного индекса?
- Чем плоха постраничная выдача через
OFFSETи как сделать её по ключу? - Как индексировать JSONB, и чем отличаются классы операторов GIN?
- Как устроен полнотекстовый поиск в PostgreSQL и чем он отличается от
LIKE? - Что такое частичный и покрывающий индекс, и когда их применять?
- Сколько стоит индекс и как понять, что он лишний?
- Когда вы вынесли бы поиск из PostgreSQL в отдельный движок и что это изменит?
Неделя 12. SaaS: тарифы, подписки и лимиты организаций
Сервис становится платным для прокатов: тарифы, ежемесячные списания без дублей, просрочка оплаты и лимиты
Задача недели
Прокат вырос из одного пункта в сервис, которым пользуются разные прокаты. Организацию до сих пор заводит начальная загрузка, а платить за сервис нельзя: прокат Ольги в Москве, как и все остальные, работает бесплатно и без ограничений.
Чему научитесь
- Описать модель подписки (тариф с версией, подписка, счёт за период, попытка списания, история) и её жизненный цикл: состояния, переходы и что запрещено в каждом состоянии.
- Считать границы месячных периодов от дня, с которого началась подписка, в часовом поясе организации:
AT TIME ZONEв обе стороны, конец месяца, летнее время. Отличать пояс IANA от смещения и в том же поясе считать дни пункта выдачи: «сегодня», окно выдачи, срок возврата. - Отделять бизнес-время от системного и управлять бизнес-временем в тестах: часы подписок вместо
Date.now()иnow()базы. - Написать биллинг, который при любом числе копий сервиса выставляет один счёт за период и списывает деньги один раз: блокировка с
SKIP LOCKED, уникальный счёт, не больше одной попытки в работе, ключ попытки, вызов провайдера вне транзакции. - Доводить списания с неизвестным исходом и с поздним ответом: сверка тем же ключом, ответ меняет только незавершённую попытку, а прошедшую оплату ничто не откатывает.
- Соблюдать лимит тарифа при одновременных запросах с помощью блокировки строки подписки и объяснять, почему не подходят
count(*) … FOR UPDATEи блокировка подсчитываемых строк. Проверять лимит тестом с одновременными запросами. - Вести просрочку оплаты: повторные списания по расписанию, отсрочка, ограниченный режим с ответом 402, уведомление владельцу через outbox. Записывать решения в ADR и разбирать гонки в отчёте.
Вопросы собеседования
- Как устроить биллинг подписок, чтобы при нескольких экземплярах и сбоях не списать дважды?
- Как считать месячные периоды подписки, и что делать с 31-м числом и летним временем?
- Как гарантировать «не больше N» при одновременных запросах в PostgreSQL?
- Что делать, если платёжный провайдер не ответил на списание?
- Как тестировать код, который зависит от времени, без ожидания и без веток «если тест»?
- Как спроектировать состояния подписки, и что разрешать при неоплате?
- Как хранить тарифы и менять цены, не ломая действующие подписки?
- Чем
SELECT … FOR UPDATEотличается отSERIALIZABLE, и когда что выбирать?
Неделя 13. Kafka как журнал событий
События аренды пишутся в Kafka, и сервис аналитики собирает по ним сводку для владельца
Задача недели
Ольга, владелица проката в Москве, задаёт простые вопросы: сколько броней подтверждено, сколько вещей сейчас у клиентов, сколько вернули и отменили, сколько дней занято. Ответ «посчитаем запросом к базе» не годится. База ядра обслуживает клиентов, и тяжёлые отчёты мешают броням. С недели 10 соседние сервисы не читают чужих таблиц. Кроме того, отчёты недель 15-16 захотят знать не только «сколько сейчас», но и «как менялось», то есть историю.
Чему научитесь
- Отличать очередь команд от журнала событий, решать, куда отправлять каждое событие, и объяснять, почему одно событие иногда нужно отправить в оба места.
- Доставлять событие из outbox в каждое место назначения отдельно, со своими попытками, захватом записи и отметкой о доставке, и переводить на это схему базы через расширение и сжатие, пока старый relay ещё работает.
- Выбирать ключ сообщения и число partitions, распределять сообщения по partitions так же, как это делает клиент Java, и сохранять порядок событий одной брони при нескольких relay.
- Настраивать продюсер (идемпотентность,
acks=all, свой срок отправки) и объяснять, откуда берутся дубли и почему они возможны даже с этими настройками. - Писать потребителя в группе: как распределяются partitions, что происходит при перераспределении, почему offset фиксируется после записи в базу, как повторять при недоступной базе и как считать lag.
- Строить проекцию «последнее состояние по версии», которую не портят дубли и опоздавшие события, пересобирать её, читая журнал с начала, и проверять это тестом с дублями и перепутанным порядком.
- Работать в общем кластере под учётной записью с минимальными правами: SASL, ACL на topic и группу, а topic и права заводит администратор. Проверять права попытками сделать то, что запрещено.
Вопросы собеседования
- Когда выбирать Kafka, а когда RabbitMQ? Можно ли использовать их вместе?
- Как гарантировать порядок событий одной сущности в Kafka?
- Как опубликовать событие в Kafka и записать изменение в базу так, чтобы не потерять ни одно из двух?
- Что такое consumer group и rebalance? Что происходит, когда копия падает?
- Когда фиксировать offset? Что такое at-least-once и exactly-once в Kafka и где кончается exactly-once?
- Как потребитель переживает дубли и события не по порядку?
- Что такое consumer lag, и как по нему понять, что потребитель отстаёт или стоит?
- Как изолировать сервисы в общем кластере Kafka?
Неделя 14. Kafka при сбоях и повторном чтении
Аналитика на Kafka не теряет и не удваивает события при сбоях, повторах и пересборке
Задача недели
Сводок недели 13 Ольге мало. Она хочет видеть активность: сколько подтверждений, выдач, возвратов и отмен было в каждый день по каждой позиции каталога. Так она поймёт, какие вещи берут по выходным и когда закупать вторую дрель. Это уже не «последнее состояние брони», а счёт событий, и повторы для него опасны. Сводка выдерживала их сравнением версий, счёту это не поможет.
Чему научитесь
- Находить, откуда у потребителя берутся повторы (повторная публикация, сбой между записью и фиксацией offset, перераспределение, повторное чтение), и отсеивать их, записывая eventId в той же транзакции, что и результат обработки.
- Объяснять, почему Kafka не даёт «ровно один раз» при записи во внешнюю базу, и проверять опасный случай: результат записан, а offset не зафиксирован.
- Различать временные и постоянные ошибки записи: временные повторять сколько нужно, постоянные до предела, а сообщение, которое невозможно обработать («яд»), отправлять в карантин, не останавливая чтение partition.
- Сохранять сообщение в карантине без изменений, то есть исходные байты ключа, значения и заголовков, вместе с причиной, текстом ошибки и числом попыток.
- Хранить позицию чтения в базе витрины рядом с данными и пересобирать витрину отдельной группой потребителей в пустую базу или в копию базы (snapshot), дочитывая до концов partitions на момент старта.
- Задавать срок хранения topic явно и проверять, что история, нужная витрине, ещё есть в журнале. Если нужного offset уже нет, клиент молча перескочит в начало или в конец, поэтому полагаться на него нельзя.
- Проверять тестами поведение при сбоях: повтор, «яд», недоступную базу, удалённую историю.
Вопросы собеседования
- Что такое exactly-once в Kafka и где он кончается? Как добиться, чтобы событие меняло внешнюю базу один раз?
- Как сделать потребителя идемпотентным? Почему не по offset?
- Что такое poison message и что с ним делать? Чем DLQ в Kafka отличается от DLQ в RabbitMQ?
- Какие ошибки потребителя стоит повторять, а какие нет?
- Как пересобрать проекцию из Kafka, не мешая работающему потребителю?
- Что будет, если потребитель отстал больше, чем на срок хранения topic?
- Как часто снимать snapshot проекции и от чего это зависит?
Неделя 15. ClickHouse и событийные таблицы
События броней и денег загружаются в ClickHouse, и владелец получает первый отчёт по дням
Задача недели
Раз в неделю Ольга садится считать: сколько броней подтвердили и сколько отменили, сколько денег пришло и сколько ушло обратно клиентам. Сводка и активность недель 13-14 касаются броней, денег в них нет. Ольге нужен отчёт по дням за любой период до года: подтверждения и отмены броней, число и сумма принятых платежей, число и сумма прошедших возвратов. Сутки считаются по поясу её организации: оплата в половине первого ночи по её часам относится к новому дню, а не к вчерашнему по UTC.
Чему научитесь
- Объяснять, чем аналитическое хранилище отличается от базы сервиса, зачем отчётам отдельная база и в каких запросах ClickHouse уступает PostgreSQL.
- Проектировать таблицу MergeTree: ключ сортировки под запрос отчёта, partition по месяцу события, срок хранения от момента загрузки. Не путать ключ сортировки с уникальностью.
- Хранить деньги целыми копейками, а время в
DateTime64по UTC, передавать 64-битные целые в JavaScript без округления и считать сутки по часовому поясу организации, в том числе в дни перехода на летнее и зимнее время. - Переносить события из Kafka в ClickHouse пакетами, фиксировать offset после вставки и при чтении учитывать каждое событие один раз.
- Отклонять до вставки то, что ClickHouse не примет, и сохранять отклонённое вместе с указанием, где оригинал лежит в журнале.
- Разделять права: загрузчик только вставляет, отчёты только читают, и их лимиты нельзя поменять из запроса. Передавать значения в запрос параметрами.
- Проверять схему, права, пакеты, повторы и отчёт тестами на настоящем ClickHouse.
Вопросы собеседования
- Чем OLAP-хранилище отличается от OLTP-базы? Когда ClickHouse не нужен?
- Как выбрать
ORDER BYиPARTITION BYдля таблицы MergeTree? - Как ClickHouse обращается с дублями? Что такое ReplacingMergeTree и когда он схлопывает строки?
- Как переносить данные из Kafka в ClickHouse, не теряя событий при сбоях?
- Почему деньги не хранят во float, и что делать с 64-битными целыми в JavaScript?
- Как ограничить аналитические запросы на общем сервере?
- Как построить отчёт по дням для организаций в разных часовых поясах?
Неделя 16. Отчёты без расхождений и сверка
Отчёты в ClickHouse сходятся с базой сервиса, даже когда события приходят повторно или с опозданием
Задача недели
Отчёт недели 15 Ольга открывает каждую неделю. Теперь ей нужно больше. Она хочет знать, сколько экземпляров занято в каждый день следующего месяца и сколько броней на эти дни уже отменили. Это загрузка, и по ней Ольга решает, докупать ли дрели. Ещё ей нужны деньги в том виде, в каком их видит бухгалтер: аренда отдельно от залога, возвраты по видам (отмена, залог, оплата без брони, ручной возврат). Залог не выручка, его вернут.
Чему научитесь
- Объяснять, почему ReplacingMergeTree убирает повторы и старые версии не сразу, выбирать последнюю версию при чтении (
argMaxилиFINAL) и измерять, во что обходится такое чтение. - Проектировать таблицу последнего состояния: версия берётся из события, ключ и partition строятся по полю, которое не меняется. Заполнять её материализованным представлением, которое работает с правами создателя, и переносить в неё старые строки.
- Считать деньги по видам операций, отдельно аренду, залог и возвраты, без соединения возвратов с платежами и без суммирующего представления поверх данных с повторами.
- Считать границы суток по правилам часового пояса, в том числе в дни, когда полуночи нет.
- Определять, до какого момента отчёт полон (watermark), по прогрессу загрузчика и неопубликованным событиям outbox и объяснять, чего watermark не видит.
- Сверять копию с источником по операциям и броням, отличать отставание от пропуска и исправлять пропуск повторной доставкой того же события.
- Проверять отчёты до и после слияния частей, а также отставание и пропуск, тестами на настоящих ClickHouse и Kafka.
Вопросы собеседования
- Как ClickHouse работает с изменяемыми данными? Когда использовать ReplacingMergeTree, когда
FINAL, когдаargMaxи сколько это стоит? - Что такое материализованное представление в ClickHouse и чем оно отличается от материализованного представления PostgreSQL?
- Как сделать так, чтобы повторная доставка событий не удваивала итоги аналитики?
- Что такое watermark и чем он отличается от lag потребителя?
- Как сверить аналитическое хранилище с базой источника и что делать с найденным?
- Почему отчёт по суткам нельзя строить на границе «полночь плюс 24 часа» и где ломается даже сама полночь?
- Что произойдёт с аналитикой, если потребитель Kafka простоял дольше срока хранения topic?
Неделя 17. System design и измерения
Выдержит ли сервис субботний наплыв клиентов: метрики, трассировка, нагрузочный тест в k6 и расчёт ёмкости
Задача недели
У сети проката «Нагрузка» двадцать тысяч позиций, тысячи броней за сезон и сотни клиентов субботним утром. Владелица сети Ольга спрашивает прямо: выдержит ли сервис субботу? А если нет, что покупать: сервер побольше, вторую копию, «микросервисы»? Ответ «должно хватить» её не устроит, ей нужны числа.
Чему научитесь
- Задавать SLO для профиля нагрузки через измеримые показатели (перцентили задержки чтения и записи и долю ошибок) и считать бюджет ошибок прогона.
- Отдавать через клиент Prometheus метрики, по которым видно узкое место: длительность запросов гистограммой по шаблону маршрута с границами из SLO, задержку и загрузку event loop, состояние и очередь пула соединений и бизнес-метрику.
- Прослеживать изменение через несколько процессов в OpenTelemetry: span'ы операций с атрибутами, передача контекста W3C Trace Context через outbox и заголовки Kafka, статус ошибки и
trace_idв строках журнала. По трассе находить медленный или упавший шаг. - Писать воспроизводимый сценарий k6 с открытой моделью нагрузки, профилем, данными из seed и порогами SLO и объяснять, почему закрытая модель скрывает перегрузку.
- Находить по
pg_stat_statements, на что база тратит время под нагрузкой, и доказывать, что запрос стал лучше, планом и числом прочитанных страниц, а не миллисекундами. - Объяснять предел пула через закон Литтла, ограничивать ожидание соединения и при перегрузке быстро отвечать 503 с
Retry-After. - Сравнивать «до» и «после» в одинаковых условиях и находить, сколько выдерживает одна копия сервиса, постепенно поднимая частоту запросов.
- Обосновывать в ADR SLO, ёмкость, поведение при отказе одного компонента и выбор между монолитом и сервисами. Объяснять, почему масштабирование не сводится к добавлению копий и почему кэш в памяти процесса у каждой копии свой.
- Проверять тестами метрики, трассу, перегрузку пула и согласованность двух копий.
Вопросы собеседования
- Как вы зададите SLO для API и проверите, что сервис его держит?
- Почему p99 важнее среднего и как считать перцентили по нескольким копиям?
- Чем отличаются открытая и закрытая модели нагрузки и что такое coordinated omission?
- Как найти узкое место Node.js-сервиса под нагрузкой: процессор, event loop, пул или база?
- Как подобрать размер пула соединений с базой?
- Сервис перегружен: что он должен отвечать и как клиенты должны повторять?
- Почему нельзя масштабировать сервис, просто добавив копий, и что мешает копиям быть взаимозаменяемыми?
- Когда стоит делить монолит на сервисы, а когда нет?
- Как провести трассу через очередь сообщений и когда обработке нужен родитель, а когда ссылка на контекст сообщения?
- Запрос медленный у одного клиента из сотни: как вы найдёте его трассу и шаг, на котором он стоял?
Неделя 18. Резервные копии и безопасность выпуска
Резервные копии с проверкой восстановления, безопасная загрузка файлов и ссылок, права в базе и выпуск по тегу
Задача недели
У проката два новых запроса. Первый от сотрудников: клиент вернул дрель со сколом и говорит, что так и было. Нужны фото состояния вещи при выдаче и при возврате, чтобы спор решался за минуту. Второй запрос от владельца: поставщик выкладывает каталог файлом JSONL по ссылке, и переносить его руками каждую неделю никто не хочет. Пусть сервис сам скачивает файл по ссылке.
Чему научитесь
- Описывать модель угроз выпуска (что защищаем, от кого и через какую границу) и проверять каждую границу тестом.
- Безопасно принимать файлы от пользователя: определять тип по содержимому, отдавать файл так, чтобы браузер его не исполнил, строить путь из своих идентификаторов и записывать файл целиком.
- Отказывать до чтения тела и ограничивать размер, время и структуру входящих данных, чтобы чужой клиент не занимал память и соединения сервиса.
- Скачивать файл по ссылке пользователя, не открывая доступ во внутреннюю сеть: проверять адреса после DNS, подключаться к проверенному адресу и не следовать переадресациям.
- Разделять права в базе: владелец схемы, рабочая роль сервиса без DDL и без удаления денежных записей, роль для копий только на чтение. Выдавать права так, чтобы повторный запуск давал тот же результат.
- Снимать копию базы и файлов, восстанавливать её в отдельной среде, проверять, что деньги сходятся и файлы на месте, и измерять время восстановления и объём потерянных данных.
- Хранить секреты и менять их без простоя: старый секрет подписи действует, пока идёт смена, метрики закрыты токеном, в журналах и копиях нет лишнего.
- Выпускать версию по тегу, проверяя, что миграции применяются к пустой базе с нуля, и писать runbook на случай восстановления, утечки секрета и выпуска.
Вопросы собеседования
- Как вы защитите загрузку файлов в веб-сервисе? Что изменится, если файлы отдаются с того же домена, что API?
- Что такое SSRF? Как устроить функцию «скачать по ссылке» в сервисе, который работает в облаке?
- Сервис ходит в базу суперпользователем. Как вы перейдёте на роль с минимальными правами без простоя?
- Как устроены копии вашего последнего проекта? Когда из них последний раз восстанавливались и сколько это заняло?
- Как поменять секрет, если старый ещё используется клиентами или провайдером?
- Что вы проверите в CI на теге выпуска, чего нет в CI ветки?
- Медленный клиент держит соединения сервиса на Node.js. Какие настройки и в каком месте вы поменяете?
Неделя 19. Итоговое изменение
Одно изменение от запроса владельца до выпуска: анализ, правило для двух таблиц, совместимый API и выкат без простоя
Задача недели
Владелец пишет в чат: «DR-002 сломалась, мастер заберёт её в понедельник на неделю. Сейчас её можно забронировать на эти дни. Сотрудник узнаёт о проблеме в день выдачи, когда клиент уже стоит у стойки. Сделайте так, чтобы дрель на ремонте не бронировали».
Чему научитесь
- Разбирать запрос на изменение до того, как писать код: записывать в карточку проблему, правило, границы, оценку по частям и условия готовности, чтобы её можно было обсудить.
- Находить всё, что затрагивает изменение: код, данные, события, отчёты и документы. Для каждого места решать, меняется ли оно и какой тест это проверяет.
- Соблюдать правило, которое связывает две таблицы, при одновременных запросах с помощью общей блокировки или SERIALIZABLE с повтором и доказывать это тестом гонки.
- Выбирать, хранить признак состояния или вычислять его при чтении, и объяснять, как этот выбор ведёт себя при выкате, истечении сроков и отменах.
- Менять HTTP-контракт так, чтобы старые клиенты не сломались: отличать безопасные изменения от ломающих и находить их в OpenAPI.
- Выпускать изменение по шагам: миграция только расширяет схему, новые правила включаются, когда выкачены все копии, затем тесты регрессии, README, ADR, CHANGELOG, тег и демонстрация на своём стенде.
Вопросы собеседования
- Расскажите об изменении, которое вы провели в существующей системе. Как вы искали, что оно задевает?
- Два запроса проверяют разные таблицы и оба проходят, нарушая правило. Как это называется и как вы это исправите?
- Флаг в базе или вычисление при чтении: как вы выбираете?
- Как вы меняете API, которым пользуются мобильные приложения старых версий?
- Как выкатить изменение правила, если старые копии сервиса ещё отвечают?
- Как вы оцениваете задачу и что делаете, когда оценка не сошлась?
- Что вы проверяете в ревью чужого изменения в первую очередь?
Неделя 20. Выпуск и защита проекта
Выпуск проекта, защита перед наставником и подготовка к собеседованию
Задача недели
Владелец проката получает от вас систему и задаёт три вопроса. Первый: «Если вы завтра уйдёте, кто-нибудь её поднимет?». Для этого нужны README с одной командой запуска, образец данных, схема того, какой процесс с кем связан, и инструкции на случай аварии: копия, восстановление, сверка. Всё это должно совпадать с кодом.
Чему научитесь
- Выпустить проект так, чтобы человек, который видит его впервые, запустил его по README, и защищать этот выпуск, а не рабочую ветку.
- Искать причину незнакомого сбоя по журналам, метрикам, состоянию базы и очередей, по порядку: гипотеза, проверка, исправление, проверка исправления.
- Находить в PostgreSQL, какой запрос кого блокирует, и разбирать очередь блокировок, не роняя сервис.
- Прикидывать ёмкость сервиса простым расчётом и объяснять, как её меняет одна медленная операция.
- Объяснять свои решения и заметную помощь ИИ так, чтобы было видно, что решения принимали вы.
- Описывать учебный проект как есть: паспорт подтверждённых навыков, строка для резюме, в которой учебный проект не выдаётся за коммерческий опыт, и план, что подтянуть дальше.
Вопросы собеседования
- Расскажите о проекте за две минуты. Что в нём было самым сложным?
- Расскажите о решении, которым вы гордитесь, и о том, которое переделали бы.
- Сервис стал отвечать медленно. Что вы сделаете в первые десять минут?
- Как найти, кто держит блокировку в PostgreSQL, и что с ним делать?
- Как связать журналы нескольких сервисов, чтобы найти путь одного запроса?
- Сколько соединений с базой нужно сервису на 1000 запросов в секунду? Как вы посчитаете?
- Спроектируйте сервис бронирования: требования, данные, процессы, узкие места.
- Как вы используете ИИ в работе и как проверяете его предложения?
- Чего вы пока не умеете и как собираетесь научиться?
Навыки, которые вы подтвердите на защите
После защиты вы получаете паспорт навыков. В нём перечислены умения, которые вы показали на своём проекте, и у каждого есть ссылка на тест, merge request или отчёт. Ниже все 25 навыков курса и недели, на которых их разбираем.
| Навык | Недели |
|---|---|
| Рабочее место разработчика: Git, терминал, Docker и основы SQL | 0 |
| Как работает Node.js: цикл событий, блокировки, промисы, отмена и таймауты | 1, 17 |
| Потоки и обратное давление | 1 |
| TypeScript на backend: проверка внешних данных и ошибки | 1, 2, 3, 4, 5, 18 |
| HTTP API: контракт, коды ошибок, постраничная выдача, OpenAPI | 1, 2, 4, 19 |
| NestJS: модули, внедрение зависимостей, запуск и остановка | 4 |
| SQL, проектирование схемы и миграции PostgreSQL | 3, 11 |
| Транзакции, блокировки и одновременные изменения данных | 3, 6, 12, 19, 20 |
| Индексы, планы запросов, JSONB и полнотекстовый поиск | 11, 17 |
| Вход, сессии, права ролей и изоляция организаций | 5, 18 |
| Деньги без ошибок округления и без двойных списаний | 1, 6, 9, 12 |
| Кэш, ограничение частоты запросов и работа без Redis | 7 |
| Очереди сообщений: RabbitMQ, outbox и inbox, недоставленные сообщения | 8, 13 |
| Сбои внешних сервисов: таймауты, повторы, Circuit Breaker, webhooks и сверка | 6, 9 |
| Подписки SaaS: списания за период, лимиты тарифа и просрочка | 12 |
| Выделение сервиса и контракты событий | 10 |
| Наблюдаемость: журналы, метрики, correlation ID и трассировка | 10, 17, 20 |
| Kafka: ключи, partitions, группы потребителей, offsets и повторное чтение | 13, 14, 15, 16 |
| ClickHouse: таблицы, дубли, поздние события и сверка | 15, 16 |
| Тестирование: модули, контракты, база данных, гонки и отказы | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 |
| Docker, CI, выпуск и миграции | 2, 4, 18, 19 |
| Безопасность приложения и восстановление из резервной копии | 18 |
| Нагрузка и system design: SLO, ёмкость и компромиссы | 17, 20 |
| Работа в команде: оценка задачи, merge request, ревью, отчёт об ошибке и разбор сбоя | 0, 19, 20 |
| Работа с ИИ-ассистентом и проверка его предложений | 1, 20 |
Посмотрите методичку целиком
Так устроена каждая из 21 методички: теория, разобранные примеры, задание, типовые ошибки, самопроверка и вопросы собеседования. Методичка первой недели открыта полностью.
Методичка недели 1 (PDF, 31 страниц) Как проходит обучение Оставить заявку
Текст программы собран из текущих версий методичек и обновляется вместе с ними.