Дополнение · X-алгоритм · страница 10 из 11
Конфигурация: как система меняется, не меняясь
Сто девяносто один параметр читается из внешней системы конфигурации, а не зашит в код. Это позволяет менять поведение ленты без выкатки — и это же делает вопрос «а что сейчас в проде» нетривиальным. Разбираем механизм, его цену и реальную историю одного параметра, разложенную по дням.
Коротко
- 191 параметр объявлен макросом
param!: имя, тип, ключ во внешней конфигурации, значение по умолчанию. - Дефолты в репозитории синхронизируются с продовыми отдельным скриптом — это способ сделать открытый код правдивым, а не декоративным.
- Эксперименты ставятся через выключатель стадии: каждая стадия пайплайна имеет метод «включена ли», внутри которого читается параметр.
- Опубликована история одного изменения по дням — от запуска A/B-теста до финального значения, включая то, что тестировалось и не поехало.
- Открытый код не равен «вот что у вас сейчас»: часть трафика всегда в экспериментах, и дефолт отражает основное значение, а не единственное.
1. Как объявляется параметр
param!(FavoriteWeight, f64, "rust_home_mixer_favorite_weight", 0.5);
param!(AuthorDiversityDecay, f64, "rust_home_mixer_author_diversity_decay", 0.5);
param!(EnableInventoryHoldout, bool, "rust_home_mixer_enable_inventory_holdout", false);
Фрагмент из param.rs · код X, Apache 2.0, коммит 28e414f
Четыре части: имя типа в коде, тип значения, ключ во внешней системе конфигурации и значение по умолчанию. Читается параметр так: query.params.get(FavoriteWeight).
Ключевое — значение приходит с запросом, а не берётся глобально. Это не деталь реализации, а то, на чём держатся эксперименты: у двух одновременных запросов от разных пользователей значение одного и того же параметра может отличаться.
Разница между const FAVORITE_WEIGHT: f64 = 0.5 и параметром — это разница между «изменить значит выкатить новую версию» и «изменить значит поменять число в конфигурации».
Для рекомендательной системы это принципиально. Проверка гипотезы «ответы стоит ценить выше лайков» с константой означает: написать код, пройти ревью, собрать, выкатить на канарейку, раскатить, подождать, собрать метрики, откатить если плохо. Дни. С параметром — минуты, и на любой доле трафика.
Заодно решается проблема отката. Плохое изменение в коде откатывается новой выкаткой; плохое значение параметра — возвратом старого числа, что происходит мгновенно и не требует сборки.
Обратная сторона в том, что 191 параметр — это 191 способ изменить поведение системы, минуя ревью кода. Дисциплина смещается из кода в процесс работы с конфигурацией.
2. Как дефолты остаются правдой
Если значения живут во внешней системе, то числа в репозитории — просто заглушки, и открытый код показывает не то, что работает. Эту дыру закрыли явно: в README сказано, что по расписанию запускается скрипт, приводящий дефолты в репозитории к основным продовым значениям.
Это стоит оценить: без такого механизма публикация кода была бы почти бессмысленной для самой обсуждаемой его части — весов. Вы бы видели структуру и не видели чисел.
Дефолт — это основное значение, а не единственное. В любой момент часть трафика находится в экспериментах с другими значениями. Заявленная политика: эксперименты, идущие на заметной доле трафика — примерно от десяти процентов, — должны быть видны в репозитории.
Отсюда правильная формулировка того, что вы читаете в коде: «так работает для большинства пользователей сейчас», а не «так работает». Разница существенна, если вы пытаетесь объяснить поведение конкретной ленты конкретного человека.
И ещё: синхронизация идёт по расписанию, а не мгновенно. Между изменением в проде и его появлением в репозитории есть лаг.
3. Эксперименты через выключатель стадии
Мы уже видели этот механизм на странице про пайплайн: у каждой стадии есть метод enable(query). Теперь понятно, зачем он получает запрос — чтобы прочитать параметры, назначенные именно этому пользователю.
fn enable(&self, query: &ScoredPostsQuery) -> bool {
query.params.get(EnableBidirectionalFollowHydration)
}
Фрагмент из BIDIRECTIONAL_BOOST_CHANGE.md · код X, Apache 2.0, коммит 28e414f
Из этого следует изящное свойство: экспериментировать можно с любой стадией, ничего не меняя в её коде. Новый источник кандидатов, новый фильтр, новый скорер — всё включается на проценте трафика одним параметром.
А поскольку обёртка вокруг стадии считает метрики автоматически, вместе с экспериментом бесплатно появляется и измерение: сколько раз стадия отработала, сколько кандидатов добавила или выбросила, сколько времени заняла.
4. История одного параметра по дням
В репозитории есть отдельный документ, разбирающий реальное изменение — то самое, которое широко обсуждалось летом 2026 года. Это редкая возможность увидеть не результат, а процесс.
Суть изменения: посты от людей, с которыми у вас взаимная подписка, получают прибавку к весу предсказанной вероятности ответа. То есть если вы и автор подписаны друг на друга, вероятность того, что вы ответите на его пост, входит в скор с повышенным коэффициентом.
| Дата | Что произошло |
|---|---|
| 10 июля 2026 | Запущен A/B-тест: небольшой доле пользователей случайно назначены значения прибавки 5, 10, 15 или 20. У большинства значение 0, то есть механизм выключен. Параллельно тестируется вторая прибавка — к весу времени задержки |
| 13 июля 2026 | По первым результатам значение 20 раскатано на многих пользователей. Эксперименты с 0, 5, 10 и 15 продолжаются на других долях |
| 24 июля 2026 | По итогам экспериментов и обратной связи от пользователей значение снижено до 15 |
Проверим, чем это кончилось, по самому коду. В param.rs сейчас:
param!(
BidirectionalFollowReplyWeightBoost, f64,
"rust_home_mixer_bidirectional_follow_reply_weight_boost", 15.0
);
param!(
BidirectionalFollowDwellWeightBoost, f64,
"rust_home_mixer_bidirectional_follow_dwell_weight_boost", 0.0
);
Фрагмент из param.rs · код X, Apache 2.0, коммит 28e414f
Первое значение — 15.0, ровно то, чем закончилась история. Второе — 0.0: прибавка к весу задержки тестировалась в том же эксперименте и, как прямо сказано в документе, широко не поехала. Нулевой параметр в коде — это след несостоявшегося изменения, а не забытая строка.
Не сам факт эксперимента, а причина финального решения. Значение снизили с 20 до 15 не потому, что метрики стали хуже, а по совокупности: результаты экспериментов плюс жалобы пользователей на то, что во время чемпионата мира они видят недостаточно обсуждения — потому что много релевантных постов писали аккаунты, на которые они не подписаны.
Разберём механику этой жалобы, она красивая. Прибавка усиливает взаимные подписки. Усиливая их, мы автоматически ослабляем всё остальное — включая посты от незнакомых аккаунтов, которые в этот момент обсуждают событие мирового масштаба. Метрики вовлечения при этом могли оставаться прекрасными: люди активно общались со знакомыми. Проблема была не в вовлечении, а в том, что лента перестала выполнять функцию «показать, что происходит в мире».
Это ровно тот сюжет про прокси-метрики: оптимизируемая величина растёт, а продукт становится хуже по измерению, которого нет ни в одной метрике. И ровно тот довод про разнообразие, что офлайн-метрики всегда голосуют против него.
Обратите внимание и на то, что величина прибавки — 15 при базовом весе ответа 5.0, то есть для взаимных подписок вес ответа вырастает вчетверо. Это не тонкая настройка, а очень сильное вмешательство, и то, что его калибровали публично и по шагам, само по себе показательно.
Частые ошибки и подводные камни
- Читать дефолт как «так работает у всех». Это основное значение; часть трафика всегда в экспериментах с другими.
- Считать, что открытый код показывает текущее состояние в реальном времени. Синхронизация дефолтов идёт по расписанию, лаг есть.
- Забывать, что параметры обходят ревью кода. 191 параметр — это 191 способ изменить поведение продукта, минуя обычный процесс проверки изменений.
- Судить об изменении только по метрикам вовлечения. В разобранном случае метрики были хорошими, а лента перестала выполнять свою функцию — это выяснилось из обратной связи, а не из дашборда.
- Не замечать нулевые параметры. Ноль в коде часто означает «пробовали, не поехало», а не «забыли убрать».
Вопросы с собеседований
Как организовать конфигурацию рекомендательной системы, чтобы можно было экспериментировать?
Всё, что может стать предметом эксперимента, выносится в параметры, читаемые из внешней системы по конкретному запросу, а не глобально. Тогда у двух одновременных запросов значение одного параметра может отличаться, и это даёт разбиение на группы без ветвлений в коде.
Второе: каждая стадия пайплайна получает выключатель, читающий параметр. Это позволяет экспериментировать не только со значениями, но и с наличием стадии — новый источник или фильтр включается на проценте трафика.
Третье, о чём часто забывают: замеры должны сниматься на уровне фреймворка, иначе вместе с экспериментом придётся руками добавлять измерение его эффекта.
Цена подхода — дисциплина смещается из кода в процесс работы с конфигурацией: изменение поведения продукта больше не проходит ревью кода.
Если значения живут во внешней конфигурации, какой смысл в открытом коде?
Сам по себе — небольшой: вы увидите структуру и не увидите чисел. Поэтому в разобранном репозитории отдельно оговорено, что дефолты приводятся к основным продовым значениям скриптом по расписанию.
Важно правильно понимать, что это даёт. Формулировка «так работает для большинства пользователей на момент последней синхронизации» — верна. Формулировка «так работает» — нет: часть трафика в экспериментах, и между изменением в проде и его появлением в коде есть лаг.
Расскажите про случай, когда хорошие метрики скрывали проблему.
Хороший пример есть прямо в этом репозитории. Летом 2026 года ввели прибавку к весу вероятности ответа для постов от людей с взаимной подпиской: сначала значение 20, потом снизили до 15.
Причина снижения — не метрики. Они, судя по описанию, были хорошими: люди активнее общались со знакомыми. Проблема выявилась из обратной связи: во время чемпионата мира пользователи стали видеть недостаточно обсуждения события, потому что много релевантных постов писали аккаунты, на которые они не подписаны, а усиление взаимных подписок автоматически ослабило всё остальное.
Мораль ровно та, что в разговоре про прокси-метрики: оптимизируемая величина росла, а функция продукта — показывать, что происходит в мире — деградировала, и ни одна метрика вовлечения этого не показывала.
Как понять, что параметр в коде стоит нулём: это выключено или сломано?
Ноль обычно означает одно из трёх, и различать их важно. Механизм проверялся и не поехал — так в разобранном репозитории обстоит с прибавкой к весу времени задержки: её тестировали в том же эксперименте, что и прибавку к ответам, но широко не раскатили. Голова обучается, но в скор не входит — как у клика по профилю: предсказание считается, вес нулевой. Механизм ждёт включения — как холдаут инвентаря с процентами по нулям.
Общее в трёх случаях то, что ноль — это положение ручки, а не отсутствие ручки. Код механизма написан, протестирован и готов; завтра значение может стать ненулевым без единой строки изменений.
Шпаргалка одним экраном
Объявление
param!: имя · тип · ключ конфигурации · дефолт. Читается по запросу, а не глобально.
Сколько
191 параметр — 191 способ изменить поведение без выкатки.
Правдивость дефолтов
Скрипт по расписанию приводит их к основным продовым значениям.
Что дефолт не значит
Не «так у всех»: часть трафика в экспериментах. Плюс лаг синхронизации.
Эксперименты
Через выключатель стадии, читающий параметр. Работает и для значений, и для наличия стадии.
Кейс
Прибавка за взаимную подписку: 10 июля тест 5/10/15/20 → 13 июля раскатили 20 → 24 июля снизили до 15.
Почему снизили
Не метрики, а обратная связь: усиление взаимных подписок ослабило всё остальное.
Масштаб вмешательства
Прибавка 15 при базовом весе ответа 5.0 — вес вырастает вчетверо.
Нули в конфиге
Обычно «пробовали и не поехало» либо «голова учится, но в скор не входит».
Первоисточники
- params/param.rs — все 191 параметр с ключами и дефолтами.
- BIDIRECTIONAL_BOOST_CHANGE.md — история изменения по дням с диффами.
- README, раздел про эксперименты и конфигурацию.
- Курс: глава «Exploration на практике» об экспериментах, глава «Прокси-метрики и долгосрочные цели» о прокси-метриках и их расхождении с целью.