15 янв. 2025 г.·7 мин чтения

Go против Rust для инфраструктуры: как выбрать с умом

Выбор между Go и Rust для инфраструктуры зависит от навыков команды, целей по задержкам, ограничений памяти, риска ошибок и реальной стоимости найма и отладки.

Go против Rust для инфраструктуры: как выбрать с умом

Почему это решение быстро становится дорогим

Большинство дебатов про Go vs Rust для инфраструктуры начинаются не с того конца. Люди сразу запускают графики скорости и скриншоты бенчмарков. Настоящие расходы чаще появляются в других местах: найм, ревью кода, дежурства и та переработка, которой вы обещали избежать.

Выбор языка влияет на то, кого вы сможете нанять в этом квартале. Если через 60 дней вам нужны два инженера, небольшой пул кандидатов может замедлить проект ещё до первой поставки сервиса. Даже при удачном поиске редкие навыки часто повышают зарплаты.

Это также меняет повседневную работу команды. Некоторые кодовые базы легко просмотреть, ревью идут быстрее, а новые инженеры становятся полезными за дни, а не недели. Другие кодовые базы требуют больше фокуса от каждого ревьюера. Merges замедляются, люди начинают сомневаться в изменениях, а простые объяснения превращаются в длинные ветки обсуждений.

Вы особенно это чувствуете во время инцидентов. Когда сервис падает в 2:00 ночи, никого не интересует хайп вокруг языка. Важно быстро найти неудачный деплой, читать логи, трассировать краш и исправить проблему, не создав новых. Язык, который кажется изящным при проектировании, может показаться дорогим, когда у уставшего инженера есть десять минут, чтобы остановить проблему у клиента.

Малые команды ощущают это первыми. Oleg Sotnikov много лет управляет продакшен-системами с компактными командами и ограниченным облачным бюджетом — в таких условиях мало места для медленной отладки или незаменимых специалистов. Язык может экономить память и CPU и при этом стоить дороже, если он замедляет людей, поддерживающих систему в рабочем состоянии.

Переписка позже обычно худший вариант. Вы платите один раз за первую версию, потом снова за миграцию тестов, документации, инструментов и за все неформальные знания вокруг системы. Немного более медленное начальное решение часто выигрывает по сравнению с модным выбором, который придётся отменять через год.

Разумное решение редко рождается из выигранного спора. Оно приходит от выбора языка, за который ваша команда сможет нанимать, поддерживать спокойно и которому можно доверять в случае поломки.

Соотнесите язык с задачей

Команды часто хотят один язык на всё. Это звучит аккуратно, но обычно добавляет риска. API, воркеры, CLI, прокси и внутренние инструменты решают разные задачи. Они не обязаны следовать одному правилу.

Начните с той части, которую пользователи ощущают ежедневно. Если нужно быстро выпустить API, часто менять его и отлаживать полусонным, Go часто спокойнее. Его легче найти на рынке, проще читать в смешанной команде и он обычно достаточно быстр, когда реальный узкий кусок — база данных, сетевые вызовы или внешние сервисы.

Rust заслуживает места в более узких частях стека. Он имеет смысл, когда память ограничена, ошибки дорого обходятся или код находится на «горячем» пути весь день. Это может быть прокси, агент на узле, процессор пакетов, парсер или сервис, который обрабатывает огромное количество мелких запросов, где каждая лишняя миллисекунда и каждый мегабайт важны.

Низкое использование памяти важно, когда вы запускаете много копий одного процесса, размещаете софт на крайних узлах или упаковываете сервисы на маленькие машины. Оно гораздо менее важно для внутреннего админ-инструмента, который запускается дважды в день и завершает работу за 30 секунд.

Простое разделение часто работает для многих команд:

  • API и административные бэкенды: отдавайте приоритет быстрой доставке и простоте отладки
  • Очереди и cron-джобы: используйте язык, который команда сможет быстро починить
  • CLI и инструменты деплоя: выбирайте тулчейн, который инженеры уже знают
  • Прокси, агенты и протокол-ориентированные сервисы: рассматривайте Rust при жёстких ресурсных ограничениях

Это практичный ответ на вопрос Go vs Rust для инфраструктуры. Не нужно единственного победителя на весь стек. Небольшая платформа может держать публичный API, бэк-офис и скрипты деплоя на Go и написать один latency-sensitive прокси на Rust. Такой микс часто дешевле, чем пытаться втиснуть один язык туда, где он не подходит.

Проверьте команду и рынок найма

Выбор языка становится дорогим, когда команда не может поставлять, ревьюить или поддерживать код без узких мест. Быстрый рантайм помогает меньше, чем кажется, если один инженер становится единственным ревьюером, отладчиком и понимателем сборки.

Начните с команды, которая у вас уже есть, а не с той, о которой вы мечтаете. Посчитайте, сколько инженеров уже могут запускать продакшен Go сегодня. Затем посчитайте, сколько могут читать Rust, уверенно его ревьюить и ловить ошибки до продакшена. В большинстве компаний эти числа далеко не равны.

Обычный подсчёт людей часто всё показывает. Сколько человек сейчас могут строить и оперировать Go-сервисами? Сколько могут ревьюить Rust без замедления команды? Сколько времени займет найм каждого профиля на вашем рынке и во сколько это обойдётся? Сколько месяцев джуниоры и мидлы будут нужны, чтобы работать самостоятельно?

Если у вас четыре бэкенд-инженера и трое уже хорошо знают Go, у Go реальное кадровое преимущество. Если никто не может уверенно ревьюить Rust, каждый пулл-реквест будет ждать одного человека. Эти затраты накапливаются в виде задержек, пропущенных багов и обычного разочарования.

Найм важен так же, как и текущие навыки. Во многих рынках роль Go закрыть быстрее и в более широком диапазоне зарплат, чем роль Rust. Rust-разработчики есть, но пул обычно меньше, и сильных инфраструктурных кандидатов найти сложнее. Если ваш план зависит от найма трёх Rust-инженеров в следующем квартале, проверьте, насколько это реально.

Стоимость обучения тоже важна. Джуниоры и мидлы обычно быстрее встают на ноги с Go. Rust оправдывает себя там, где критична производительность и безопасность, но кривая обучения реальна. Команды тратят больше времени на правила владения, ревью и непривычные шаблоны, прежде чем всё станет рутинным.

Для инфраструктурных задач лучше выбирать язык, который ваша команда сможет поддержать в 2:00 ночи без драмы. Язык с чуть меньшей эффективностью рантайма может выиграть, если он даёт больше ревьюеров, более быстрый найм и меньше застопоренных релизов.

Измерьте потребности рантайма перед спорами

Большинство споров начинается слишком рано. Если у команды нет чисел, люди спорят из вкуса.

Запишите лимиты сначала. Вам нужен целевой latency, бюджет памяти на инстанс и время старта, подходящее под то, как сервис работает. API для логина, пакетная задача и командная утилита не требуют одного и того же профиля.

Короткая заметка — достаточно:

  • p95 latency ниже 120 ms
  • память ниже 180 MB на инстанс
  • старт < 2 секунд
  • постоянный трафик около 300 запросов в секунду

Этот список меняет разговор. Если сервис работает весь день за балансировщиком, время старта может почти не иметь значения. Если вы часто поднимаете джобы, оно важно.

Затем оцените, сколько инстансов вы реально будете запускать. Экономия памяти выглядит эффектно на графике, но счёт в облаке меняется только когда эта экономия повторяется на достаточном количестве машин. Экономия 60 MB на три инстанса почти не значима. На 80 инстансах она может решить, нужен ли ещё один узел.

Используйте ожидаемый трафик, а не фантастические пики. Если продукт получает 400 rps в загруженные дни, проектируйте систему под это и добавьте разумный запас. Не проектируйте под 50 000 rps только потому, что это может случиться когда-нибудь. Так команды загоняют себя в более сложный язык без выгоды.

Бенчмарки помогают только если они похожи на ваш сервис. Постройте одну маленькую реалистичную службу: парсит вход, обращается к базе или кешу и возвращает ответ. Запустите одинаковую задачу на обоих языках на одинаковом железе. Измерьте p50 и p95 latency, память, CPU и время старта.

Один такой тест часто закрывает спор. Иногда Go уже достаточно быстр, и выигрывает более дешёвая разработка. Иногда Rust оправдывает себя, если память ограничена, CPU дорог или вы ожидаете долгий запуск большого числа инстансов.

Считайте стоимость отладки

Частичная поддержка CTO
Привлеките опытного специалиста по архитектуре, найму и компромиссам в продакшне.

Выбор языка может выглядеть дешёвым в первый день и очень дорогим в 3:00 ночи, когда сервис упал, сработали алерты, и дежурный имеет 20 минут, чтобы найти причину.

Стоимость отладки — это не только количество багов. Это то, как быстро кто-то может воспроизвести проблему, прочитать код, доверять инструментам и выпустить безопасный фикс.

Это часто решает больше, чем сырая скорость. Go обычно даёт командам более быстрый цикл «изменил — собрал — запустил», короткие времена компиляции и код, который больше инженеров может читать в стрессе. Это важно, когда баг прячется в обработке запросов, retry-логике или в плохом деплое.

Rust меняет баланс. Его система типов ловит много проблем до продакшена, и это реально экономит силы позже. Но если ошибка проскочила, особенно в async-коде или в участках с нетривиальной моделью владения, фикс может потребовать более опытного инженера и больше времени.

Пара вопросов делает эту стоимость видимой. Сколько занимает чистая сборка на обычном ноутбуке? Может ли новый член команды разобраться в stack trace без дополнительного контекста? Кто будет отлаживать сервис во время дежурства? Как быстро человек сможет написать тест на timing-баг или гонку? Сколько стоит один лишний час простоя бизнесу?

Timing-баги заслуживают особенного внимания. Go даёт простой race detector и цикл обратной связи, который часто кажется легче. Rust убирает некоторые классы багов заранее, но он не устраняет ошибки распределённых систем, неверные предположения или запутанную retry-логику. Они по-прежнему стоят времени.

Оцените один тяжёлый баг, а не средний. Представьте биллинг-воркер, который падает раз в четыре дня под нагрузкой, но только после сетевого таймаута и повтора. Если два сеньора тратят шесть часов на воспроизведение, а дежурный уже лишился ночи — этот один баг может стоить больше, чем месяцы экономии по рантайму.

Для тонких команд чаще побеждает язык, который проще отлаживать. Если у вашей команды глубинный опыт Rust и стоимость ошибки высока, Rust окупается. Если дежурство крутится по широкой группе и быстрые фиксы важны, Go обычно снижает общую сумму расходов.

Судите по стоимости отказа

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

Простое ранжирование помогает. Низкоимпактные сервисы: безсостояночные API, прогревщики кеша, внутренние утилиты и batch-задачи, которые можно переиграть. Среднеимпактные: клиент-ориентированные системы, где аутеджем вредно, но восстановление чистое. Высокоимпактные: авторизация, биллинг, provision, очереди с повторной доставкой. Очень высокоимпактный код близок к базам данных, state machines, финансовым записям или необратимым действиям.

Чем выше ущерб, тем больше смысла платить за строгую безопасность заранее. Здесь Rust часто получает преимущество. Если одна ошибка памяти, гонка или некорректное состояние могут испортить данные или вызвать неправильное действие, более сильные гарантии — не академический аргумент, а способ снизить вероятность дорогостоящей недели.

Go чаще подходит в другой части спектра. Если сервис меняется каждую неделю, откат лёгок, и сбой не оставляет отравленных данных, проще и быстрее менять код — важнее. Команды спокойнее шипят фичи, когда код легко читать посреди инцидента.

Многие команды пропускают этот шаг. Они сравнивают графики скорости и игнорируют радиус поражения. Это наоборот. Выигрыши рантайма важнее только тогда, когда стоимость восстановления меньше, чем сэкономленные ресурсы.

Представьте маленькую платформу с тремя сервисами: webhook intake API, биллинговый дневник и background worker для писем. Webhook можно написать на Go и перезапускать при необходимости. Email-воркер тоже может быть простым. Биллинговый журнал заслуживает более строгих правил, медленнее релизов и, возможно, Rust, если команда это потянет.

Не подбирайте язык под гордость команды. Подбирайте его под то, что ломается при ошибке. Если в худшем случае — короткий outage, отдавайте приоритет скорости изменений. Если худший случай — молчаливая порча данных, платите больше заранее, чтобы продакшн этого не сделал.

Используйте простой процесс принятия решения

Проверьте ситуацию с наймом
Сопоставьте стек с инженерами, которых вы можете нанять, обучить и сохранить.

Языковые споры обычно никуда не ведут, когда команда остаётся абстрактной. Выберите один реальный сервис. Он должен иметь постоянный трафик, понятные лимиты и стоимость отказа, которую можно объяснить в одном предложении.

Подходят API-воркер, лог-инжестер, консьюмер очереди или небольшой внутренний gateway. Избегайте самой тяжёлой системы и игрушки. Нужен сервис близкий к продакшну, но достаточно маленький, чтобы двое людей могли доделать его без недель спора.

Постройте одинаковую маленькую версию на обоих языках с одинаковым scope. Дайте каждой версии один и тот же endpoint, одно и то же хранилище, одинаковый лог и тесты. Намеренно делайте дизайн скучным. Модные фреймворки скрывают те компромиссы, которые вы пытаетесь измерить.

Отслеживайте работу числами:

  • часы до чистой первой версии
  • p95 latency, использование памяти и CPU под одинаковой нагрузкой
  • часы на написание тестов и исправление провалившихся тестов
  • время на поиск и исправление одного искусственно посеянного бага в каждой версии

Последняя метрика важнее, чем большинство ждут. Сервис, который экономит 15% CPU, но требует вдвое больше времени на отладку, может быть худшим выбором для повседневной инфраструктуры.

Напишите правило решения перед началом дебатов. Например: по умолчанию Go для стандартных сетевых сервисов с широким владением командой; Rust использовать только когда оправданы требования безопасности памяти, жёсткие ресурсные лимиты или высокий ущерб от сбоев.

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

Реалистичный пример с маленькой платформой

Представьте SaaS с одним публичным API, несколькими бэкграунд-воркерами и биллинговым потоком, который должен работать без сбоев каждый день. Две инженерные роли отвечают за релизы, дежурства и большинство багфиксов. Если что-то ломается в 2:00 ночи, те же люди, кто писал код, должны читать логи, патчить и выпускать фиксы быстро.

Такая конфигурация меняет дискуссию. Сырая скорость важна, но время команды важнее. API обрабатывает CRUD, проверки авторизации, вебхуки и несколько внешних вызовов. Оно не испытывает сильного давления по памяти, а продуктовая команда постоянно просит новые эндпойнты.

Поэтому команда выбирает Go для API. Код остаётся простым, новые фичи выходят по графику, а продакшен-отладка остаётся «скучной» в хорошем смысле. Для такой маленькой команды скучное — обычно правильный выбор.

Go также соответствует их кадровой реальности. При двух инженерах они не могут позволить себе долгие отклонения в релизах или ночные эпизоды. Если один болеет или занят биллингом, другой должен уметь прочитать код и внести безопасное изменение без долгой распаковки контекста.

Они не применяют это правило везде. В одной части стека есть жёсткое ограничение: прокси, работающий на очень маленьких инстансах рядом с рабочими нагрузками клиентов. Процесс держит много соединений, и даже умеренный всплеск памяти вызывает шумные ошибки.

Для прокси Rust проходит честный тест, потому что компромисс меняется. Команда готова мириться с более медленной итерацией и крутой кривой обучения, если это сократит использование памяти и уменьшит вероятность крашей. В этой узкой задаче дополнительное усилие может окупиться.

Многие правильные решения выглядят именно так. Команда не выбирает язык ради идентичности. Они используют Go там, где важна стабильная доставка, и Rust там, где бюджет памяти строг и отказ дорого обходится.

Ошибки, которые ведут к переработке

Проверьте план инфраструктуры
Устраните догадки, прежде чем маленькая команда выберет дорогой путь.

Команды создают дорогую переработку, когда выбирают язык, чтобы показать вкус, а не решить реальную проблему. Часто это означает выбор Rust, потому что он выглядит серьёзным, а команда едва знает его за пределами мелких экспериментов. Стоимость проявляется позже: медленные ревью, больше времени на lifetimes и владение, и кодовая база, которую могут менять лишь один-два человека.

Обратная ошибка тоже часта. Go легко читать, просто преподавать и проще нанимать на многих рынках. Это делает его сильным дефолтом, но не универсальным ответом. Если сервис работает в жёстких лимитах памяти, пропускает много работы через маленькие машины или лежит на пути, где спайки latency вредят клиентам, Go может стать более дорогим выбором. Команды затем месяцы тратят на оптимизацию памяти, добавление кешей или оплату больших машин, тогда как давление было очевидно заранее.

Бенчмарки тоже вводят в заблуждение. Победа в микробенчмарке красиво выглядит на графике, но расходы на найм и отладку обычно важнее. Несколько миллисекунд не помогут, если новые инженеры требуют недель, чтобы стать продуктивными, или дежурный не может быстро отследить баг в продакшне. Под давлением читаемый код часто побеждает «умный» код.

Ещё одна ошибка — рассматривать текущую команду как всю историю. Спросите, кто будет поддерживать этот сервис через год. Останутся ли авторы? Сможет ли другой инженер починить баг, выпустить патч и объяснить компромиссы без повторного чтения кода? Если ответ «нет», выбор языка может постареть плохо.

Небольшая платформа может жить с чуть менее эффективным сервисом. Ей гораздо труднее с медленным наймом, хрупкой собственностью кода и сложной отладкой. Выбирайте Rust когда рантайм-давление требует этого и команда может поддержать. Выбирайте Go когда важнее простота и скорость работы команды. Переработка начинается, когда команды притворяются, что эти компромиссы не реальны.

Быстрые проверки и дальнейшие шаги

Хороший выбор языка должен выглядеть хорошо в 2:00 ночи, когда сервис тормозит, алерты горят, и дежурный должен быстро его починить. Если ваша команда может читать, отлаживать и патчить Go под давлением, но застывает с Rust — это реальная стоимость. Обратное тоже верно.

Используйте короткий чеклист перед окончательным решением:

  • Может ли текущая команда трассировать баг в продакшне на этом языке без ожидания одного специалиста?
  • Сможете ли вы нанимать под этот стек по разумной зарплате в течение 12 месяцев?
  • Сервис ломается безопасно, или одна ошибка может испортить данные, заблокировать трафик или сломать восстановление?
  • У вас измеренные потребности рантайма, а не догадки, которые оправдывают дополнительную сложность?
  • Будет ли этот выбор иметь смысл после ухода первых двух инженеров или их смены ролей?

Затем запишите одностраничное правило и держите его простым. Используйте Go для внутренних API, control plane, планировщиков и операционных инструментов, где важны понятный код, быстрый найм и лёгкая отладка. Используйте Rust для частей, где безопасность памяти, жёсткие задержки или устойчивость к крахам важны настолько, что оправдывают дополнительные затраты на разработку. Если сервис не имеет строгого рантайм-давления или высокого ущерба от отказа, Go часто безопасный дефолт.

Это маленькое правило экономит много перетягиваний каната. Оно останавливает каждый новый проект от повторного открытия того же спора и даёт новым сотрудникам понятную стартовую точку.

Если решение всё ещё кажется политическим или личным, нейтральный обзор может помочь. Oleg Sotnikov на oleg.is работает со стартапами и малыми компаниями по техническому направлению, инфраструктуре и AI-first разработке — короткий внешний обзор часто дешевле месяцев переработки.

Для большинства команд лучший ответ не в одном языке навсегда. Это выбор языка, который команда сможет запускать, отлаживать и нанимать под минимальные боли.