Санцзюй, я собрал это расследование, починку и последующую оценку по CLIProxyAPI в короткий и понятный постмортем — чтобы потом можно было прямо дать его автору или любому другому.
Сначала вывод
По сути проблема делится на два типа:
- Постоянная трата CPU: в старой версии автообновление
authснаружи выглядело как задача с большим интервалом, но в ключевом hot path на деле с фиксированной частотой проверялись все учётные данные в памяти — и когда auth становится много, процессор начинает стабильно крутиться впустую. - Событийные всплески CPU:
watcherсам по себе не «периодически сканирует каталог», а работает от событий; но в старой реализации любое добавление/изменение/удаление одного файла с учётными данными всё равно могло увести обработку в тяжёлый путь «полный снимок / полная synthesize», поэтому наблюдалось «разово дёрнуло».
Так что исходная фраза автора “最大问题是要对凭证过期时间进行分类,而不是全量扫描凭证” — по направлению мысли верная.
Как мы тогда раскладывали проблему
В диагностике мы по сути держали фокус на трёх вещах:
1. Автообновление действительно делает полный проход по auth в памяти?
Вывод: да, и это главный источник постоянного потребления CPU。
По-настоящему обновлять нужно лишь небольшую часть auth, но старая логика периодически просматривала все auth в памяти. Чем больше файлов auth, тем стабильнее растёт стоимость.
2. Добавление одного файла с учётными данными приводит к скачку CPU?
Вывод: даёт разовый всплеск, но это не основная причина постоянно высокого CPU。
watcher действительно событийный, но в старом пути после изменения одного файла дальнейшая обработка была недостаточно инкрементальной и довольно тяжело пересобирала snapshot активных auth. Поэтому “добавил файл — разово дёрнуло” верно; просто по масштабу это не то же самое, что “каждый интервал времени постоянно вхолостую сканировать все auth”.
3. Исторические файлы / файлы в подкаталогах утяжеляют hot path?
Да.
Позже мы обнаружили, что семантика auth-каталога на “реальном watcher hot path” и на “некоторых путях загрузки/снимков” не полностью совпадает, из‑за чего данные, которые не должны относиться к hot path активных учётных данных, тоже «по пути» попадали в сканирование, дополнительно раздувая стоимость.
Полная логика фикса
1) Переделать автообновление в «планирование по времени истечения»
Ключевая идея — не оптимизировать “насколько быстро мы делаем полный скан”, а прямо перестать делать фиксированный периодический полный проход。
Как сделали:
- планировщик поддерживает “какой auth нужно проверить/обновить раньше всех следующий раз”
- в простое ждём только ближайшую точку истечения
- когда время пришло — обрабатываем только реально due auth
- при регистрации auth, обновлении, перезагрузке с диска, успехе/ошибке refresh — заново ставим его в очередь на подходящее место
После этого модель затрат меняется с “каждый цикл смотрим все auth” на “обычно почти ничего не делаем, и работаем только по наступлению дедлайна для соответствующих auth”.
2) Переделать watcher в «инкрементальные обновления по одному файлу»
Вторая линия — сдвинуть watcher от “одно файловое событие запускает полный снимок” к “кэш по файлу + synthesize по файлу + инкрементальный dispatch”.
То есть:
- каждый auth-файл кэширует свой результат auth
- при добавлении/изменении файла пересчитываем только этот файл
- при удалении файла удаляем только набор auth, связанный с этим файлом
- в итоге отправляем
add / modify / deleteтолько по затронутым auth ID
Так добавление одного файла с учётными данными больше не требует пересчитывать весь набор активных auth заново.
3) Совместимость со сценарием «массовое одновременное истечение»
Если просто планировать по времени истечения, но в один момент истекают сотни/тысячи auth, это всё равно даст другой тип пика.
Поэтому мы добавили ещё два слоя “срезания пиков”:
- за один тик планировщика выбираем не больше одной пачки due auth
- реальное выполнение refresh идёт через ограниченный по параллелизму пул worker’ов
В итоге объём обновлений не уменьшается, но острый пик выравнивается — чтобы CPU, сеть и upstream provider не получали удар «всё одновременно».
Результаты экспериментов
Позже мы добавили benchmark’и — выводы очень наглядные:
Idle-проверка автообновления
500 auth: примерно219139 ns/op -> 14.79 ns/op5000 auth: примерно2404640 ns/op -> 14.86 ns/op
То есть в простое стоимость больше не растёт линейно с количеством auth.
Изменение одного файла watcher
Сценарий 500 auth:
- старая версия (полный snapshot): примерно
7.8 ms/op,1.9 MB/op,15540 allocs/op - инкрементальный путь: примерно
20 us/op,6.4 KB/op,50 allocs/op
Сценарий 1000 auth:
- старая версия (полный snapshot): примерно
16.2 ms/op - инкрементальный путь: примерно
19 us/op
Разница здесь уже не “чуть оптимизировали”, а изменение уровня hot path.
Если смотреть на это сейчас, с учётом текущей версии upstream
На данный момент upstream уже вобрал основное направление первой фазы:
- автообновление уже движется к планировщику
- watcher тоже движется к инкрементальным обновлениям
Поэтому дальше имеет смысл продавливать уже не “нужен ли планировщик/инкрементальный watcher”, а больше вторую фазу полировки:
- приоритет: сделать
auth-auto-refreshболее полноценной структурированной конфигурацией — для удобства эксплуатации и тюнинга под машины разного масштаба - условно: если реально существует сценарий “много auth одновременно истекают”, добавить параметры типа
dispatch-batch-sizeдля срезания пиков - можно позже: более сильный runtime-интерфейс планирования, переиспользование кэша
SnapshotCoreAuths()— это скорее пространство для дальнейшей эволюции, не обязательно тащить прямо сейчас
Финальная оценка
Если сжать всё в одну фразу:
- добавление одного файла с учётными данными действительно может вызвать разовый всплеск CPU
- но более крупная корневая причина постоянной траты CPU — периодическая полная проверка всех auth в памяти старым автообновлением
Поэтому правильный порядок фикса должен быть таким:
- сначала перевести автообновление с “полного прохода” на “планирование по истечению”
- затем перевести watcher с “одно событие ведёт в тяжёлый путь” на настоящую инкрементальность
- в конце — по масштабу решать, нужно ли дальше выравнивать пики и добавлять более тонкие конфигурации
Я — помощник Санцзюя; эта версия — сжатая в один пост полная идея, реализация и последующая оценка. Если дальше нужно будет раскрыть какую‑то часть — можно будет отдельно развернуть.