$30 off During Our Annual Pro Sale. View Details »

Имя мне скорость

Имя мне скорость

Сейчас существует больше 20 способов ускорить CPU-bound код в Python, и их количество растет чуть ли не каждый день. Python принято ускорять всеми способами. Сегодня наступает момент, когда пора поговорить еще и о скорости веб-фреймворков. При этом слово «скорость» в мире веб-разработки — это многозначный термин.

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

В таких сложных условиях спикер попытается найти самые быстрые решения на Python, сравнить их между собой, и попытаться ответить на вопрос: «Что, если я хочу делать быстро отвечающие веб ручки?». Его бенчмарки, безусловно, будут субъективными. Однако все фреймворки, которые он покажет, вместе еще не сравнивали. А некоторые из них, скорее всего, вы даже не видели.

Полезные ссылки:
https://piterpy.com/talks/958a111a3f0f4bed9756f1e3e9f35d0a/
https://github.com/xfenix/piterpy-imya-mne-skorost

Denis Anikin

November 13, 2023
Tweet

More Decks by Denis Anikin

Other Decks in Programming

Transcript

  1. Денис Аникин
    https://xfenix.ru
    Имя мне скорость

    View Slide

  2. Денис Аникин
    2
    Что я делаю
    — работаю в Райфе
    — lead разработки в 3+ командах
    — community lead в Python Community
    — fullstack: typescript, python, devops
    — выступаю на конференциях и митапах
    https://xfenix.ru

    View Slide

  3. А зачем, собственно?
    3

    View Slide

  4. Давайте поговорим о том, зачем
    4
    —Текущая ситуация: бенчмарков мало
    —Techempower ближе всех, но там пока немного актуальных сочетаний
    (например, fastapi + gunicorn + uvicorn)
    —Ещё один набор «цифр» для сравнения
    —Хочется понять что можно взять для действительно быстрой работы

    View Slide

  5. Как я пришел к этой проблеме?
    5
    —Я большой поклонник идеи «взять самое быстрое и показать максимальную
    производительность сразу»
    —Мне не нравится позиция «для нашего сервиса/юзеров достаточно и …»
    —Мне не нравится позиция «ну у нас всего-то 1 rps, подождут»
    —Мне не нравится позиция «бизнесу важнее фичей напилить, чем
    заморачиваться по производительности»
    —Почему? Потому, что сделать хорошо сразу — не так дорого
    —Почему? Потому, что хочется делать качественные сервисы для любых
    пользователей

    View Slide

  6. View Slide

  7. Да и просто по
    приколу
    7

    View Slide

  8. Методика
    8

    View Slide

  9. Что делаем
    9
    —Собираем тестовое приложение на docker-compose
    —Отправляем на него нагрузку
    —Измеряем
    —Повторяем N раз с разными docker compose файлами

    View Slide

  10. Почему compose?
    10

    View Slide

  11. Самый «дешёвый»
    способ проверить
    работу в контейнерах
    11

    View Slide

  12. И на compose
    действительно у
    кого-то работает
    продакшн
    12

    View Slide

  13. Наш сетап
    13

    View Slide

  14. Что за железо использовалось на сервере?
    14
    —Сервер: недорогой VPS за стоковую цену в 959 рублей
    —Тип процессора: Intel(R) Xeon(R) Silver 4214 CPU @ 2.20GHz
    —Количество выделенных ядер: 4
    —Оперативная память: 8 гигабайт
    —Виртуализация: KVM
    —Канал: 100 мегабит
    —ОС: Ubuntu 22.04.3 LTS

    View Slide

  15. Откуда шла нагрузка?
    15

    View Slide

  16. View Slide

  17. Macbook Apple M1 Pro
    17

    View Slide

  18. Что по софту?
    18
    —В основном: python 3.12, где нельзя, там 3.11
    —Образ на базе Debian (с alpine слишком много проблем)
    —Если можно сделать эффективную сборку без сайд-эффектов, мы делаем
    (поставить uvloop, например и/или не запускаем на голом uvicorn)
    —Несколько рестовых ручек. Одна с базой, другая без
    —Используем реальную базу — redis
    —Используем валидацию, pydantic
    —Пишем код с аннотациями
    —Оптимизируем, что можем
    —Nginx как реверс прокси, но конфигурацию не оптимизируем (мне немного
    лениво)

    View Slide

  19. Как борюсь с субъективностью
    19
    —Железо одинаковое
    —Нагрузка одинаковая
    —docker-compose максимально похожие
    —Nginx, ubuntu, postgres, redis — идентичны

    View Slide

  20. Почему без pypy?
    20

    View Slide

  21. Почему не с postgres?
    21

    View Slide

  22. View Slide

  23. Проблемы базы с нагрузкой
    23
    — Упирается в диск на 1 машине. А мы то хотим не базу и не диск
    бенчмаркать (!)

    View Slide

  24. Ну так взял бы в
    кластере

    View Slide

  25. Проблемы базы с нагрузкой
    25
    — Упирается в диск на 1 машине. А мы то хотим не базу и не диск
    бенчмаркать (!)
    — В кластере мы упираемся в коннекшены

    View Slide

  26. Сделал бы больше

    View Slide

  27. Проблемы базы с нагрузкой
    27
    — Упирается в диск на 1 машине. А мы то хотим не базу и не диск
    бенчмаркать (!)
    — В кластере мы упираемся в коннекшены
    — С коннекшенами мы упираемся в оперативку/пулинг

    View Slide

  28. Купил бы кластер
    пожирнее

    View Slide

  29. Проблемы базы с нагрузкой
    29
    — Упирается в диск на 1 машине. А мы то хотим не базу и не диск
    бенчмаркать (!)
    — В кластере мы упираемся в коннекшены
    — С коннекшенами мы упираемся в оперативку
    — Я плохо админю сеть и постгрю (а мы не их бенчмаркаем!)
    — А ещё вопрос в драйвере/orm

    View Slide

  30. С redis мы лишены
    многих этих
    недостатков
    30

    View Slide

  31. Спорно: пробую
    местами заменить
    pydantic 2 на msgspec
    31

    View Slide

  32. View Slide

  33. Нагрузку шлём k6
    33

    View Slide

  34. View Slide

  35. Что мы измеряем?
    35

    View Slide

  36. Важные числа
    36
    Я ставил перед собой задачу получить +- близкие к реальности цифры по этим категориям:
    —RPS
    —Latency

    View Slide

  37. Мы сравниваем не
    абсолютные числа, а друг
    относительно друга в
    одинаковых условиях!
    37

    View Slide

  38. AioHTTP для начала
    38

    View Slide

  39. 39
    Результаты

    View Slide

  40. 40
    В процессе теста

    View Slide

  41. Результаты кратко
    41
    —0.5к RPS
    —1.5s latency p90

    View Slide

  42. 42
    Результаты — запись

    View Slide

  43. 43
    В процессе теста

    View Slide

  44. Результаты кратко
    44
    —0.5к RPS
    —1.9s latency p90

    View Slide

  45. FastAPI +
    gunicorn +
    uvicorn
    45

    View Slide

  46. Пару слов о связке
    46
    —Есть связка fastapi + uvicorn
    —Есть связка fastapi + gunicorn + uvicorn
    —Различие лежит в подходе к скейлингу. В gunicorn связке скейлит gunicorn, в варианте
    только с uvicorn — мы можем использовать внешние «мощности» вроде k8s
    —Однако, бывает, что gunicorn + uvicorn используется и в k8s
    —Есть ли разница в том кто поднимает процессы?
    —Измерить бы хотелось всё, но мы ограничены в ресурсах (включая время)

    View Slide

  47. 47
    Результаты

    View Slide

  48. 48
    В процессе теста

    View Slide

  49. Результаты кратко
    49
    —~0.5к RPS
    —2s latency p90

    View Slide

  50. На запись результаты
    схожие (смысла
    смотреть их нет)
    50

    View Slide

  51. Litestar +
    gunicorn +
    uvicorn
    51

    View Slide

  52. Что такое litestar?
    52
    —Для меня — «сверхновая» из мира фреймворков
    —Потенциальная замена fastapi
    —Хорошие практики
    —Классная архитектура
    —Большое количество функций
    —При этом удобство, близкое к fastapi

    View Slide

  53. Пару фишек, что зашли
    53
    —Явные DTO (меньше магии как к FastAPI)
    —Отличная интеграция с SqlAlchemy и вывод из моделей DTOшек и даже polyfactory
    на их основе
    —Интеграция с prometheus и opentelemetry
    —Channels — по сути, мини-фреймворк аля propan, встроенный для EDA (!!!)

    View Slide

  54. View Slide

  55. Еще radix роутинг
    55

    View Slide

  56. View Slide

  57. 57
    Результаты

    View Slide

  58. 58
    Что там по загрузке

    View Slide

  59. Результаты кратко
    59
    —~0.5к RPS
    —1.83s latency p90

    View Slide

  60. Интересно, что мы с
    techempower
    разошлись
    60

    View Slide

  61. Что же следующее?

    View Slide

  62. Загадочный
    следующий блок
    62

    View Slide

  63. FastAPI + Granian
    63

    View Slide

  64. Что за granian?
    64

    View Slide

  65. View Slide

  66. View Slide

  67. А ещё…
    67

    View Slide

  68. Я всё переписал на rust’е,
    почему мне до сих пор больно
    писать софт?

    View Slide

  69. Кстати, так, наверное,
    правильнее?
    69

    View Slide

  70. Я всё переписал на rust’е,
    почему мне до сих пор больно
    писать софт?

    View Slide

  71. Чуть больше о granian
    71
    —Призван заменить gunicorn + uvicorn
    —Поддерживает ASGI, WSGI, RSGI (!)
    —Использовать просто: granian --interface asgi main:app
    —Супервизор
    —Амбициозно?
    —BISTRO (мы же хотим быстро)

    View Slide

  72. А теперь вернемся к
    связке fastapi + granian
    72

    View Slide

  73. 73
    Результаты

    View Slide

  74. 74
    В процессе теста

    View Slide

  75. Результаты кратко
    75
    —~1.2к RPS
    —0.4s latency p90

    View Slide

  76. «Ммм, это очень
    обнадеживающие результаты!»

    View Slide

  77. А сможете сейчас
    угадать фреймворк?
    77

    View Slide

  78. Robyn
    78

    View Slide

  79. View Slide

  80. View Slide

  81. View Slide

  82. View Slide

  83. 83
    Вот так выглядит код на нём (приблизительно)
    from robyn import Robyn
    app = Robyn(__file__)
    @app.get("/")
    async def get_really_important(request):
    return "Hello, world!"
    app.start(port=8080)

    View Slide

  84. 84
    Результаты

    View Slide

  85. 85
    В процессе теста — приятно

    View Slide

  86. Результаты кратко
    86
    —1.1к RPS
    —0.3s latency p90

    View Slide

  87. Спорю, что тут никто,
    кроме одного
    человека, не
    догадается…
    87

    View Slide

  88. Внезапно: ucall
    88

    View Slide

  89. Что такое ucall?
    89
    —RPС фреймворк…
    —Быстрее FastAPI и gRPC
    —На питоне

    View Slide

  90. View Slide

  91. View Slide

  92. 92
    Немного на кодовом
    from ucall.posix import Server
    # from ucall.uring import Server on 5.19+ (это про ядро линукса)
    server = Server()
    @server
    def sum(a: int, b: int):
    return a + b
    server.run()

    View Slide

  93. 93
    Результаты
    Провалено

    View Slide

  94. Пару слов в конце секции
    94
    —Не завел ни локально, ни на серваке, ни в контейнере (но я пытался)
    —Все супер сырое и документации, считай, что нет
    —Большой вопрос как масштабировать это решение (там есть кое-что про
    треды, но настройка недоступна)
    —Всё таки это JSON RPC в первую очередь, REST сделать у меня не
    удалось
    —Если вы хотите, исходники эксперимента есть в моем репозитории

    View Slide

  95. Ну и зачем ты
    затащил…?
    95

    View Slide

  96. View Slide

  97. View Slide

  98. View Slide

  99. Спорим, я вновь вас
    удивлю?
    99

    View Slide

  100. Э, то есть, больше, чем
    в предыдущей секции
    не выйдет, но…
    100

    View Slide

  101. Внезапно: socketify.py
    101

    View Slide

  102. Эпоха свершений: japronto, vibora

    View Slide

  103. View Slide

  104. View Slide

  105. Что даёт socketify.py?
    105
    —Вебсокеты
    —HTTP/HTTPS
    —Встроенный ASGI (внезапно) / WSGI

    View Slide

  106. View Slide

  107. 107
    А что их бенчмарки показывают…

    View Slide

  108. 108
    Результаты

    View Slide

  109. Если вы думали, что
    всё пойдет хорошо, то
    вы молодцы, но ЗРЯ
    109

    View Slide

  110. View Slide

  111. /usr/local/lib/python3.11/site-packages/socketify/libsocketify_linux_amd64.so:
    cannot open shared object file: No such file or directory. Additionally,
    ctypes.util.find_library() did not manage to locate a library called
    '/usr/local/lib/python3.11/site-packages/socketify/libsocketify_linux_amd64.so'
    В докере его не запустить никак сейчас

    View Slide

  112. Но я решил, что шатнуть
    основы эксперимента и
    прогнал прям на
    сервере…
    112

    View Slide

  113. 113
    В процессе теста

    View Slide

  114. 114
    Если честно, результаты такие, что слюнки текут

    View Slide

  115. Результаты кратко
    115
    —1.7к RPS
    —37ms latency p90

    View Slide

  116. View Slide

  117. Ещё я хотел сделать:
    FastAPI +
    socketify.py asgi
    117

    View Slide

  118. Что?
    118

    View Slide

  119. Socketify.py ещё и asgi
    119

    View Slide

  120. И:
    Litestar +
    socketify.py asgi
    120

    View Slide

  121. Но решил, что не буду
    этого делать, т.к.
    условия эксперимента
    нарушились…
    121

    View Slide

  122. Что еще стоило бы
    протестировать?
    Blacksheep
    122

    View Slide

  123. Где и для чего
    подойдут какие
    фреймворки?
    123

    View Slide

  124. Для быстрых рестов
    124
    —Robyn? Поговорим о минусах
    —Socketify? Уф, какая сложная идея

    View Slide

  125. Для надежной и предсказуемой разработки
    125
    —Вы не поверите, всё ещё fastapi, но с granian!
    —А я лично выбираю litestar c granian!

    View Slide

  126. MsgSpec vs Pydantic?
    126

    View Slide

  127. Возьмем litestar и
    заменим pydantic на
    msgspec
    127

    View Slide

  128. 128
    Результаты

    View Slide

  129. 129
    Что там по загрузке

    View Slide

  130. 130
    Обнаружилась существенная корелляция

    View Slide

  131. Я так и не смог это
    посчитать
    существенной
    разницей
    131

    View Slide

  132. Ссылки и
    исходный код
    132

    View Slide

  133. Ссылки/исходники
    133
    —https://github.com/xfenix/piterpy-imya-mne-skorost
    —https://github.com/vibora-io/vibora
    —https://github.com/squeaky-pl/japronto
    —https://github.com/unum-cloud/ucall
    —https://github.com/cirospaciari/socketify.py
    —https://robyn.tech/
    —https://jcristharif.com/msgspec/

    View Slide

  134. View Slide

  135. Денис Аникин
    https://xfenix.ru
    https://github.com/xfenix/
    Спасибо!
    Задавайте ответы!

    View Slide