
Пользователь Reddit опубликовал в r/rustjerk сгенерированный ИИ пост под названием «Почему наш CTO запретил использовать Rust после одного переписывания кода». Очевидно, что этот рассказ выдуман, но у меня есть история, похожая на него в том смысле, что успех проекта на Rust привёл к прекращению использования этого языка в компании.
Несколько лет назад я работал в стартапе-«единороге», во время пандемии развивавшемся невероятно быстро. Его основное приложение было написано на Ruby on Rails, а часть инструментария для работы с видео — на Node.js, но мы никак не применяли быстрые компилируемые языки наподобие Rust и Go. Через пару месяцев после моего прихода в компанию нам нужно было реализовать работающий в реальном времени сервис, который бы позволял нам получать информацию о том, кто из пользователей находится онлайн (то есть в профиле есть зелёная точка) и чем они занимаются (например: N пользователей смотрят презентацию X, M пользователей находятся в разделе маркетинга и так далее). Ничего особо сложного, но мы рассчитывали на изначальный рост до ста тысяч пользователей. Эта цель тоже не особо сложна, но большинство разработчиков согласилось, что Ruby — не лучший выбор для этого.
Начались дискуссии о выборе языка. Команда, которая должна была писать сервис, выбрала Rust, но руководство пока не было уверено, поэтому предложило написать в качестве proof of concept несколько сервисов, по одному на каждом из языков: Elixir, Rust, Ruby и Node.js. Тогда я был в отпуске, поэтому, честно говоря, не знаю, почему в этот список не вошёл Go, который мог бы стать вполне подходящим кандидатом. Спустя примерно неделю proof of concept были завершены, и мы провели их бенчмаркинг. Я не был в команде, проводившей их, но выполнял множество задач, связанных с производительностью и наблюдаемостью, поэтому помогал в бенчмаркинге. Результаты меня не удивили: Rust оказался самым быстрым и потреблял меньше всего памяти, затем шли Elixir, Node.js и Ruby. Впрочем, тонкость в том, что версию на Node.js в конечном итоге пришлось бы сделать распределённой из-за однопоточной среды выполнения. Ещё один интересный аспект заключался в том, что версия на Rust содержала проблему, вызванную тем, что разработчик при отправке сообщений клиентам использовал async future — они обходили всех клиентов, чтобы получить список каналов, в которые нужно выполнять передачу, что при высоких нагрузках блокировало среду выполнения на несколько секунд. Если знаешь, что делать, эту проблему легко решить, но новичок, скорее всего, сделал бы всё правильнее на Go или Elixir, чем на Rust. Впрочем, возможно, я и не прав, ведь другие proof of concept (PoC) были написаны людьми, имевшими опыт работы с соответствующими языками, и только PoC на Rust был написан новичком в этом языке.
Обсудив бенчмарки, эргономику языков и то, насколько они хорошо подходят для компании, команда снова выбрала Rust. Ещё один любопытный аспект — человек, писавший PoC на Rust, изначально голосовал за Elixir, потому что уже имел опыт работы с Elixir, но после PoC он проголосовал за Rust. В целом, я думаю, что одной из главных причин выбора Rust была его универсальность. Его не только считали подходящим для работы с сетью и веб-сервисов, но и потенциально могли задействовать для совместного использования кода с Node.js, Ruby и другими языками (например, в то время мы уже знали, что идут переговоры о приобретении стартапа, написанного на Python). Также мы обсуждали написание SDK для наших API на различных языках, и это тоже было ещё одним потенциально интересным сценарием использования — написать ядро на Rust, добавить обёртки для Ruby, Python, Node.js и так далее.
На proof of concept потребовалось время, поэтому сроки поджимали, и писать сервис на Rust вместо команды предложено было мне, ведь у меня был опыт работы с этим языком. Я работал вместе с автором PoC на Rust и стремился давать ему писать как можно больше кода; мы часто проводили сессии парного программирования.
Из-за временных ограничений я хотел максимально всё упростить, поэтому предложил решение в стиле базы данных. Если рабочая нагрузка достаточно проста, поддержка ста тысяч соединений на Rust не будет чем-то особенным. Кроме того, для MVP нам не требовалось никаких сложных фич: достаточно было запрашивать, находится ли пользователь с указанным id онлайн, и с какой частью приложения он работает. Если пользователь отключается, то это значит, что он офлайн. Если сервис умирает, мы перезапускаем его и позволяем клиентам подключиться повторно. Позже мы собирались добавить события наподобие user_online
и user_entered_area
, но в этом мы тоже не видели ничего особо сложного. Для работы в реальном времени мы хранили бы всё в памяти и отправляли события в Kafka для последующей обработки. То есть сервис, по сути, был API на основе WebSocket, обёртывающим несколько хэш-таблиц в памяти.
Первую готовую для продакшена версию мы написали за две недели. Спустя ещё одну-две недели мы развернули её, потому что команде SRE требовалось время на подготовку инфраструктуры. Это были два сервера с автоматическим включением резерва — в случае сбоя основного сервера мы переключали всех клиентов на резервный. Примерно в течение месяца мы добавили ещё несколько фич, и сервис без проблем работал с ожидаемой нагрузкой в чуть менее ста тысяч пользователей.
К сожалению, планы внутри компании поменялись, и нас попросили перевести сервис в режим обслуживания, потому что компания не хотела больше вкладываться в фичи реального времени. Поэтому мы проверили работу алертов, инструментации и так далее, оставили сервис работать и с ворчанием вернулись в свои старые команды к своим старым задачам. Сервис непрерывно работал в течение следующих нескольких месяцев. Никаких ошибок и багов, ничего — идеал для инфраструктурной команды.
Спустя несколько месяцев компания готовила большое событие с ожидаемым пиком в пятьсот тысяч одновременных пользователей. Я и второй автор сервиса были заняты другими задачами, поэтому компания решила нанять трёх разработчиков на Rust, чтобы обеспечить нужную производительность сервиса. Новая команда приступила к бенчмаркингу и обнаружила несколько узких мест... снаружи сервиса. После настройки параметров ядра, изменений в конфигурации балансировщика нагрузок и так далее сервис мог обрабатывать миллион одновременных пользователей с p99=10 мс и два миллиона пользователей с p99=25 мс. Точные значения я не помню, но примерно такие результаты были получены на машине с 64 ядрами (или около того).
И здесь начались проблемы. Когда руководство решило нанять разработчиков на Rust, отвечавший за это решение директор был настроен расширять использование Rust, но когда компания за год растёт с тридцати до тысячи человек, неизбежны частые реорганизации и смены команд. Новый директор, отвечавший за проект в то время, когда мы оценивали производительность, оказался им недоволен. Его самая большая претензия заключалась в том, что если сервис не нужно дополнительно поддерживать, то трём инженерам не останется работы!
Хоть это кажется потенциальной проблемой, но я увидел в этом возможность. Ещё несколько команд уже высказали свою заинтересованность в работе на Rust, и я считал, что их сценарии использования подходили для Rust: например, обработка событий для сбора аналитики или сервис уведомлений в реальном времени. Стоит также добавить, что двое из трёх разработчиков на Rust были очень опытными: раньше они работали в финтехе и с распределёнными системами. Поэтому мы предложили расширить использование Rust в компании. К сожалению, принимающий решения директор был непреклонен. Вскоре после начала обсуждений он сказал разработчикам на Rust, что им лучше начать учить Ruby/Node.js или начинать искать новую работу. На мой взгляд, это было огромной потерей, потому что вскоре они уволились, но поделать мы ничего не могли.
Откровенно говоря, я понимаю часть аргументов, лежавших в основе этого решения; например, в то время (где-то 2020 год) Rust был относительно нишевым языком, поэтому гораздо больше разработчиков знали Node.js и Ruby, чем Rust. Но с запретом на Rust тоже были связаны риски; например, что делать с единственным сервисом на Rust? Целые команды хотели попробовать Rust для своих сервисов и у нас уже было три разработчика, готовых помочь; я знал, каким бы был мой ответ, но, к сожалению, решения принимал не я.
Самое забавное в этой истории заключалось в том, что если бы сервис на Rust не оказался настолько успешным, то компания, вероятно, сохранила бы команду разработчиков на Rust. Допустим, если бы им понадобились месяцы на оптимизацию сервиса (а так и происходило со многими другими сервисами компании), то никто и слова бы ни сказал. Так обстоят дела в бизнесе. А в дальнейшем нам понадобились новые фичи, но Rust-команда так до них и не добралась (и это, кстати, остаётся проблемой в компании — нам нужна фича X, проще всего её было бы реализовать в сервисе на Rust, но у сервиса на Rust нет команды... Что ж, давайте сделаем на коленке неоптимальное решение, на которое понадобится гораздо больше времени и которое будет гораздо более сложным, чем внесение изменений в сервис).
А теперь небольшой бонус: что же случилось потом? Вскоре после решения о запрете Rust для разработки всего нового было принято решение переписать Rust-сервис на Node.js, чтобы его могли поддерживать наши команды. Была предпринята только одна провалившаяся попытка. Да, надо быть честным, подобный сервис можно написать на Node.js. Однако проблема в том, что один процесс Node.js не способен вынести подобную нагрузку из-за своих характеристик среды выполнения (однопоточность, ограниченные возможности передачи задач воркерам сервиса — всего этого явно недостаточно). Кроме того, при этом пришлось бы менять и архитектуру. Больше никакого единственного процесса на единственном сервере, и вместо него множество процессов, синхронизируемых через какой-нибудь сервис, базу данных или очередь. Насколько помню, человек, занимавшийся переписыванием, решил использовать внешний сервис Ably, чтобы не обрабатывать подключения WebSocket вручную, но, к сожалению, спустя примерно два месяца выяснилось, что решение недостаточно производительно. Повторюсь, я знаю, что это реализуемо, но из-за необходимости более комплексной архитектуры сделать это не так просто, как на Rust. Поэтому сервис на Rust просто работал в продакшене, и о нём вспоминали обычно только тогда, когда нужно было расширить его возможности, но без поддерживающей его команды компания или отказывалась от новых фич, или пыталась обойти тот факт, что сервис на Rust никто не поддерживает.
Комментарии (7)
rsashka
01.07.2025 11:30Хорошая сказка. Вот только что-то мне подсказывает, что дело было совсем не в том, что "Его самая большая претензия заключалась в том, что если сервис не нужно дополнительно поддерживать, то трём инженерам не останется работы!", а из-за того, что нужно было нанимать еще трех новых человек, тогда как используя существующий стек технологий все можно было сделать уже имеющимися силами.
Директор посчитал бюджет и принял итоговое решение и конкретный язык программирования или framework тут не причем.
kolya7k
01.07.2025 11:30Странно, на такое 2 недели… На C++ давно уже пишем гораздо более сложные вещи за день примерно. Без проблем с производительностью, WS/бинарный протокол/rest. Без явного выделения памяти, RAII и так далее. Поддерживать может любой программист, кто понимает C-подобный синтаксис, даже полный даун.
Dhwtj
Сын еврея-юриста недавно закончил университет, тоже стал юристом, получил практику и выиграл свой самый первый судебный процесс. Прибегает весь взволнованный домой:
- Папа, папа, я сегодня выиграл свой первый суд! И знаешь, папа, это то самое дело которое ты вел все прошлые 10 лет и не мог выиграть, а я его выиграл за один день!
Отец на это очень раздраженно отвечает:
- Вы только посмотрите на этого идиота! Он сегодня за один день закончил дело которое кормило нашу семью почти 10 лет! Кто нас теперь кормить-то будет?
monyae
© rust