18.04.08

Dog-pile эффект. Как отгонять стаи собак.

Dog-pile эффект — ситуация когда кэш протухает, а большое количество запросов генерирует высокую нагрузку на источник данных, из которых строиться кэш.

Представьте, что вы кэшируете результат какого то тяжёлого запроса, например, список популярных статей. В какой-то момент времени кэш протухает, и его кто-то должен построить заново. В общем то пока все хорошо. Кроме случаев когда построение кэша тяжёлая операция, а запросов на него много. Например, запрос для генерации кэша занимает 1 секунду, а пользователи ломятся по 10 штуков в секунду. Соответственно, 9 пользователей (кроме первого) будут только зря нагружать базу. А при большом количестве запросов могут и полностью ее положить.

И пусть весь мир подождёт

Первое, что нужно решить — может ли пользователь ждать генерации кэша. Пример с популярными статьями это цветочки, ибо есть еще сложно рассчитываемые рейтинги, которые могут считаться оооочень долго.

Предположим, что у нас простой случай и пользователь не сломается, если подождёт секунду-другую.

Честные блокировки

Решение в лоб. Первый пришедший лочит кэш на запись и отправляется генерировать кэш. Остальные, увидев лок, понимают, что не успели, чешут репы и думают, что делать.

Как именно делать лок зависит только от вашей фантазии и имеющихся средств. Это может быть, что угодно:

Лок файловой системы
много косяков, но иногда работает :). Подробности в описании функции flock().
Мьютекс в хранилище
Если мы используем memcache и генерируемый кэш имеет ключ «popular_articles», тогда наличие данных с ключем «popular_articles_lock», говорит о том, что наш кэш уже кто-то генерирует. Тоже самое спарведливо и для других хранилищ.
IPC семафоры
Настоящие пацанские семафоры ;) Ни разу не использовал — руки не доходят.

Плюсы локов в том, что клиент сам решает, что делать в ситуации, когда ему нужны данные, а их кто-то генерирует. Например, если есть старые данные, то мы можем их отдать, а если данных нет, то либо подождать, либо честно вывести пользователю, что данных нет. На самом деле висящие в течении секунды пользователи совсем не есть гуд, но иногда можно допустить и такое.

Так же не стоит забывать, что при генерации может возникнуть ошибка, и в этом случае лок может остаться висеть.

Организация «окна» для генерации

Способ был подсмотрен в исходниках какого-то фреймворка :)

Суть его в том, что данные после истечения ttl не удаляются. Т.е. приходит первый запрос, видит что ttl истек, и начинает генерировать новое значение. Чтобы остальные запросы подумали, что все нормально, он продлевает ttl существующего кэша на какую-то заранее определённую величину. Если писатель умирает, то кэш быстро снова протухнет и кто-нибудь подхватит знамя генерации с трупа павшего товарища. Если же все нормально, то будет записан новое значение кэша и установлен новый ttl.

Основным минусом такого подхода является то, что ttl нельзя хранить средствами самого кэш-storage, т.к. во всех самых известных хранилищах невозможно получить значение ttl и сами данные с истекшим ttl.

Я хочу вас всех, я хочу вас сразу!

Случай у нас тяжёлый, и пользователю будет скучно коротать 20 секунд рисуя матом надписи на пыльном столе. Я знаю про кого будут эти надписи.

Главное откровение: чтобы избавиться от последствий многопоточности надо свести ее к одному потоку. Просто и со вкусом. Убираем всю генерацию кэша в оффлайн. ttl-ем в данном случае будет время перезапуска генерирующих кэш скриптов. Помимо быстрого ответа пользователю тут есть еще один плюс — наборы кэшей часто генерировать быстрее, чем каждый из них по отдельности, ибо можно хранить какие-то промежуточные данные.

08.04.08

Организация "кусочкового" кеширование HTML

Навеяно парой статей на Хабре, и тем, что вчера сделал cache тег для Macro.

Зачем?

Хочется убить двух зайцев: избавиться от запросов за редко изменяемыми данными, и их отрисовки. Вообще это смахивает на попытку минимальным количеством движений сделать глобальное счастье. Можно потом перед другими фрэймворками меряться, ага.

На самом деле задача избавиться от отрисовки куска страницы вообще какая-то странная, если мы используем быстрый шаблонизатор. Application-сервера, при нормальной арихитектуре, масштабируются легко и непринужденно, а APC, сам закэширует скомпилированный шаблон, если он компилируется в РНР-код, как это сделано в Smarty, Macro и многих других.

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

Откуда данные, и как заставить протухнуть кэш.

Данные в шаблоне могут получаться двумя способами. Первый и традиционный это push-подход, когда контроллер заполняет какой-то data-transfer-object, или в сам шаблон. Такой подход используется чаще всего, ибо это "тру MVC"! ;)

Второй подход - pull. При pull подходе в шаблоне расставляются инструкции по получению данных, в обход контроллера, напрямую от моделей, сервисов и прочих провайдеров данных. Этот способ позволяет избавиться от повторяющихся set'ов в контроллере, но не позволяет ограничить, из контроллера, их получение, и усложняет поддержку шаблонов, ибо иногда трудно понять откуда получены данные.

Теперь об определении развалидации кэша. Тут тоже два основных подхода. Первый - ttl(time-to-live - время жизни). Мы определяем, что список новостей, например, валиден в течении 15 минут. Основной плюс - мы уверены, что за данными скрипт будет обращаться не чаще чем раз в 15 минут, и зная частоту обращений можем точно расчитать эффективность такого кеша. Второй плюс - простота реализации. Главный же минус в том, что наш список на самом деле может измениться через секунду после построения кэша, а пользователь увидит изменения только через 15 минут.

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

А теперь тоже самое, но в свете кэширования частей страницы.

Push me. And then just touch me...

Кэшировать части страницы, при push-данных хуже, чем кэшировать эти самые данные. Почему? Потому что мы "загадим" кодом работы с кэшем и шаблон, и контроллер, а из плюсов только экономия на отрисовке.

Pull-данные и развалидация по действию

Что мы получаем? Логика работы с кешем в шаблоне и тех контроллерах, которые отвечают за действия. Опять не тру, и лучше опять кэшировать данные, через какой-нибудь LastNewsService.

Pull-данные и ttl

Вот то ради чего собственно и делаются теги cache, блоки django, компоненты и т.д. Вся логика только в шаблоне (или настройках компонента). Красотища!

Вывод:

Кэширование части HTML имеет смысл только для pull данных с устареванием по времени. Во всех остальных случаях его плюсы, по сравнению с кэшированием данных, сводятся к экономии работы шаблонизатора.

20.03.08

Тесты производительности различных cache storage

Version 0.4

Ну вот опять!

Передо мной в данный момент стоит задача сравнить популярные бытрые хранилища данных, основанные на использовании оперативной памяти. Найденные сравнения(например, вот это) не совсем подходят, так как необходима информация не только о скорости get/set операций, но и о add/delete (их предполагается использовать для создания мьютексов).

На самом деле целей тестирования две:

  1. Найти самый быстрый и удобный cache storage, с которым можно взаимодействовать из PHP-шного расширения напрямую, а не через сокеты, как у memcached.

  2. Оценить разницу в скорости, между хранилищем из пункта 1 и memcached.

Итак в тестировании участвуют:

Идут лесом:

Тестирование проводилось с помощью немного измененного пакета limb/cache. Этот пакет позволит (я надеюсь ;) в случае нехватки памяти на application-сервере, легко и непринуждённо (всего одна бессоная ночь) перейти на memcached.

Кто на свете всех быстрее, и румяней, и милее.

В этой номинации сравнивались APC и XCache, как самые известные, и исходя из других моих сексуальных предпочтений: я больше люблю заниматься им с девушками, чем с библиотеками, которые вдруг перестали поддерживаться или начали конфликтовать с другим софтом. В общем APC и XCache я больше доверяю.

Загадочные цифры - это количество совершаемых операций в секунду.

add get set delete
APC / integer 111231 150792 96590 154379
XCache / integer 40342 99218 56146 114956
diff 176% 52% 72% 34%
APC / array 51547 90296 42422 92647
XCache / array 23091 68078 43226 84848
diff 123% 33% -2% 9%
APC / object 20373 20743 19542 122401
XCache / object* 10598 14100 15355 75741
diff 92% 47% 27% 62%
* - В ходе тестирования выяснилось, что XCache не умеет работать с объектами, и их ему приходится отдавать в уже сериализованном виде. Это, конечно решается с помощью собственного драйвера, но "осадок остался" (с).

APC рулит! Поэтому и войдет в PHP6 ;)

Выходи memcached - биться будем!

add get set delete
APC / integer 111231 150792 96590 154379
Memcached / integer 6679 8436 6667 9304
diff 1565% 1687% 1349% 1559%
APC / array 51547 90296 42422 92647
Memcached / array 6783 8319 6854 9142
diff 660% 985% 519% 913%
APC / object 20373 20743 19542 122401
Memcached / object 5088 5648 5143 9081
diff 300% 267% 280% 1248%

Выводы делаейте сами, а я спать пойду.

Исходники тестов можно скачать и поправить ;)