Обновить

FastAPI на AMD FX-8320: оптимизация P99 latency в условиях ограниченных ресурсов (HDD, DDR3)

Уровень сложностиСредний
Время на прочтение10 мин
Охват и читатели11K
Всего голосов 14: ↑14 и ↓0+15
Комментарии11

Комментарии 11

Спасибо за работу, но всё же есть замечания и вопросы.

1) Не увидел итоговой инфы, на сколько какое изменение улучшило производительность. Есть куча таблиц, графиков, но все они разные и по разным аспектам, и непонятно, например, стоило ли вводить локальный кэш?

2) Увидел, что в одном месте в середине статьи, что итоговая RPS увеличилась на 40% от базовой без прокси, и упала на 40% от базовой с прокси, и составляет около 10 RPS. Ну и тысячи RPS на пингах. Как-то не вяжется с утверждением в начале, что "выдаёт тысячи RPS" и всё такое. Не думаю, что основная нагрузка среднего сервиса - это пинги. А реальные запросы как-то не ускорились - 40% вверх это даже не на порядок. А 40% вниз (с прокси) это совсем печально.

3) Удивлен, что Postgres не имеет автонастройки при установке или хотя бы утилиты для автонастройки параметров в зависимости от профиля оборудования (но я не DBA, не знаю, как с этим у других БД). Или что такое средство, если оно есть, не различает SSD и HDD (и так далее).

4) Опять везде вижу эту задержку/lanency. Этот термин раньше означал именно задержку на этапе передачи сигнала, а не время обработки. Сейчас же вижу его не первый раз в смысле "время обработки". Это уже новое устоявшееся значение или ленивость авторов?

5) Да, и самое интересное:

В ходе нагрузочных тестов я выделил 4 критических узких места

Вот тут бы рассказать, как автор выделил эти места и как определил, какие конкретные проблемы он раскопал. Вполне потянет на отдельную полезную статью.

То тесть, дано: маленький RPS. Требуется: определить узкие места в системе, включая БД и ОС. Не просто узкие места, а почему они узкие и как их расширить.

6) В догонку. Интересно, на что тратится наибольшее время и CPU в финальной версии, для бизнес-запросов (не пингов). Я про то, что если заменить всё на Java, например, не улучшится ли что-то? Или нет?

По-моему это просто волчарская статья из расчета, что никто в цифрах разбираться не будет. Посмотрите в профиле автора "О себе" - чистая реклама насколько он что там увеличил.

Добрый день!

Для ясности: в профиле прямо указано, что это итоги R&D проекта IronTrack. Это мой личный открытый проект (Open Source), исходный код которого вместе со всеми конфигурациями доступен по ссылкам в конце статьи.
Любой желающий может склонировать репозиторий и воспроизвести замеры. Конечно, на другом железе абсолютные цифры будут отличаться, но в самом репозитории я выложил детальные бенчмарки с таблицами и ADR (Architectural Decision Records), где обосновано каждое изменение.

Для ясности: у вас обычный пет-проект ради портфолио без какого-либо смысла. Просто скаффолднутое апи на ворохе технологий, которые хорошо выглядят в глазах рекрутера, который не будет разбираться детально.

Если это будет смотреть разработчик, то вам эта хайпорепа пойдет скорее в минус, ибо навайбкожено и оверкильно. А кейз оптимизации вообще смотрится забавно. Из него торчат уши того, что у вас есть fx-8320 и есть только HDD. Хотя сегодня еще придется поискать систему не на SSD, если вам захочется похостить свой API.

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

Удачи в трудоустройстве.

Сергей, спасибо за внимательное прочтение и конструктивные замечания! Постараюсь ответить развернуто:

1. Про профит от изменений (L1-кэш и Ed25519)

Согласен, сводная таблица «до/после» сделала бы картину нагляднее — добавлю её в материал.
Рост RPS в сценарии чтения профиля (с 1613 до 2384, +48%) был достигнут за счёт двух факторов:

  • перехода с RSA на Ed25519 (снижение стоимости криптографии)

  • введения гибридного L1/L2-кэша

Почему L1 важен: на архитектуре FX с DDR3 даже сетевой round-trip и сериализация данных из Redis/Valkey создают ощутимые задержки. Локальный кэш (cashews) позволяет "горячим" данным оставаться в адресном пространстве процесса, снижая нагрузку на Event Loop и уменьшая вариативность latency.
Эффект действительно синергетический — каждое изменение даёт вклад, но вместе они заметно уменьшают стоимость запроса.

2. Тысячи RPS vs 10 RPS на регистрации

Здесь важно разделять характер нагрузки. Для большинства Read-Heavy систем чтение составляет основную часть трафика (часто до 90%), и именно на таких сценариях (/access/me) оптимизация дает обещанные «тысячи».
Регистрация же — это чистый CPU-bound. Argon2id — memory-hard алгоритм, и 10 RPS здесь — физический предел вычислительных блоков FX-8320 при выбранных параметрах безопасности.

Про падение с прокси:
Да, в proxy-mode throughput ниже — это ожидаемая плата за:

  • TLS-шифрование

  • дополнительный context switching

  • сетевой hop

Задача прокси в данном эксперименте была не в максимальном RPS, а в стабилизации нагрузки и более ровном распределении latency под пиками.
Это компромисс между "сырой" пропускной способностью и устойчивостью.

3. Про автонастройку PostgreSQL

PostgreSQL поставляется с консервативными дефолтами, чтобы гарантированно работать на разных конфигурациях.

Инструменты вроде pgtune существуют, но они не знают:

  • реальный профиль нагрузки

  • характер данных

  • фактическую производительность конкретного HDD

Параметр random_page_cost напрямую влияет на решения планировщика. На HDD его значение принципиально важно для корректного выбора между sequential scan и index scan.

В моём случае корректировка позволила избежать неэффективных планов при росте объёма данных.

4. Latency vs Response Time

Вы правы, в телеком-контексте latency — это задержка распространения сигнала.

В веб-бенчмарках (включая wrk) под latency обычно понимается end-to-end response time — от отправки запроса до получения полного ответа.
В статье термин используется именно в этом прикладном смысле.

5. Про диагностику (Bottlenecks)

В статье действительно больше внимания уделено решениям, чем самому процессу диагностики.
Процесс поиска узких мест (инструменты типа htop, анализ EXPLAIN ANALYZE) заслуживает отдельной статьи.
Если вкратце, блокировку цикла ловил по резкой деградации отзывчивости "легких" эндпоинтов при нагрузке на регистрацию.

6. Про Java/Go

В финальной версии на "тяжелых" запросах (как регистрация) основная утилизация CPU уходит на выполнение инструкций алгоритма Argon2id. Поскольку это memory-hard и CPU-bound задача, смена языка на Java или Go не даст кратного прироста — те же математические операции будут выполняться на тех же ядрах FX-8320.

Основной профит от Java/Go был бы заметен в пропускной способности (throughput) при огромном количестве одновременных соединений за счет более легких потоков/горутин. Но в условиях моего стенда (HDD и 12 ГБ RAM) узким местом становится физика диска и вычислительная сложность криптографии, а не накладные расходы в данном сценарии.

Access/me и до оптимизации давал около 1.5К RPS. Так что же поменялось?

инструменты не знают... фактическую производительность конкретного HDD

А за 1 секунду её разве нельзя прикинуть, прогнав тривиальный тест? Поэтому и говорю - удивительно, что этого нет.

В финальной версии на "тяжелых" запросах (как регистрация) основная утилизация CPU уходит на выполнение инструкций алгоритма Argon2id.

Откуда такие данные? Непонятно. Нужны измерения, а не рассуждения - здесь и везде в статье.

А что там про кэш самой Postgres? Уверен, что 12GB RAM с лихвой хватит на всю БД для нескольких сотен тясяч пользователей.

за счет более легких потоков/горутин.

А при чём тут лёгкие потоки и горутины? Ведь FastAPI не создаёт по физическому потоку на каждый запрос - как и Java/Go.

Про RPS и "что изменилось": Согласен, 1.5К — уже достойный результат. Но для меня целью было не только поднять пиковый RPS, но и сделать систему более «холодной» и предсказуемой. Замена RSA на Ed25519 и внедрение L1-кэша позволили снизить нагрузку на ядра FX в моменты пиков, что критично для старой архитектуры. В итоге получили более стабильный P99, высвободив ресурсы CPU для других задач.

Про тюнинг HDD: Ваша идея с авто-тестом интересна, но я исходил из того, что HDD — устройство с нелинейной задержкой. Скорость зависит от фрагментации, текущей очереди I/O и даже области диска. Ручной тюнинг random_page_cost — это осознанный способ заставить планировщик Postgres быть «пессимистом» по отношению к медленному шпинделю, не полагаясь на моментальные замеры, которые могут быть обманчивы.

Про Argon2id и измерения: Здесь я полагался на фундаментальные свойства алгоритма (он спроектирован быть тяжелым для CPU и RAM по дизайну) и мониторинг через htop. Согласен, что детальный профайлинг через py-spy или Flame-графики — это отличная точка роста для следующих материалов, спасибо за наводку.

Про кэш БД и 12GB RAM: Для чтения этого объема действительно хватит. Однако регистрация — это Write-Heavy сценарий. Кэш в RAM не отменяет физическую необходимость записи WAL-логов на диск для соблюдения ACID. На HDD 7200 RPM именно стадия fsync (сброс данных на блины) остается бутылочным горлышком, которое нельзя полностью нивелировать объемом оперативной памяти.

Про горутины и Python: Мой поинт был не в количестве потоков, а в модели планировщика. В Python асинхронность кооперативная: если один запрос занял CPU тяжелой математикой (Argon2), он блокирует весь Event Loop. В Go есть механизм вытеснения (preemption), который позволяет рантайму более гибко распределять ресурсы даже на CPU-bound задачах. Использование ThreadPoolExecutor в моем случае — это необходимый способ изолировать тяжелую криптографию от основного цикла, чтобы сохранить отзывчивость системы.

Снизить нагрузку... Более стабильный... Опять одни рассуждения. Мне как технарю, да и большинству читателей эти рассуждения и так понятны. Что непонятно, так это конкретные цифры - была такая-то нагрузка на CPU, а стала такая-то. А логично рассуждать, приходя к противоположным результатам, мы все умеем.

Про тест HDD, я имел ввиду утилиту или установочную программу от Postgres, а не вас.

Скорость зависит от фрагментации, текущей очереди I/O и даже области диска.

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

На HDD 7200 RPM именно стадия fsync... остается бутылочным горлышком

Опять рассуждения вместо тестов и замеров. Не рассуждений ждёт проницательный читатель)

Мой поинт был не в количестве потоков, а в модели планировщика

Но вы же прямо написали "за счёт более лёгких потоков". А теперь говорите "это не я". Нехорошо)

6) На Go вам не нужно было бы заниматься ручным распределением по воркерам тяжёлых задач. Рантайм Гошки уже имеет достаточно хороший шедулер.

И не нужен был бы «сглаживающий прокси». Даже http сервер из стандартной библиотеки достаточно хорош.

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

Но это всё, безусловно, от лукавого. Если вам ближе Python, то конечно делайте на нём. А кто-то и на Perl бы сделал, и на TCL, и не нам их осуждать.

Занимательная статья, спасибо

А можно было просто реализовать на Go!

Простите, я троллю.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации