Типичные ошибки при разработке высоконагруженных проектов на Python

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

1. Использование зависимостей на чистом Python вместо зависимостей, реализованных на Си

Если вы столкнулись с задачей парсинга JSON, обратите внимание на библиотеки Си, которые можно вызывать из Python:

  • simplejson;
  • ujson;
  • python-rapidjson.

Сделайте собственные измерения производительности.

2. Использование JSON в качестве формата для сериализации по умолчанию

JSON не является самым быстрым форматом для сериализации. Существуют более эффективные решения. Обратите внимание на следующие форматы:

  • MessagePack;
  • Protocol Buffers;
  • Apache Thrift.

Для конфигурационных файлов попробуйте YAML .

3. Использование только Python при создании высоконагруженного проекта

Не ограничивайтесь Python при разработке проекта. Некоторые задачи эффективнее решать за счет использования нескольких языков программирования:

  • Golang;
  • NodeJS;
  • Rust.

Произведите тонкую настройку производительности Python:

  • Cython;
  • Расширения CPython C/C++;
  • PyPy и CFFI.

4. Использование синхронных фреймворков для задач сетевого программирования

Синхронные фреймворки, такие как Django, Flask или Pyramid неэффективны, если иметь в виду задачи сетевого программирования, в виду следующих особенностей:

  • большое количество конкурентных соединений;
  • многопоточный подход неэффективен из-за накладных расходов;
  • требуется использование выбранной реализации в бекенде:
    • poll;
    • epoll;
    • kqueue.

Используйте асинхронные фреймворки для высоконагруженных проектов: Tornado, Twisted.

Используйте и изучайте asyncio и aiohttp. А также обратите внимание aio-библиотеки, такие как aiohttp, aiopg, aiomysql, aioredis и другие.

5. В кодовой базе не используются потоки и процессы

Используйте потоки, чтобы разделить работу для задач, связанных с вводом-выводом. Например, в Flask.

Используйте процессы, чтобы масштабировать приложения, связанные с вводом-выводом, в рамках одной ноды: gunicorn + aiohttp.

Используйте потоки или процессы для делегирования блокирующих операций в задачах, связанных с процессором: ThreadPoolExecutor, ProcessPoolExecutor.

6. Развертывание нового функционала без нагрузочного тестирования

Перед тем, как развернуть новый функционал, подумайте, как может возрасти нагрузка на проект в следующих ситуациях:

  • увеличилось количество пользователей;
  • увеличилось количество данных;
  • увеличилось количество операций;
  • уменьшилось количество серверов;
  • возникли непредвиденные ситуации.

Определите наиболее затратные и частые операции:

  • вставка данных в хранилище: PostgreSQL, ElasticSearch, Redis;
  • вычисления и другие связанные с процессором задачи;
  • вызов внешних сервисов, таких как S3, например.

Предусмотрите, как операции могут быть запущены пользователями:

  • конечные точки REST API;
  • периодическая обработка собранных данных.

Собирайте показатели, связанные с операциями:

  • показатели StatsD: счетчики, таймеры;
  • показатели ноды в collectd: процессор, память, ввод-вывод.

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

  • установка сетевых соединений;
  • создание HTTP-запросов;
  • отправка некоторых данных;
  • получение информации с сервера.

Выводы

  • Используйте asyncio и aiohttp для сетевого программирования.
  • Используйте ThreadPoolExecutor для блокирующих операций
  • Используйте процессы для масштабирования в рамках ноды.
  • Осуществляйте нагрузочное тестирование новых функций перед тем, как сделать их доступными в продакшене.