DISQUS - самая популярная система комментирования и одновременно самое большое в мире Django-приложение. Она установлена более чем на полумиллионе сайтов и блогов, в том числе и очень крупных, таких как Engadget, CNN, MTV, IGN. Основной особенностью в её реализации является тот факт, что DISQUS не является тем сайтом, который хотят увидеть пользователи, он лишь предоставляет механизмы комментирования, авторизации и интеграции с социальными сетями. Пики нагрузки возникают одновременно c появлением какой-то шумихи в Интернете, что достаточно непредсказуемо. Как же им удается справляться с этой ситуацией?
Платформа
- Linux - операционная система
- Python - язык программирования
- Django - основной framework
- Apache 2.2 + mod_wsgi - веб-сервер
- PostgreSQL - СУБД
- memcached - кэширование
- HAProxy - балансировка нагрузки
- Slony - репликация данных
- heartbeat - обеспечение доступности
Статистика
- До 17 тысяч запросов в секунду
- 500 000 сайтов
- 15 миллионов зарегистрированных пользователей
- 75 миллионов комментариев
- 250 миллионов посетителей (на август 2010г.)
Основные трудности
- Непредсказуемость нагрузки (основными причинами шумихи в Интернете являются катастрофы и выходки знаменитостей)
- Обсуждения никогда не теряют актуальность (нельзя держать в кэше все дискуссии с 2008 года)
- Нельзя угадать на каком сайте из тысяч возникнет пик трафика
- Персональные настройки, динамическое разбиение на страницы и сортировки снижают эффективность кэширования
- Высокая доступность (из-за разнообразия сайтов и их аудитории сложно запланировать технические работы)
Архитектура
- Оборудование, в сумме около 100 серверов:
- 30% веб-серверов (Apache +
mod_wsgi
) - 10% серверов баз данных (PostgreSQL)
- 25% кэш-серверов (memcached)
- 20% балансировка нагрузки и обеспечение доступности (HAProxy + heartbeat)
- 15% прочие сервера (Python скрипты)
- 30% веб-серверов (Apache +
- Балансировка нагрузки:
- HAProxy:
- Высокая производительность
- Интеллектуальная проверка доступности
- Неплохая статистика
- 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, приглашаю постоянных читателей и всех остальных потестировать :)