PRODСуверенная европейская платформа BaaSОткрыть панель управления →

Производительность · 10 минута чтения

Почему ни один сборщик мусора не меняет задержку p99

Affane Daylami · Fondateur · 2 июля 2026 г.

Вернуться в блог

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

Этот текст на английском языке был создан автоматически на основе французского оригинала и еще не проверялся.
Эта страница была переведена автоматически. Английская версия является авторитетной.

Эта статья опирается исключительно на сторонние датированные источники, а не на изобретенный шифр Aurabase. Задержка p99, специфичная для нашего бэкэнда, не включена. Ядро нашего приложения мы пишем на Rust, без сборщика мусора: факт, который можно проверить непосредственно в коде, рабочей области Cargo и сервисах axum. Однако мы еще не опубликовали воспроизводимую методологию тестирования p99, чтобы продемонстрировать это в цифрах. Этот текст объясняет механизм, а не измеренный результат.

Самое необходимое

  • p99 измеряет самый медленный запрос из ста — место, где пауза сборщика мусора (GC) причиняет боль больше всего, а не в среднем (Дин и Баррозо, «Хвост в масштабе», Google, 2013).
  • Сборщик мусора прерывает всю программу («остановить мир»), чтобы освободить неиспользуемую память. В Rust нет GC: память освобождается именно в тот момент, когда значение выходит за пределы области видимости, что проверяется компилятором.
  • Discord задокументировал службу кэширования в 2020 году, где Go запускал цикл сборки мусора как минимум каждые две минуты, причем каждый цикл вызывал резкий скачок задержки (блог Discord Engineering).
  • Чтобы уменьшить паузу GC, нужны годы инженерных разработок, даже в Google: в период с 2015 по 2018 год коллектор GB увеличился с 300-400 мс до 500 мкс, так и не достигнув нуля (go.dev).
  • Бэкенд-ядро Aurabase написано на Rust, без сборщика мусора — проверено в коде. На сегодняшний день данные о задержке p99 Aurabase не опубликованы: это остается механизмом, а не измерением.
#
Задержка хвоста

Почему р99 не средний

Среднее скрывает главное. Если 99 запросов из 100 отвечают за 5 мс и только один занимает 500 мс, средний показатель остается низким. Но один пользователь из ста испытывает ожидание в сто раз дольше. P99 измеряет именно этот запрос: самый медленный сотый запрос, тот, который нарушает ваше соглашение об уровне обслуживания, в то время как ваша информационная панель средней задержки остается зеленой.

В Google Джеффри Дин и Луис Андре Баррозу формализовали эту проблему в «Хвост в масштабе» (Communications of the ACM, vol. 56, 2013). Их наблюдение, которое с тех пор часто цитируется: «Временные эпизоды с высокой задержкой, которые не имеют значения в системах среднего размера, могут стать доминирующими в общей производительности обслуживания в больших масштабах». Короче говоря: случайные эпизоды задержки, незначительные в небольших масштабах, в конечном итоге доминируют над воспринимаемой производительностью распределенной системы.

Серверная часть, обрабатывающая тысячи запросов в секунду, в тот или иной момент обязательно отправит запрос, который выпадает во время паузы GC. В широком масштабе это не редкость. Это статистическая достоверность.

#
механика ГХ

Что делает сборщик мусора и почему он все ставит на паузу

Сборщик мусора (GC) постоянно отслеживает живые объекты программы — те, на которые еще куда-то ссылаются — и освобождает память объектов, которые стали недоступны. Это отслеживание называется tracing: GC просматривает опорный граф, отмечает то, что еще используется, затем очищает остальное.

Проблема: обход этого графа, пока программа продолжает создавать новые ссылки, приводит к противоречивым результатам. Исторический ответ, который до сих пор используется многими современными сборщиками мусора в качестве крайней меры, — это stop-the-world — вся программа приостанавливает маркировку и сканирование. Чем больше куча, тем продолжительнее обычно бывает пауза: ее продолжительность зависит от размера текущих данных, а не от текущей рабочей нагрузки.

Большинство современных GC используют стратегию поколений: они предполагают, что большинство объектов умирают молодыми. Поэтому недавние выделения сканируются часто, но быстро в небольшой области памяти. Объекты, пережившие несколько циклов, мигрируют в большую область и сканируются нечасто, но когда эту область необходимо очистить, связанная с этим пауза увеличивается вместе с ее размером. Именно этот «серьезный» перерыв, а не маленькие «незначительные» перерывы, доминируют в p99 службы с высоким трафиком и большим распределением ресурсов.

Информация

Современные параллельные и генерационные GC сокращают частоту и продолжительность этих перерывов, работая параллельно с программой. Но почти все они имеют запасной механизм остановки мира для пограничных случаев — и для его уменьшения требуются годы инженерных разработок. В разделе 04 приводится количественный пример с указанием источников.

#
Реальный случай

Discord, 2020: перерыв в сборке мусора становится производственным инцидентом

В феврале 2020 года инженер Джесси Ховарт опубликовал пост, ставший эталоном в отрасли: «Почему Discord переходит с Go на Rust» (блог Discord Engineering). Соответствующий сервис Read States управляет статусом чтения сообщений для миллионов пользователей — десятки миллионов записей в кэше — с сотнями тысяч обновлений в секунду.

Диагноз прямой, процитированный в статье: «Go заставит сборку мусора запускаться минимум каждые 2 минуты». Другими словами, Go запускает цикл сборки мусора в этом сервисе как минимум каждые две минуты — и каждый цикл приводит к резкому увеличению задержки, что видно на графиках команды.

Команда сначала уменьшила размер кэша, чтобы сгладить скачки. Компромисс остался неблагоприятным: меньше пауз GC, но больше запросов на пропуск кэша попадает в базу данных — следовательно, общий уровень p99 в других местах снижается. Основным решением было переписывание сервиса на Rust без сборщика мусора, который нужно было бы отслеживать.

Пост вызвал оживленную техническую дискуссию: более 1580 баллов и 642 комментария на Hacker News в тот же день его публикации (4 февраля 2020 г.) — признак того, что проблема выходит далеко за рамки дела Discord.

#
Перейти к истории

Три года разработки в Google позволили уменьшить паузу с 400 мс до 500 мкс

Сборщик мусора Go иллюстрирует масштаб усилий, необходимых для управления паузой GC — даже с ресурсами специальной команды в Google. Рик Хадсон, технический руководитель Go GC, задокументировал эту историю в двух официальных сообщениях в блоге Go.

До августа 2015 г.300-400 мсИсторический коллекционер Го, до редизайна
Август 2015 · Go 1.530-40 мсПервый конкурирующий коллектор, установлено целевое время < 10 мс
2016 · Го 1.6< 10 мс (удерживается SLO)Первоначальная цель достигнута в производстве
Март 2017 · Перейти 1.8менее миллисекундыУдаление сканирования стека «Останови мир»
Август 2017 · Перейти 1.9100-200 мкс (отметка)Новый неформальный ориентир, упомянутый командой
2018 · Объявлено SLO500 мкс за циклЦель обслуживания формализована Риком Хадсоном

Источник: «Getting to Go: Путешествие сборщика мусора Go», go.dev, 12 июля 2018 г.; и «Go GC: приоритет низкой задержки и простоты», go.dev, 31 августа 2015 г.

Три года самоотверженной работы сократили типичный перерыв в тысячу раз. Но пауза никуда не исчезла: это цель обслуживания (SLO), а не абсолютная гарантия нуля. Трассировка GC по своей конструкции должна время от времени пересекать граф живых объектов. Единственная регулируемая переменная — это частота и продолжительность этого путешествия, а не его существование.

Этот выбор приоритета не является нейтральным. Go в первую очередь нацелен на сетевые сервисы и веб-серверы, где пауза в несколько сотен миллисекунд напрямую нарушает работу пользователя — отсюда огромные усилия, вложенные в задержку, а не в чистую пропускную способность GC. Другие управляемые среды выполнения унаследовали различные компромиссы, сформированные их историческими сценариями использования, прежде чем компенсировать это своими собственными сборщиками с низкой паузой. Общая точка остается той же: все они начинаются с трассировки сборщика мусора, следовательно, с механизма паузы, который должен быть сведен к минимуму и никогда не может быть устранен при построении.

#
Механика ржавчины

Почему в Rust нет этой проблемы по своей конструкции

Rust не уменьшает паузы GC: он устраняет механизм, вызывающий их. Компилятор при компиляции отслеживает, кому принадлежит каждое значение памяти — этоownership. Когда владелец значения выходит за пределы области видимости, Rust автоматически вставляет вызов, освобождающий эту память, в то же место двоичного кода. Этот механизм называется RAII (инициализация получения ресурсов): выпуск является детерминированным, а не запланированным сборщиком мусора, работающим в фоновом режиме.

Александру Недельку, автор технического блога, признанного в экосистеме Scala/Rust, резюмирует этот компромисс в недавней статье: «Компромисс, на который идет Rust, — это простота использования в пользу производительности с предсказуемой задержкой и безопасностью» (alexn.org, 21 июля 2026 г.). Rust жертвует некоторой простотой написания ради предсказуемой задержки.

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

Вот механизм примерно в десяти строках — общий пример, а не выдержка из кода Aurabase:

example.rsrust
struct Connection {
    id: u32,
}

impl Drop for Connection {
    fn drop(&mut self) {
        println!("connexion {} fermée", self.id);
    }
}

fn handle_request() {
    let conn = Connection { id: 42 };
    // ...обрабатывает запрос...
} // conn здесь выходит за рамки: выполняется drop()
   // в конкретный момент, проверенный компилятором —
   // не через цикл сбора мусора.

Важный нюанс: не все бесплатно. Типы подсчета ссылок (Rc, Arc) добавляют небольшую стоимость к каждому клонированию и выпуску. Эта стоимость остается локальной и детерминированной. Никогда не бывает паузы, которая останавливает всю программу, пока она проходит через кучу памяти.

Полезное разъяснение по поводу асинхронного бэкэнда: асинхронная среда выполнения Rust (tokio, используемая всеми сервисами Aurabase) не имеет ничего общего со сборщиком мусора. Он планирует совместные задачи в пуле потоков, но никогда не перебирает граф живых объектов для освобождения памяти. Путаница часто возникает в экосистемах, где асинхронная среда выполнения и сборщик мусора управляются одной и той же виртуальной машиной.

#
Архитектурное значение

Что это меняет для серверной части с высоким трафиком

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

Бэкэнд-ядро Aurabase применяет этот принцип: все сервисы (aura-gateway, aura-auth, aura-db, aura-realtime, aura-storage, aura-functions, aura-ai…) написаны на Rust и организованы в едином рабочем пространстве Cargo. Это можно проверить непосредственно в репозитории:

Cargo.tomltoml
[workspace]
resolver = "2"

members = [
    "services/aura-gateway",
    "services/aura-auth",
    "services/aura-db",
    # ...9 других сервисов + общие библиотеки
]

[workspace.dependencies]
axum = { version = "0.8", features = ["ws", "multipart", "macros"] }

Фактический отрывок из Cargo.toml, рабочая область выпуска 2021, преобразователь v2 — проверено в репозитории Aurabase.

Чего этот факт не доказывает на данном этапе: показатель задержки p99, измеренный для Aurabase. Мы еще не опубликовали воспроизводимую методологию тестирования для нашей собственной серверной части — это работа, а не результат, доступный сегодня. Отсутствие сборщика мусора — механизм, проверенный в коде. Это само по себе не является свидетельством измеренной латентности p99. Помните об этом различии, несмотря на любые маркетинговые аргументы по этому вопросу, включая наши — см. наше техническое сравнение Aurabase и Supabase для получения подробной информации об архитектуре.

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

#
Нюанс

Что не решает отсутствие GC

No-GC — не волшебная палочка

Удаление сборщика мусора устраняет только один источник задержки хвоста — но не все. Серверная часть Rust по-прежнему может отображать ухудшенный p99 из-за ожидания сети, насыщенного пула соединений Postgres, оспариваемой блокировки базы данных, плохо индексированного SQL-запроса или медленного вызова стороннего API. Механизм, описанный в этой статье, устраняет структурную причину. Он не обеспечивает иммунитета против других.

Например, в Aurabase каждая служба взаимодействует с Postgres через пул соединений (sqlx) и с другими службами через NATS JetStream. Недостаточный размер пула, медленная в использовании подписка NATS или SQL-запрос без подходящего индекса создают собственный скачок задержки — независимо от отсутствия сборщика мусора.

Практический вывод: отсутствие GC — веская архитектурная причина выбрать серверную часть Rust для системы, поддерживающей p99. Само по себе это не является гарантией задержки – ни в Aurabase, ни где-либо еще. Метод, который имеет значение, остается прежним: измерить, опубликовать методологию, а затем исправить то, что показывают измерения. Если вы переходите с серверной части с помощью GC, в нашем руководстве по миграции Supabase на Aurabase подробно описано, что меняется, а что остается прежним.

#
Часто задаваемые вопросы

Часто задаваемые вопросы: сборщик мусора и задержка p99

Отсутствие сборщика мусора гарантирует низкий p99?+
Нет. Это устраняет структурный источник непредсказуемых пауз, но другие факторы — сеть, пул соединений, блокировки базы данных — также влияют на задержку хвоста. См. раздел «Что не исправит ни один сборщик мусора» выше.
Почему Discord просто не настроил свой GC Go по-другому?+
Команда попробовала несколько настроек, включая уменьшение размера кэша, описанное в их сообщении от 2020 года. Компромисс остался неблагоприятным: меньше пауз GC против большего количества промахов в кэше. Переписывание в Rust устранило компромисс, а не переместило его.
Решат ли проблему современные сборщики мусора, такие как Go?+
Они значительно сократили его — в период с 2015 по 2018 год время сборки мусора в Go увеличилось с 300–400 мс до 500 микросекунд (go.dev), — но не устранили. Трассировка GC по своей конструкции сохраняет механизм паузы для крайних случаев.
Выпустила ли Aurabase тест задержки p99?+
Еще нет. Факт, проверенный в коде, — отсутствие сборщика мусора (Rust, Workspace Cargo, axum Services). Измеренное значение задержки p99 и его воспроизводимая методология еще не опубликованы — мы предпочитаем отсутствие цифр цифрам, не имеющим источников.

ГОТОВЫ К РАЗВЕРТЫВАНИЮ?

Ваш бэкэнд за пять минут.

Кредитная карта не требуется · 500 МБ бесплатно · 50 000 MAU