Забавно, но когда программист разрабатывает какой-либо продукт, он редко задумывается над вопросом могут ли на одну кнопку в один момент времени нажать одновременно 2000 человек. А зря. Оказывается могут. Как ни странно но большинство движков, написанных такими программистами, очень плохо ведут себя под большими нагрузками. Кто бы подумал, а всего один лишний INSERT, не проставленный index, или кривая рекурсивная функция могут поднять load averages чуть ли не на порядок.
В этой статье я опишу как мы, разработчики проекта, сумели выжать из одного сервера с Pentium 4 HT / 512Mb RAM, максимум, держа одновременно 700+ пользователей на форуме и 120,000 на трекере. Да, проект этот — торрент трекер. Предлагаю сразу оставить в стороне разговоры о копирайтах и правах, мне это не интересно, что действительно интересно — это HighLoad.
[/url]
Для начала опишу проект таким, каким он был:
Обычный торрент трекер на движке TorrentPier (он же в девичестве phpbb 2.x)
- Сервер на FreeBSD 6.0
- Pentium 4 HT / 512Mb RAM
- Web-сервер Apache
- База MySQL
- Вся логика на PHP
То есть практически LAMP
Вкратце сразу распишу последовательно те шаги которые мы предприняли:
- Установка на сервер opcode cache
- Замена apache на nginx
- Кэширование некоторых промежуточных выборок в НЕ RDBMS
- Перевод ключевой части (читай трекера) на C++
- Оптимизация сетевого стека FreeBSD, а также её обновление до последней -STABLE
- Оптимизация MySQL
- Кэширование BB-кодов
- Переписка кода на использование SphinxSearch
- Профайлинг кода и установка средств мониторинга
- Разборка запросов из MySQL slow query log
Теперь о каждом пункте поподробнее
Установка на сервер opcode cache
Он нужен всегда! Установка php-cache дало 300%+ производительности, потратив 15 минут времени.
Кэши бывают разные: eAccelerator, xCache, APC и т.д… Мы остановились на последнем, из-за хорошей скорости и возможности хранить в нём пользовательские данные
Замена apache на nginx
Apache — тяжёлый и медленный, сначала стоял как основной web-сервер, потом перед ним был поставлен nginx, отдающий статику и сжимающий ответы gzip’ом. Далее от apache отказались вообще в пользу связки nginx+php-fpm (если быть точным на тот момент это был spawn_fcgi, но сейчас такой вариант лучше). Связка в те времена была не самая популярная для production, но у нас она работала замечательно!
Кеширование некоторых промежуточных выборок в
НЕ RDBMS
RDBMS — это зло. Оно удобно, но за удобство надо платить. В данном случае скоростью. А нам именно она и нужна. Так, что часть результатов самых популярных и не критичных к актуальности запросов к мускулу мы закешировали в APC. Сразу предчувствуя множество вопросов почему не в memcached… Как бы вам ответить… мне уже надоело даже это слово слышать memcached,memcached,memcached как будто это панацея от всего. Его не предлагают последнее время разве, что только от диареи. В нашем случае выбор пал на APC ибо он не использует TCP соединение и из-за этого работает в
разы быстрее. Темболее пока у нас всё отлично крутится на одном сервере и нам распределённое хранилище не так уж и нужно.
Вы можете выбрать любое другое key/value хранилище, не обязательно хранящее данные в оперативной памяти.
Но весьма вероятно, что в вашем случае memcached/memcachedb/memcacheQ будут лучшим вариантом.
Вообще была идея сделать многоуровневую кэш-прослойку в которой php искал значение в глобальных переменных, потом в APC, далее в memcached, а лишь потом лезет в базу SELECT’ом. Но так как проектом занимаемся в свободное от учёбы/работы/семьи время, то до этого пока не дошло.
Перевод ключевой части (читай трекера) на C++
120000 активных пиров создают не мало коннектов к nginx, что ещё хуже, так каждый из них дёргает php, который дёргает мускул. Вам не кажется что ето уж слишком? Нам тоже так показалось. Один из наших разработчиков собрался с силами и переписал код XBTT под фронтенд TorrentPier’а. Оно того стоило, теперь клиент обращается к трекеру на 2710 порт, который держит в памяти табличку с пирами, там его быстро находит, делает что надо и отдаёт ответ пиру обратно[url=http://www.turdialog.ru]. Раз в минуту скидывает результаты в базу. Всё прекрасно. +100000% производительности.
Вот результаты теста, когда мы поставили время анонса — 1 минута
Код:
input (rl0) output
packets errs bytes packets errs bytes colls drops
20K 0 2.5M 16K 0 1.5M 0 0
PID USERNAME THR PRI NICE SIZE RES STATE C TIME WCPU COMMAND
10 root 1 171 52 0K 8K RUN 1 538.6H 47.12% idle: cpu1
6994 root 1 108 0 98140K 96292K CPU0 0 3:57 33.98% xbt_tracker
11 root 1 171 52 0K 8K RUN 0 595.0H 31.20% idle: cpu0
35 root 1 -68 -187 0K 8K WAIT 0 17.1H 21.14% irq21: rl0
12 root 1 -44 -163 0K 8K WAIT 0 482:57 9.96% swi1: net
[root@****] /usr/ports/devel/google-perftools/> netstat -an | wc -l
24147
Цена вопроса 100 Метров памяти и 30% загрузка однго проца. Итого получается, что с такой же загрузкой можно держать примерно 8 Миллионов пиров на одной машине при получасовом времени анонса
Оптимизация сетевого стека FreeBSD, а также её обновление до последней -STABLE
В последних версиях FreeBSD 6 очень хорошо переработан планировщик 4BSD, а в 7ке так вообще есть такая приятная вещь как ULE, с которой мускул работает в разы шустрее на SMP
Также в любой высокопроизводительной инсталяции FreeBSD нужно крутить sysctl, я рекомендую это делать по Сысоеву
Оптимизация MySQL
База данных это то, во что рано или поздно упирается любой проект, мы не исключение.
В то время myisam у нас использовался по двум причинам
- он используется по-умолчанию
- в нём есть FULLTEXT индекс для поиска по форуму
Так мы потратили немало времени крутя буферы. Особенно в этом помог tuning-primer.sh
В дальнейшем планируется перевод базы на Xtradb. В любом случае нам пока хорошо — база влезает в память =)
Кэширование BB-кодов
Оказывается phpbb «на лету» преобразовывает bbcod’ы в html. Не хорошо. Закешировали сгенерированный html код в отдельном поле базы данных для каждого поста/подписи. В итоге база потяжелела почти в 2 раза, зато сайт начал летать.
Переписка кода на использование SphinxSearch
Как-то читал презентацию фликера, по поводу того как они у себя делали поиск. Так как база у них на innodb они сделали отдельную ферму Master-MultipleSlaves на myisam, чтобы обрабатывать на ней поиски. Что ж, мы не так богаты, сервер у нас только один. Ещё один наш разработчик, взяв волю в кулак, перевёл весь поиск по сайту на сверхбыстрый SphinxSearch. Результат превзошёл все ожидания. Сервер опять залетал.
Как косвенный эффект это позволило нам ввести просто
супер-мега-удобный rss со встроенным поиском, который почти не грузит сервер.
Профайлинг кода и установка средств мониторинга
Странно, но многие этим ещё не пользуются. А зря. Если не знаешь где находится bottleneck устранить его невозможно. Для этого мы напихали в php код hook’ов профайлера, а на сервер установили munin.
Разборка запросов из MySQL slow query log
Тут классика! 20% запросов к базе занимают 80% времени. Приглядитесь может и у вас так. А после разбора логов, подписок FORCE INDEX к запросам и закоментирования нескольких строчек в php загрузка в час пик упала в два раза, а главная страница начала грузится в 10(!!) раз быстрее.
В общем очень рекомендую проводить такую операцию раз-два в год или после введения множества мелких нововведений. Очень помог инструмент mysqlsla.
Вместо послесловия
Вот как несколькими шагами мы превратили обычный LAMP в комплексную систему. Сейчас мы живём на обычном Core2Duo 2Ггц с 3Гб оперативки, сейчас ноутбуки в магазинах продаются «покруче», но нам хватает, а загрузка в час пик не поднимается выше 1.5 при 200000 тысячах пиров и ~500 пользователях форума. Интересно каких объёмов потребовался бы парк если бы мы тупо росли горизонтально используя LAMP и репликацию?
Посмотрим как всё изменится когда нам перестанет хватать одного сервера.