Разбираюсь в задачах без готового решения
Беру на себя путь от первых неясных требований до работающего результата. Погружаюсь в незнакомую систему, исследую ограничения и нахожу рабочий путь.
Открыт к предложениям
C++ · Backend · Разработка с AIБатуми, Грузия
Решаю сложные задачи и довожу проекты до результата
01 / Что делаю
Беру на себя путь от первых неясных требований до работающего результата. Погружаюсь в незнакомую систему, исследую ограничения и нахожу рабочий путь.
От модели данных и границ компонентов до взаимодействия системы целиком. Учитываю ограничения продукта и продумываю, как провести решение через реализацию и внедрение.
Организую разработку с агентами: постановку задач, подготовку контекста и окружения, работу над решениями, ревью и проверку результата. Выстраиваю обратную связь через тесты и измерения.
Выстраиваю этапы, согласовываю зависимости между командами, помогаю пройти сложные участки и довести работу до релиза.
Исследую задержки, потребление памяти и скорость работы. Нахожу причины, проверяю гипотезы экспериментами и меняю систему на том уровне, где получается нужный эффект.
Делаю сборки, проверки и развёртывание воспроизводимыми. Ускоряю обратную связь, автоматизирую повторяющуюся работу и создаю удобный процесс доставки изменений.
Внедряю изменения в сложные долгоживущие кодовые базы с бережным отношением к стабильности и качеству.
Разбираюсь в смысле задачи и данных, пересматриваю ограничения и нахожу, какую сложность можно убрать.
02 / Горжусь
По условиям задачи нужно было организовать рассылку рыночных данных с низкими задержками нескольким получателям. Я разбил работу на несколько этапов.
Горжусь, что за несколько дней удалось собрать решение в совершенно новой для меня области, достойно показавшее себя даже на фоне опытных соперников из индустрии HFT.
Проектное разделение данных было родовой травмой 2ГИС. Пользователю были доступны сценарии только в рамках одного города, даже когда города находились рядом, как, например, Самара и Тольятти. Нельзя было построить маршрут из одного в другой, нельзя было искать результаты на границе и видеть их на общей карте. Для решения этой проблемы потребовалось полностью переосмыслить всю архитектуру не только приложения, но и потоков данных, питавших его. Нужно было переработать почти каждый модуль ядра, начиная от системы обновлений и заканчивая модулями карты, справочника и другими.
Я отвечал за доставку этой задачи не только в рамках нашей команды, но в рамках всего департамента. Декомпозировал работы, выявлял кросс-командные зависимости, составлял и актуализировал роадмап, проводил регулярные синки со смежными командами, прорабатывал архитектуру и лично участвовал в имплементации не одного сложного компонента.
Большим челленджем было как в принципе провести такие большие изменения, занявшие больше двух лет, от начала реализации до выпуска, так и делать это внутри сложного живого продукта с регулярным релизным циклом и множеством других постоянно добавляющихся фич, которые нередко делались без оглядки на новую реальность.
Чертовски приятно было услышать от руководителя мобильного направления, что эту задачу мы вытянули во многом благодаря мне.
Вторая родовая травма 2ГИС — офлайн-модель продукта, выросшего из CD-дисков для ПК. В 2020-х требовать после установки скачать сотни мегабайт, чтобы просто начать пользоваться приложением, было слишком большим барьером. Пользователи уходили на экране загрузки баз, и это мешало всерьёз вложиться в рекламу: заметная часть бюджета улетела бы в трубу ещё до знакомства людей с продуктом.
Онлайн-режим должен был выйти к рекламной кампании, календарный слот которой жёстко зафиксировали за год. Предстояло масштабно изменить живое приложение: карта, справочник, поиск и загрузка данных должны были поддерживать несколько режимов одновременно, при этом продолжались регулярные релизы и разработка других фич. Я вёл фичу целиком, находя сложные компромиссы между ограничениями архитектуры, продуктовыми требованиями, производительностью и сроками.
В середине разработки я понял, что выбранная схема взаимодействия с бэкендом не оправдывает ожиданий, и разработал новую, совместившую быстрое время ответа и консистентность данных, критичную для продукта и особенностей устройства ядра.
За несколько месяцев до срока стало очевидно, что текущими силами нам не справиться, поэтому эскалировал вопрос о привлечении дополнительных инженеров из смежных команд и в очередной раз с продакт-менеджером пересмотрел скоуп must-have задач первого релиза. Пересборка плана, ежедневная координация, понятные контрольные точки и последовательная стабилизация помогли довести задачу до выпуска.
Онлайн-режим вышел вовремя, снизив уровень оттока пользователей сразу после установки с 22,3% до 7,3%.
Основной челлендж был попасть в жёсткий годичный дедлайн — без права на ошибку. Хотя многие позже признавали, что более реалистичный срок подобных изменений — пара лет.
Новый 3D-движок Zenith разрабатывался около 7 лет и был призван принести в продукт фундаментальные изменения: бесшовную карту, онлайн-режим и гибкую стилизацию. При этом он использовал совершенно новый формат хранения данных, несовместимый с предыдущим, а также предоставлял другой API. Эти обстоятельства делали задачу его интеграции чрезвычайно сложной и не оставляющей обходных путей. Я взялся за неё и шаг за шагом проработал различные аспекты.
Для начала собрал прототип, позволивший прощупать весь путь от обращений к API движка через новые интерфейсы в ядре и до отрисовки кадров внутри Qt/QML-приложения.
Затем спроектировал механизм рантайм- и компайлтайм-флагов для изоляции новой интеграции. Это было важно, чтобы вести долгую разработку в основной ветке, но не влиять на боевой код до релиза.
Далее каждый потребитель движка нужно было либо продублировать, сделав интеграцию через новый API и используя его возможности по максимуму, либо временно переключить на адаптер ради скорости интеграции.
Отдельной задачей стал вопрос миграции. Мы не могли позволить пользователям со старыми данными одномоментно потерять возможность пользоваться приложением, поэтому я предложил механизм рантаймового выбора движка в зависимости от формата доступных данных с пересозданием всей сессии ядра в случае переключения между несовместимыми датасетами. Это позволило пользователям плавно перейти на новую версию без негативных эффектов.
Наиболее сложным было соблюсти жёсткое требование по обратной совместимости с данными, когда оно не было заложено на уровне самого движка. Пришлось изобрести механизм внешней детекции и переключения движков, а также научить весь код ядра работать с обоими.
Существовавший в продукте механизм доставки обладал ворохом проблем: большой пик по памяти при загрузке и обновлении данных, серьёзное фоновое потребление памяти, скорость выборок и надёжность хранения данных. Решение каждой из них по отдельности требовало больших усилий в сумме без гарантированного результата.
Я предложил альтернативный подход с полной переработкой всей системы при сопоставимых усилиях. В центр решения поставил нашу компактную проприетарную read-only kv-базу, обеспечивающую высокую степень сжатия и быстрые выборки. Для удовлетворения EAV-модели данных со сложными критериями фильтрации добавил к ней hash-индексы, по одному на каждый уникальный фильтр в продукте.
Для обеспечения высокой частоты доставки без перекачивания всей базы разработал систему патч-баз. Различие между двумя слепками данных превращается в отдельную маленькую базу такого же формата, так что для заданной опорной базы достаточно перезагружать только патч. Клиентский код склеивает результаты на лету, получая необходимое целевое представление.
Перед принятием решения о таком большом рефакторинге я доказал на дешёвых прототипах соответствие целевым метрикам по скорости работы и размеру данных, что позволило инвестировать в него без риска неопределённости успеха.
Задача не была в моей зоне ответственности, но я увидел возможность потратить выделенные ресурсы эффективнее и предложил альтернативное решение, доказав его перспективность на берегу. В задаче сочетается большое количество грамотно скомбинированных архитектурных приёмов, позволивших в совокупности достичь высоких показателей производительности, надёжности и в то же время простоты и прямолинейности получившейся системы.
На заре своего развития CI в команде ядра 2ГИС был минимальным набором задач на Jenkins с постпроверками master-ветки, сконфигурированных разработчиками. Под моим началом он превратился в выделенное направление со своими целями, приоритетами и роадмапом, а инфраструктура приобрела очертания зрелого решения.
Горд, что инструменты, разработанные мной и моим подчинённым, стали ежедневно обеспечивать быструю, надёжную и удобную поставку кода разработчиков.
Так как основным инструментом взаимодействия наших пользователей с кластером была TS-библиотека, мы решили перевести и наш CLI с Go на неё, чтобы не держать две реализации с периодически отличающимся поведением.
Go задавал высокую планку переносимости для исполняемого файла CLI, поэтому и новое решение мы хотели видеть одним статически слинкованным бинарём.
Это оказалось непростой задачей. Наша сборочная система была построена на Nix, и теоретически он позволял собрать пакеты из своего каталога статически. Но на практике всё оказалось не так радужно. Пришлось обойти множество ошибок сборки, последовательно добавляя патчи и флаги к конфигурации. Часть проблем оказалась специфической для macOS, например, с codesign в герметичном сборочном окружении Nix.
Следующие неприятности возникли при попытке запаковать наш CLI на oclif в SEA (Single Executable Application — экспериментальная возможность Node.js для однофайловой поставки). Пришлось фактически реализовать маленькую VFS, пропатчив require.
В статически собранном Node.js нельзя было пользоваться динамической загрузкой библиотек, поэтому пришлось переопределять некоторые зависимости на WASM-реализации вместо нативных.
Также пришлось повозиться со скоростью запуска, чтобы пользователи не испытывали дискомфорта при переходе на новую версию.
В этой задаче сложились вместе необходимость разобраться и построить решение поверх незнакомого технологического стека и чудовищное количество препятствий, которые нужно было преодолеть по пути. Тем приятнее было проявить свои сильные стороны и довести её до результата.
03 / Опыт
Backend-разработчик
Батуми, Грузия
Разрабатывал серверные компоненты, инструменты для разработчиков и инфраструктуру блокчейна nil.
Вёл задачи от архитектуры и прототипов до интеграции, развёртывания и проверки на реальном кластере.
Расследовал критичные сбои и организовывал их устранение, помогая команде пройти путь от монолитного прототипа до распределённой инсталляции.
Ведущий C++-разработчик
Батуми, Грузия
Занимался архитектурой, данными, памятью и производительностью C++-ядра 2ГИС. Брался за задачи, решение которых ещё предстояло найти. Делал прототипы, проверял архитектурные гипотезы на реальных данных, согласовывал работу со смежными командами и доводил интеграции до релиза.
Руководитель группы разработки ядра мобильного приложения
Новосибирск, Россия
Руководил выпуском крупных фич, отвечал за архитектуру, план и результат команды. Совмещал личную разработку и техническое сопровождение с межкомандной координацией. Проводил найм и развитие инженеров в команде, включая наставничество и ведение отдельных направлений.
C++-разработчик
Новосибирск, Россия
Отвечал за разработку крупных кросскомандных фич. Проектировал архитектуру, налаживал кросскомандное взаимодействие, дробил работу на подзадачи, составлял из них роадмап и поддерживал его актуальность, реализовывал ранние прототипы для валидации гипотез.
CI-инженер
Новосибирск, Россия
Построил CI для большого кроссплатформенного C++-проекта: сделал инфраструктуру воспроизводимой, сократил полный цикл сборок и внедрил блокирующие проверки на вливание. Определял развитие направления, нанимал инженеров и постепенно передал им самостоятельное ведение CI.
C++-разработчик
Новосибирск, Россия
Наладил поставку новой десктопной версии на Qt/WebUI под Ubuntu и macOS.
Исследовал возможность миграции на QtWebEngine, изучив исходники Qt и Chromium.
Младший C++-разработчик
Новосибирск, Россия
Поддерживал и дорабатывал десктопную версию 2ГИС, приносившую основной доход компании.
Perl-разработчик
Новосибирск, Россия
Автоматизировал внутренние процессы департамента производства данных и разрабатывал веб-краулеры для сбора данных об организациях из открытых источников.
Frontend-разработчик
Новосибирск, Россия
Разрабатывал веб-проекты и руководил выпуском нескольких новых проектов с жёсткими сроками.
Улучшил процесс вёрстки за счёт внедрения БЭМ и SCSS.
Роли, задачи и технологии.
04 / Задачи
Исследовал граф зависимостей карты и избавился от синхронного ожидания готовности нескольких тяжёлых сервисов, а также добавил предварительный прогрев ещё нескольких. Удалось сократить время инициализации подсистемы до 2 раз, а стили подготавливать на 200–300 мс раньше.
Также обнаружил многократную подготовку одних и тех же данных внутри 3D-движка. Собрал прототип с кэшированием результатов и добился ускорения показа карты на слабых устройствах на 20%. Передал наработки команде движка, и коллеги довели их до релиза.
Помог с нуля сформировать команду мобильного SDK, организовав процесс найма. Отсматривал резюме, рассылал письма кандидатам, проводил собеседования. Сам готовил вакансии и обращения, применяя индивидуальный подход на основе опыта кандидатов. В сжатые сроки удалось нанять более 10 сильных C++-разработчиков в несколько команд.
Написал автоматизированный краулер, расширивший базу кандидатов с меньшей нагрузкой на HR-специалистов.
Передал процесс найма коллегам: поделился материалами, вместе отработали встречи-знакомства и технические интервью, постепенно делегировал ранние этапы.
Схема работы с данными в мобильном приложении 2ГИС подразумевала единовременное отображение в память всех контейнеров данных на старте. Это гарантировало высокую производительность, но приводило к повышенному расходу виртуального адресного пространства, которое на мобильных устройствах (iOS и 32-битных Android) зачастую было весьма ограничено. Проблема грозила усугубиться, когда добавилось требование обновлять данные на лету — в этот момент нас ждал и вовсе двухкратный пик.
Я подошёл к проблеме системно и начал с её фиксации. Подготовил стенд на Google Benchmark. Для него реализовал memory manager на основе gperftools, подсчитывающий как обычные аллокации, так и файловые отображения. Когда бейзлайн стал формализован и воспроизводим, приступил непосредственно к оптимизации.
Вместо жадного отображения всего контейнера переделал реализацию VFS на отображение отдельных регионов по требованию. Так как паттерн обращения к данным почти всегда затрагивает только небольшое подмножество файлов в контейнере, это радикально снизило футпринт, что и подтвердил бенчмарк.
В рамках разработки блокчейна nil мы двигались поступательно от монолитного приложения к распределённому кластеру. Мне досталась задача по отделению RPC-ноды от валидатора.
Для удобства разработки и отладки приложение должно было сохранить возможность работать в рамках одного процесса. Поэтому я выделил транспортный слой в абстракцию и с помощью неё изолировал RPC-компонент от знания, обращается ли он к валидатору локально или по сети. В качестве сетевого транспорта мы использовали libp2p — организовал поверх неё протокол на Protobuf. Для минимизации бойлерплейта активно использовал дженерики и рефлексию в Go. Получилось лаконично и выразительно.
Разные типы узлов в разных конфигурациях должны были обслуживать разные наборы протоколов: read-only вызовы, rw-, отладочные. Отрефакторил код регистрации, разделив интерфейсы на непересекающиеся иерархии, обеспечив гибкую композируемость без лишних ветвлений.
В рамках задачи не ограничился кодом, а также настроил деплой новой конфигурации в тестовом и боевом контуре, попутно разобравшись в устройстве инфраструктуры.
Инициировал и довёл переход всей C++-разработки компании на C++20. Внёс необходимые изменения с учётом ограничений Clang, GCC, MSVC и зависимостей проекта. Согласовывал изменения между командами и оперативно исправлял возникающие проблемы. После включения стандарта активно продвигал его возможности среди коллег через собственный код и ревью: concepts, spaceship operator, consteval, template lambdas и т. д.
Внедрил clang-format и инициировал формализацию негласных правил оформления кода.
Расследовал и исправлял проблемы со временем компиляции. Через -ftime-trace и Templight++ анализировал тяжёлые include-зависимости и дорогие шаблонные инстанциации. Подобрал оптимальный состав PCH.
Продвигал гарантии времени компиляции через систему типов. В частности, реализовал библиотеку незануляемых идентификаторов и zero-cost optional-обёрток над ними.
Внедрял инструменты для улучшения выразительности и простоты кода, такие как полифил std::source_location и magic_enum.
Сборка nil под macOS в GitHub CI на первых порах использовала альтернативный путь сборки в обход штатного для проекта Nix. Это мешало выпустить новую версию CLI, имевшую нетривиальный процесс сборки в Nix. Я взялся исправить это и унифицировать процесс сборки под Linux и macOS.
Важной деталью пазла оказался кэш Nix. Без него сборка CLI каждый раз пересобирала Node.js с нуля и длилась больше часа, попутно выжигая лимиты раннера. В отличие от autoscale-раннеров под Linux, в которых кэш конфигурировался в момент разворачивания ноды, для стандартных раннеров macOS этот процесс должен был быть частью сборочного воркфлоу.
Успешная сборка должна записывать в S3-бакет AWS новые артефакты, а значит, и иметь права на это. Я не стал складывать долгоживущий токен в секреты GitHub, а пошёл по рекомендованному для таких случаев пути через получение короткоживущих токенов посредством OIDC-интеграции GitHub CI и AWS. Для этого в Terraform добавил OIDC-провайдер и заиспользовал соответствующую роль в сборочном workflow.
Таким образом удалось решить проблему неоднородности сборок под разные платформы и получения требуемых артефактов.
В рамках проекта мобильного SDK развивалась новая версия уже существовавших в продукте компонентов: модуля этажных планов, пробок, визуализации маршрутов и т. д. Нашей команде они были нужны для реализации онлайн-режима, но я видел, что параллельно идёт работа по редизайну навигатора, в котором должна была добавиться миникарта. Я понимал, что общее решение должно учитывать обе задачи. Удалось собрать всю картину и спроектировать изменения, которые приносили пользу сразу обоим направлениям и не противоречили друг другу.
Для интеграции нужно было состыковать разные жизненные циклы объектов в ядре и SDK, не получить циклические и висячие ссылки и избежать гонок при инициализации. Приходилось искать довольно тонкие переходные решения, учитывающие устройство обеих систем. Также в существующей архитектуре основная карта была всегда, а множество компонентов хотели получать к ней доступ уже на старте. Нужно было вписать в эти ограничения дополнительные карты. Я придумал механизм управления ими, согласовал его с платформами, реализовал изменения и собрал прототип миникарты в C++-ядре и в Android-приложении на Qt/QML.
В миникарте использовались компоненты SDK для отображения и постепенного «съедания» линии маршрута, маркера местоположения, слежения за ним, а также новые стили. И она обладала важным свойством для подобной интеграции. Это был новый компонент, где можно было использовать возможности SDK без сложной замены уже существующей реализации. Поэтому отрисовка маршрута средствами SDK появилась в миникарте гораздо раньше, чем в основном навигаторе. Мы нашли изолированный участок, на котором новый код уже приносил пользу в продукте, и его дальнейшее развитие должно было сохранять эту совместимость.
Переходная интеграция получилась местами хрупкой, но её использование в основной ветке изменило ответственность за совместимость: дальнейшие изменения SDK уже должны были учитывать работающее приложение. Нам больше не приходилось постоянно догонять их в стороне.
05 / Проекты
Создал сервис карт желаний от идеи до работающего продукта с реальными пользователями и платежами.
Занимался проектом от и до: принимал продуктовые решения, проектировал архитектуру, разрабатывал, тестировал и эксплуатировал сервис в Google Cloud.
06 / Стек
Стек не важен — важно, чтобы задача была сделана.
Делаю вещи
07 / Публикации
Доклады на конференциях, интервью об архитектуре и разбор сложной технической проблемы.
08 / Образование
Прикладная математика и информатика
Новосибирск, Россия
09 / Контакты
Ищу команду с классным продуктом, амбициозными задачами и техническими вызовами.
Открыт к предложениям
Связаться