Открыт к предложениям Батуми, Грузия · удалённо, готов к релокации

Магомед Бонкуров

Senior Java Developer 6 лет 5 месяцев коммерческой backend-разработки

Пишу и вывожу в прод backend на Java и Spring. Последние три с половиной года — образовательная платформа «Сберкласс», где почти вся нагрузка приходится на первые минуты урока. Занимаюсь тем, что обычно и болит на таких проектах: асинхронные интеграции через Kafka, тяжёлые запросы в PostgreSQL, нагрузочные тесты и разбор того, что упало на проде. Самое заметное из сделанного — выдачу задания ученику удалось ускорить с 1,9 секунды до 260 мс по p95.

  • 6,5 лет коммерческой Java-разработки
  • ~4 года в грейде Senior
  • ~700 RPS на сервисе после оптимизации
  • Java 8→17 участвовал в миграции версий
Листайте вниз

01 О себе

О себе

Я backend-разработчик на Java. Начинал с CRUD-модулей и отчётов. Сейчас на мне сервисы целиком: схема данных и контракты, поведение под нагрузкой, разбор того, что упало ночью.

Больше всего люблю разбираться, когда система вдруг ведёт себя не так, как ожидалось. Упал соседний сервис — что теперь с моим эндпоинтом. После вроде безобидного релиза p95 подскочил втрое. Куда-то утекает память, а консьюмер зачем-то обрабатывает одно событие дважды. Почти любое решение в бэкенде — это компромисс, и я стараюсь заранее понимать, чем плачу: где-то жертвую строгой консистентностью ради доступности, где-то простотой ради скорости. Не люблю, когда минусы решения всплывают уже на проде, поэтому проговариваю их сразу — и себе, и команде.

Работал и с legacy, и с проектами с нуля. В чужой код захожу осторожно: сначала обкладываю тестами и метриками, потом уже меняю — переписывать всё подряд не тянет. Провожу ревью, менторю джунов и мидлов, участвую в технических собеседованиях.

Английский — уверенный B1. Говорю и пишу, свободно читаю документацию и исходники, веду рабочую переписку и код-ревью на английском.

02 Технологический стек

Технологический стек

Язык и платформа

Фреймворки

Данные и хранилища

Интеграции и API

Тестирование и нагрузка

Инфраструктура и наблюдаемость

Наведите на технологию или нажмите — коротко расскажу, что это и как я это использую.

03 Опыт работы

Опыт работы

  1. Март 2025 — настоящее время

    Senior Java Developer @ «Куб»

    Разработка ПО и системная интеграция, Грозный, работа удалённо. Отдел разработки ~25 человек. Отвечаю за backend платформы обработки заявок и начислений, 3 сервиса в личной зоне ответственности, ~450 RPS в пике.

    • Заменил синхронный REST-обмен между сервисами заявок и начислений на асинхронный через Kafka: transactional outbox у продюсера, идемпотентный консьюмер с дедупликацией по ключу события. События перестали теряться при недоступности смежного сервиса. Сознательно разменял строгую консистентность на eventual — распределённую транзакцию отклонил из-за связности и стоимости поддержки.
    • Ускорил основную ручку: через pg_stat_statements и EXPLAIN (ANALYZE, BUFFERS) нашёл N+1 и отсутствующий индекс, заменил выборку в цикле на batch, добавил составной индекс — p95 упал с 820 мс до 190 мс, CPU базы в пик снизился на ~35%.
    • Обернул вызовы внешних систем в Resilience4j (circuit breaker, таймаут, retry с экспоненциальной задержкой): недоступность смежного сервиса перестала выражаться в зависших потоках и растущей очереди запросов — сервис быстро отвечает отказом вместо того, чтобы держать соединение.
    • Спроектировал партиционирование таблицы событий по месяцам (~300 млн строк) и вынес ночные агрегаты в materialized view — отчётные запросы перестали конкурировать с OLTP, построение отчёта сократилось с 4 минут до 40 секунд.
    • Веду ревью, менторю junior- и middle-разработчиков, участвую в технических собеседованиях.
    Стек
    • Java 17
    • Spring Boot
    • Kafka
    • PostgreSQL
    • Redis
    • Docker
    • REST
    • GraphQL
    • Resilience4j
    • JMeter
    • Grafana
    • Kibana
    • GitLab CI/CD
    • Gradle
    • Flyway
  2. Октябрь 2022 — Февраль 2025

    Старший Java-разработчик @ «Сберобразование»

    Образовательная платформа «Сберкласс». Перешёл из компании-подрядчика (Haiku dev) в продуктовую команду заказчика на тот же продукт с расширением зоны ответственности.

    • Перевёл 5 тяжёлых выборок с JPA на JdbcTemplate там, где маппинг в граф сущностей был лишним; горячие места искал через pg_stat_statements и EXPLAIN (ANALYZE, BUFFERS), добавил составные индексы, ночные агрегаты вынес в materialized view — сборка отчёта по успеваемости класса сократилась с 90 с до 12 с.
    • Закэшировал профили и справочники в Redis (cache-aside, TTL 15 минут с инвалидацией по событию изменения) — снял ~40% чтений с PostgreSQL в учебные часы, p95 профильных ручек стал стабильным вне зависимости от пика.
    • Провёл нагрузочное тестирование в JMeter: потолок был ~350 RPS, первыми ложились пул HikariCP и синхронный вызов сервиса контента. После расширения пула и асинхронного вызова сервис держал ~700 RPS при p95 ниже 400 мс.
    • Разработал GraphQL-слой для клиентских приложений: устранил N+1 в резолверах батчингом через DataLoader, ограничил глубину и сложность запроса — число обращений к БД на сборку карточки упало с десятков до 2–3.
    • Реализовал REST API модулей заданий, успеваемости и результатов тестов, авторизацию вынес на Keycloak (OAuth2/OIDC, JWT) с ролевой моделью ученик/учитель/администратор; контракты описал в OpenAPI.
    • Покрыл модуль интеграционными тестами на Testcontainers (реальные PostgreSQL и Kafka) до ~75% по критичному коду; вёл ревью ~15 PR в неделю, онбордил двух разработчиков в модуль.
    Стек
    • Java 11 → 17
    • Spring Boot
    • Spring Data JPA
    • JdbcTemplate
    • PostgreSQL
    • Kafka
    • Redis
    • Keycloak
    • GraphQL
    • REST
    • Docker
    • Bitbucket
    • Bitbucket Pipelines
    • Testcontainers
    • JMeter
    • Grafana
    • Kibana
    • Flyway
  3. Октябрь 2021 — Сентябрь 2022

    Java-разработчик @ «Haiku dev»

    Подрядная разработка на продукте «Сберкласс»: аутстафф-команда 6 человек внутри модуля контента и заданий.

    • Нашёл утечку памяти в сервисе контента по heap dump: незакрывающийся кэш держал ссылки на объекты пользовательских сессий. После фикса сервис перестал уходить в OOM примерно раз в двое суток, ночные перезапуски прекратились.
    • Закрыл 8 фич в модуле заданий целиком: REST-контроллеры и GraphQL-резолверы, миграции схемы, тесты, выкатка на dev/staging и приёмка с аналитиком.
    • Разбирал дефекты с прода по логам в Kibana. Самый тяжёлый: двойная отправка ответа учеником создавала дубль результата — закрыл уникальным индексом по бизнес-ключу и идемпотентным ключом запроса, повторную отправку API возвращает как конфликт с актуальным состоянием.
    • Написал ~80 тестов на JUnit и Mockito и вычистил ~15 флакающих — прогон в CI перестал падать на ровном месте, релизы не откладывались из-за красной сборки.
    Стек
    • Java 8/11
    • Spring Boot 2.x
    • Spring Data JPA
    • JDBC
    • Caffeine
    • Eclipse MAT
    • PostgreSQL
    • GraphQL
    • REST
    • Maven
    • Bitbucket
    • JUnit
    • Mockito
    • JMeter
    • Kibana
  4. Ноябрь 2019 — Апрель 2021

    Java-разработчик @ «ChemCool»

    Образовательный портал для учащихся 8–11 классов (проверка знаний, тесты по предметам): 4 сервиса, команда 5 человек, работал в проде с несколькими тысячами пользователей.

    • Реализовал backend клиентского сервиса на Spring Boot: ~30 REST-эндпоинтов (каталог тем, прохождение теста, выдача результатов), доступ к PostgreSQL через Spring Data JPA и Hibernate.
    • Настроил обмен событиями между сервисами через Apache Kafka: результаты теста уходили в сервис статистики асинхронно — расчёт перестал блокировать ответ пользователю, всплески после уроков не роняли основной сервис.
    • Закрыл аутентификацию и авторизацию на Spring Security: вход по JWT, разграничение ролей, ограничение доступа к чужим результатам.
    • Покрыл свои модули тестами до ~65% по критичному коду — повторяющаяся регрессия ушла из релизов.
    Стек
    • Java 8
    • Spring Boot
    • Spring Security
    • Hibernate / JPA
    • HQL
    • PostgreSQL
    • Apache Kafka
    • REST
    • Maven
    • Git
    • JUnit
    • Mockito
    • OpenAPI
    • React (базово)

04 Разбор задач

Разбор задач

Три задачи, которые я разбираю на собеседованиях подробнее всего. Здесь важно не «что я использовал», а какой был выбор, что я взвешивал и чем за это заплатил.

Отчёт по успеваемости класса собирался полторы минуты

Сберкласс · Сберобразование · 2023

Задача

Учитель открывал сводку по классу и ждал полторы минуты. В учебные часы такие запросы шли пачками и отъедали CPU у основной базы, а вместе с ним подтормаживала и выдача заданий ученикам — основной сценарий платформы. Жаловались при этом абстрактно: «платформа тупит», про сам отчёт никто не упоминал.

Что сделал

  • Нашёл реальный источник нагрузки: не гадал, а взял pg_stat_statements и отсортировал по суммарному времени. Верхние строки — не сам отчёт, а 5 выборок вокруг него.
  • Прогнал их через EXPLAIN (ANALYZE, BUFFERS). Классический N+1: на каждого ученика уходил отдельный запрос за результатами, плюс Hibernate поднимал в память граф сущностей, из которого нужны были три поля.
  • Там, где маппинг в сущности был лишним, перевёл выборки на JdbcTemplate с плоской проекцией. Не «отказался от ORM» — заменил ровно те места, где ORM мешал.
  • Добавил составные индексы под фактические предикаты (проверил, что планировщик их действительно берёт, а не игнорирует).
  • Ночные агрегаты вынес в materialized view с обновлением по расписанию: свежесть данных на сутки — приемлемая цена, отчёт за прошлый период не обязан быть онлайновым.

Результат

Отчёт стал собираться за 12 секунд вместо 90. Но главным был даже не сам отчёт: тяжёлые аналитические запросы перестали конкурировать с боевой нагрузкой, и жалобы на тормоза в учебные часы прекратились.

События терялись, когда смежный сервис лежал

«Куб» · 2025

Задача

Сервис заявок ходил в сервис начислений синхронно по REST. Если тот лежал, заявка сохранялась, а начисление не создавалось. Расхождение вылезало только через сутки в сверке, и правили его руками. Ретрай прямо в том же методе не спасал: сервис мог упасть в момент между коммитом заявки и повторной попыткой.

Что сделал

  • Отказался от распределённой транзакции: она связала бы два сервиса в один релизный цикл, а стоимость поддержки двухфазного коммита здесь не окупалась.
  • Взял transactional outbox: событие пишется в таблицу outbox в той же транзакции, что и заявка. Атомарность обеспечивает сама база — не приложение.
  • Отдельный relay читает outbox и публикует в Kafka. Ключ сообщения — id сущности, поэтому события по одной заявке приходят в одну партицию и не переупорядочиваются.
  • Kafka даёт at-least-once, значит дубли неизбежны. Консьюмер держит таблицу processed_events и отбрасывает повтор по ключу события — обработка стала идемпотентной.
  • Что не удалось обработать за 5 попыток, уходит в DLQ и разбирается вручную, вместо того чтобы крутиться в бесконечном retry и забивать партицию.

Результат

Теперь падение сервиса начислений не ломает данные: события лежат в outbox и доезжают, когда он поднимется. Ручные правки после сверки прекратились. Расплачиваемся за это тем, что согласованность стала не мгновенной, а с задержкой — с бизнесом мы это обсудили заранее, для такого сценария задержка нормальная.

Сервис уходил в OOM примерно раз в двое суток

Сберкласс · Haiku dev · 2022

Задача

Сервис контента раз в двое суток падал по OutOfMemoryError. Его просто перезапускали по ночам по расписанию и жили так — почему он течёт, никто не докапывался. На dev воспроизвести не выходило: там столько трафика не набиралось.

Что сделал

  • Включил -XX:+HeapDumpOnOutOfMemoryError, дождался следующего падения и получил дамп с реальными данными вместо догадок.
  • Разобрал дамп в Eclipse MAT: доминатором оказался самописный кэш на ConcurrentHashMap — без ограничения размера и без TTL. Он удерживал объекты пользовательских сессий, и GC не мог их собрать.
  • Заменил самописный кэш на Caffeine с maximumSize и expireAfterWrite; проверил, что при вытеснении не остаётся сильных ссылок из слушателей.
  • Прогнал сервис под нагрузкой и посмотрел на график кучи после нескольких Full GC: раньше базовая линия ползла вверх между сборками, теперь возвращается на место. Добавил алерт на заполнение old gen, чтобы поймать повтор заранее.

Результат

Падения прекратились, ночной перезапуск по крону убрали за ненадобностью. Заодно паузы GC стали короче и ровнее — сборщику больше не приходилось раз за разом обходить постоянно растущий живой набор объектов.

05 Архитектура систем

Архитектура систем

Две схемы, которые я чаще всего рисую на собеседовании. Обе — про то, как система ведёт себя, когда что-то ломается, а не когда всё хорошо.

Контур сервиса образовательной платформы
проверка JWT REST REST события подписка Веб-клиент React Мобильное приложение API Gateway маршрутизация, rate limit Keycloak OAuth2 / OIDC BFF GraphQL Сервис заданий Spring Boot Сервис прогресса Spring Boot Kafka события обучения PostgreSQL партиции по месяцам Redis кэш профиля Сервис статистики считает прогресс
  • Клиент
  • Сервис
  • Хранилище
  • Брокер (асинхронно)
  • Внешняя система

Почему BFF, а не прямой доступ к сервисам. У веба и мобильного разные потребности в данных: клиент собирал карточку урока из 4–6 вызовов и упирался в задержки сети. GraphQL-слой собирает ответ на сервере, клиент делает один запрос. Плата за это — свой слой, который надо версионировать и защищать от тяжёлых запросов (ограничение глубины и сложности). Почему прогресс уезжает в Kafka, а не пишется синхронно. Запись прогресса не должна ронять урок: если аналитика недоступна, ученик этого не замечает. Разменял строгую консистентность на eventual — но тогда потребителю нужна идемпотентность, и это отдельная схема ниже.

Надёжная доставка событий: transactional outbox
commit тот же commit poll publish at-least-once проверка после N retry Сервис-продюсер Основные таблицы PostgreSQL Таблица outbox та же транзакция Relay читает и публикует Kafka topic ключ = id сущности Консьюмер processed_events ключ события DLQ разбор вручную
  • Сервис
  • Хранилище
  • Брокер (асинхронно)

Задача. Сохранить данные и отправить событие так, чтобы не получилось «в базе есть, в Kafka нет» или наоборот. Отправка из того же метода после коммита не спасает: между коммитом и publish сервис может упасть. Решение. Событие пишется в таблицу outbox в той же транзакции, что и данные, — атомарность даёт сама база. Отдельный relay читает outbox и публикует в Kafka. Цена. Kafka даёт at-least-once, поэтому дубли неизбежны: консьюмер держит processed_events и отбрасывает повтор по ключу события. Что не смогли обработать за N попыток — уходит в DLQ, а не крутится в бесконечном retry, забивая партицию.

06 Контакты

Открыт к предложениям на Senior Java

Работаю удалённо, готов и к релокации. Живу в Батуми, но держусь московского времени, так что с командами в России пересекаемся весь рабочий день. Быстрее всего поймать меня в Telegram — обычно отвечаю в течение часа. Тестовое и техническое интервью — без проблем.