Read-From, Write-Through: улучшаем кеш страниц

19 сентября 2008

Предыстория

Есть кеш страниц, организованный по принципу 404-й. То есть – если страница лежит в кеше, она отдается сервером без участия PHP; если ее там нет – по обработчику 404-й ошибки вызывается MainController сайта, который генерирует страницу, кладет ее в кеш и отдает клиенту.

Кроме того, страницы экспайрятся по каким-то событиям. Неважно, каким. Важно, что экспайрятся, и время от времени их нужно повторно генерировать. Это решается удалением из кеша заэкспайренной страницы, после чего при запросе страницы снова вызывается PHP.

Проблема

Если в то время, пока страница генерируется скриптом, запросить ее еще раз, будет запущен еще один экземпляр скрипта и он тоже займется страницей. А при следующем запросе – еще один. И еще. В общем, race condition и в целом неправильная ситуация.

Задача

Устранить race condition с минимальным использованием PHP (предположим, что запуск и инициализация фреймворка на PHP – дорогостоящая операция).

Решение

Суть в том, что сервер каким-то образом должен понять, что страница уже генерируется, и новый скрипт не запускать. Но каким? Углубляться в настройки nginx, который стоит на продакшн, желания нет.

Первая попытка — при запуске PHP ставить флажок «занимаюсь этой страницей», а при следующем запуске – его проверять, и если флажок стоит, то просто подождать. Плохо. Это избавляет нас от лишнего прогона скрипта, но поток PHP все равно занят.

Вторая попытка — при запуске PHP немедленно записывать в кеш пустую страницу. Плохо. Кто-то ее увидит и расстроится. Так что пустая страница проблемы не решает.

Третья попытка (и последняя)

Экспайренные страницы не удаляются из кеша, а переименовываются – например, к ним приписывается суффикс «.old». Таким образом, при обращении к странице будет вызван PHP. А PHP, увидев, что есть старый (хоть какой-то) экземпляр этой страницы, немедленно переименует ее обратно – чтобы работал кеш – а уже после этого сгенерирует новую страницу и положит ее на место устаревшей.

Бинго! PHP запускается не чаще, чем в исходной постановке задачи, а кеш стал надежнее и эффективнее.



Пять комментариев. Напиши еще один
  1. C4ce9cd01cca42e11e9235e8a9d96a85 # 20 сентября 2008 config (owox.ua) написал:

    ты это еще не реализовал, а в блоге написал :))

  2. C4ce9cd01cca42e11e9235e8a9d96a85 # 20 сентября 2008 config (owox.ua) написал:

    еще надо учесть момент когда php скрипт запустился потому кеша нет, но другой php скрипт уже занялся генерацией нового кеша(тот который самым первым не нашел файла в кеше). можно по идее из посмотреть нет ли файла (переименнованного из .old) и отдать его пользователю. иначе php опять полезет в базу или еще куда-то там, будет опять писать кеш и все такое, что совершенно ни к чему

  3. 777894ea5153122bfa6b83f5bbf23622 # 20 сентября 2008 Леонид Шевцов (автор) написал:

    Ага, точно, не учел. Интересная проблема.

  4. 9facc2cda46c25d56ce5312f6a95680e # 22 сентября 2008 www.katkovonline.com (katkovonline.com) написал:

    эта проблема называется dog pile effect решений масса.

  5. D5c8c29dcecdb377a2affaf7da023daf # 02 ноября 2008 standov (web.zavtra.com.ua) написал:

    Да, я такую штуку реализовал в одном из проектов где нужно было «на вот этом железе за месяц» сделать «2K запросов в сек»….в общем, решилось все очень просто, в MVC вреймворке на єтапе генерации URL, проверялся факт наличия сстраницы на диске и генерилась линка на него, иначе на фронт-контролер

(нужна разметка?)

  • **жирный**
  • > цитата

отменить