DISQUSDISQUS - самая популярная система комментирования и одновременно самое большое в мире Django-приложение. Она установлена более чем на полумиллионе сайтов и блогов, в том числе и очень крупных, таких как Engadget, CNN, MTV, IGN. Основной особенностью в её реализации является тот факт, что DISQUS не является тем сайтом, который хотят увидеть пользователи, он лишь предоставляет механизмы комментирования, авторизации и интеграции с социальными сетями. Пики нагрузки возникают одновременно c появлением какой-то шумихи в Интернете, что достаточно непредсказуемо. Как же им удается справляться с этой ситуацией?

Платформа

  • Linux - операционная система
  • Python - язык программирования
  • Django - основной framework
  • Apache 2.2mod_wsgi - веб-сервер
  • PostgreSQL - СУБД
  • memcached - кэширование
  • HAProxy - балансировка нагрузки
  • Slony - репликация данных
  • heartbeat - обеспечение доступности

Статистика

  • До 17 тысяч запросов в секунду
  • 500 000 сайтов
  • 15 миллионов зарегистрированных пользователей
  • 75 миллионов комментариев
  • 250 миллионов посетителей (на август 2010г.)

Основные трудности

  • Непредсказуемость нагрузки (основными причинами шумихи в Интернете являются катастрофы и выходки знаменитостей)
  • Обсуждения никогда не теряют актуальность (нельзя держать в кэше все дискуссии с 2008 года)
  • Нельзя угадать на каком сайте из тысяч возникнет пик трафика
  • Персональные настройки, динамическое разбиение на страницы и сортировки снижают эффективность кэширования
  • Высокая доступность (из-за разнообразия сайтов и их аудитории сложно запланировать технические работы)

Архитектура

Архитектура DISQUS

  • Оборудование, в сумме около 100 серверов:
    • 30% веб-серверов (Apache + mod_wsgi)
    • 10% серверов баз данных (PostgreSQL)
    • 25% кэш-серверов (memcached)
    • 20% балансировка нагрузки и обеспечение доступности (HAProxy + heartbeat)
    • 15% прочие сервера (Python скрипты)
  • Балансировка нагрузки:
    • HAProxy:
      • Высокая производительность
      • Интеллектуальная проверка доступности
      • Неплохая статистика
  • Репликация:
    • Используется Slony-I
    • Основана на триггерах
    • Master/Slave для обеспечения большего объема операций чтения
  • Высокая доступность:
    • heartbeat
    • Пассивная копия мастер баз данных на случай сбоя основной
  • Партиционирование:
    • Реализовано на уровне кода
    • Простая реализация, быстрые положительные результаты
    • Два метода разделения данных:
      • Вертикальное:
        • Создание нескольких таблиц с меньшим количеством колонок вместо одной (она же нормализация)
        • Позволяет разделять базы данных
        • Данные объединяются в коде (медленнее, чем на уровне СУБД, но не намного)
        • Бартер производительности на масштабируемость
        • Более эффективное кэшировние
        • Механизм роутеров в Django позволяет достаточно легко реализовать данный функционал
      • Горизонтальное:
        • Некоторые сайты имеют очень большие массивы данных
        • Партнеры требуют повышенного уровня доступности
        • Помогает снижать загрузку по записи на мастер базе данных
        • В основном используется все же вертикальное партиционирование
  • Производительность базы данных:
    • Особое внимание уделяется тому, чтобы индексы помещались в оперативную память
    • Логирование медленных запросов (автоматизировано с помощью syslog-ng + pgFouine + cron)
    • Использование пулов соединений (Django не умеет этого, используется pgbouncer, позволяет экономить на ресурсоемких операциях установления и прекращения соединений)
    • Оптимизация QuerySet'ов:
      • Не используется чистый SQL
      • Встроенный кэш позволяет выделять части выборки
      • Но это не всегда нужно, они убрали этот кэш
    • Атомарные операции:
      • Поддерживают консистентность данных
      • Использование update(), так как save() не является thread-safe
      • Отлично работают для таких вещей, как счетчики
    • Транзакции:
      • TransactionMiddleware поначалу использовалось, но со временем стало обузой
      • В postgrrsql_psycopg2 есть опция autocommit:
        • Это означает что каждый запрос выполняется в отдельной транзакции
        • Обработка каждого пользовательского HTTP-запроса не начинает новую транзакцию
        • Но все же транзакции из нескольких операций записи в СУБД нужны (сохранение нескольких объектов одновременно и полный откат в случае ошибки)
        • В итоге все HTTP-запросы по-умолчанию начинаются в режиме autocommit, но в случае необходимости переключаются в транзакционный режим
  • Отложенные сигналы:
    • Постановка в очередь низкоприоритетных задач (даже если они не длинные по времени)
    • Асинхронные сигналы очень удобны для разработчика (но не так, как настоящие сигналы)
    • Модели отправляются в очередь в сериализованном виде
  • Кэширование:
    • Используется memcached
    • Новый pylibmcна основе libmemcached в качестве клиента (проекты django-pylibmc и django-newcache)
    • Настраиваемые алгоритмы поведения клиента
    • Используется _auto_reject_hosts и _retry_timeout для предотвращения повторных подключений к вышедшим из строя кэш-серверам
    • Алгоритм размещения ключей: консистентное хэширование на основе libketama
    • Существует проблема, когда одно очень часто используемое значение в кэше инвалидируется:
      • Множество клиентов одновременно пытаются получить новое значение из СУБД одновременно
      • В большинстве случаев правильным решением было бы вернуть большинству устаревшие данные и позволить одному клиенту обновить кэш
      • django-newcache и MintCache умеют это делать
      • Заполнение кэша новым значением вместо удаления при инвалидации также помогает избежать этой проблемы
  • Мониторинг:
    • Информация о производительности запросов к БД, внешних вызовов и рендеринге шаблонов записывается через собственный middleware
    • Сбор и отображение с помощью Ganglia
  • Отключение функционала:
    • Необходим способ быстро отключить новый функционал, если оказывается, что он работает не так, как планировалось
    • Система должна срабатывать мгновенно, по всем серверам, без записи на диск
    • Позволяет запускать новые возможности постепенно, лишь для части аудитории
    • Позволяет постоянно использовать основную ветку кода
    • Аналогичная система используется и в Facebook
  • Масштабирование команды разработчиков:
    • Небольшая команда
    • Месячная аудитория / количество разработчиков = 40 миллионов
    • Это означает:
      • Автоматическое тестирование
      • И максимально простой процесс разработки
    • Новый сотрудник может начать работать уже через несколько минут, нужно лишь:
      • Установить и настроить PostgreSQL
      • Скачать исходный код из git
      • С помощью pip и virtualenv установить зависимости
      • Изменить настройки в settings.py
      • Выполнить автоматическое создание структуры данных средствами Django
  • Непрерывное тестирование:
    • Ежедневное развертывание с помощью Fabric
    • Hudson обеспечивает регулярно осуществляет и тестирует сборки
    • Интегрирован Selenium
    • Быстрое тестирование с помощью Pyflakes и post-commit hooks
    • 70 тысяч строк Python кода, 73% покрытие тестами, прогон всех тестов занимает 20 минут
    • Собственная система исполнения тестов с поддержкой XML, Selenium, подсчета количества запросов, тестирования Master/Slave базы данных и интеграцией с очередью
  • Отслеживание проблем и задач:
    • Переключились с Trac на Redmine (из-за поддержки под-задач)
    • Отправка исключений на e-mail - плохая идея
    • Раньше использовали django-db-log, но теперь опубликовали свою систему сбора ошибок и логов под названием Sentry

Делаем выводы

  • Язык программирования, каким бы он ни был, не является проблемой
  • Django в целом очень хорош (но приходится все же использовать набор собственных патчей)
  • Даже при использовании низкопроизводительного framework можно построить масштабируемую систему
  • Вертикальное партиционирование позволяет пожертвовать производительностью в пользу масштабируемости
  • Даже небольшой командой разработчиков можно добиться высоких результатов, если не пренебрегать автоматизацией тестирования
  • Большое значение имеет возможность вовремя отслеживать и оперативно реагировать на сбои

Источник информации

Данная статья написана на основе выступления Jason Yan и David Cramer на DjangoConf 2010. В презентации можно найти примеры кода, ссылки на упоминаемые проекты и дополнительные материалы:

Другие статьи по масштабируемости высоконагруженных систем можно почитать в соответствующем разделе, а вовремя узнавать о новых - подписавшись на RSS. Вчера, кстати, прикрутил DISQUS к Insight IT, приглашаю постоянных читателей и всех остальных потестировать :)