<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Блог Андрея Смирнова (кэш)</title><link>http://www.smira.ru/</link><description></description><language>ru</language><lastBuildDate>Sun, 11 Jan 2015 19:24:28 GMT</lastBuildDate><generator>http://getnikola.com/</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Проблема одновременного перестроения кэшей</title><link>http://www.smira.ru/posts/20081028web-caching-memcached-4.html</link><dc:creator>Andrey</dc:creator><description>&lt;p&gt;&lt;/p&gt;&lt;p&gt;Серия постов про "Web, кэширование и memcached" продолжается. Начало здесь: &lt;a href="http://www.smira.ru/2008/10/16/web-caching-memcached-1/"&gt;1&lt;/a&gt;, &lt;a href="http://www.smira.ru/2008/10/21/web-caching-memcached-2/"&gt;2&lt;/a&gt; и &lt;a href="http://www.smira.ru/2008/10/24/web-caching-memcached-3/"&gt;3&lt;/a&gt;.
В этих постах мы поговорили о memcached, его архитектуре, возможном применении, выборе ключа кэширования, кластеризации, атомарных операциях и реализации счетчиков в &lt;a href="http://danga.com/memcached/"&gt;memcached&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Сегодня мы рассмотрим проблему одновременного перестроения кэша, которая возникает при большом количестве одновременных обращений к кэшу, который был только что сброшен или потерян, что может привести к перегрузке БД.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.smira.ru/wp-content/uploads/2008/10/overload-300x231.jpg" alt="Перегрузка backend" title="Перегрузка backend" width="300" height="231" class="alignnone size-medium wp-image-143"&gt;&lt;/p&gt;
&lt;p&gt;Следующий пост будет посвящен тэгированию кэшей.&lt;/p&gt;
&lt;!--more--&gt;

&lt;h3&gt;Одновременное перестроение кэшей&lt;/h3&gt;
&lt;p&gt;Данная проблема характерна в первую очередь для высоконагруженных проектов. Рассмотрим следующую ситуацию: у нас есть выборка из БД, которая используется на многих страницах или особо популярных страницах (например, на главной странице). Эта выборка закэширована с некоторым «сроком годности», т.е. кэш будет сброшен по прошествии некоторого интервала времени. При этом сама выборка является относительно сложной, её вычисление заметно нагружает backend (БД). В какой-то момент времени ключ в memcached будет удален, т.к. истечет срок его жизни (срок жизни был установлен у кэша), в этот момент несколько frontend’ов (несколько, т.к. выборка часто используется) обратятся в memcached по этому ключу, обнаружат его отсутствие и попытаются построить кэш заново, осуществив выборку из БД. То есть в БД одновременно попадет несколько одинаковых запросов, каждый из которых заметно нагружает базу данных, при превышении некоторого порога запрос не будет выполнен за разумное время, еще больше frontend’ов обратятся к кэшу, обнаружат его отсутствие и отправят еще больше запросов в базу данных, с которыми база данных тем более не справится. В результате сервер БД получил критическую нагрузку, и «прилёг». Что делать, как избежать такой ситуации?&lt;/p&gt;
&lt;p&gt;Проблема с перестроением кэшей становится проблемой только тогда, когда имеют место два фактора: много обращений к кэшу в единицу времени и сложный запрос. Причем один фактор может компенсировать другой: на относительно непопулярной, но очень сложной и долгой выборке (которых вообще-то не должно быть) мы можем получить аналогичную ситуацию. Итак, что же делать?&lt;/p&gt;
&lt;p&gt;Можно предложить следующую схему: мы больше не ограничиваем время жизни ключа с кэшом в memcached – он будет там находиться до тех пор, пока не будет вытеснен другими ключами. Но вместе с данными кэша мы записываем и реальное время его жизни, например:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;годен&lt;/span&gt; &lt;span class="err"&gt;до&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2008&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mo"&gt;03&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;53&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="err"&gt;данные&lt;/span&gt; &lt;span class="err"&gt;кэша&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Теперь при получении ключа из memcached мы можем проверить, истёк ли срок жизни кэша с помощью поля «годен до». Если срок жизни истёк, кэш надо перестроить, но мы будем делать это с блокировкой (о блокировках речь пойдет в следующем разделе), если не удастся заблокироваться, мы можем либо подождать еще (раз блокировка уже есть, значит кэш кто-то перестраивает), либо вернуть старое значение кэша. Если заблокироваться удастся, мы строим кэш самостоятельно, при этом другие frontend’ы не будут перестраивать этот же кэш, так как увидят нашу блокировку. Основное преимущество хранения в memcached без указания срока годности – именно возможность получить старое значение кэша в случае, если кэш уже перестраивается кем-то. Что именно делать – ждать, пока кэш построит кто-то другой, и получать новое значение из memcached, или возвращать старое значение, – зависит от задачи, насколько приемлемо старое значение и сколько можно провести времени в состоянии ожидания. Чаще всего можно позволить себе 2-3 секундное ожидание с проверкой удаления блокировки и, если кэш так и не построился (что маловероятно, получается что выборка происходит больше чем за 2-3 секунды), вернуть старое значение, освобождая frontend для других задач.&lt;/p&gt;
&lt;h4&gt;Пример такого алгоритма&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Получаем доступ к кэшу cache, его срок жизни истёк.&lt;/li&gt;
&lt;li&gt;Пытаемся заблокироваться по ключу user cache_lock.&lt;ul&gt;
&lt;li&gt;Не удалось получить блокировку:&lt;ul&gt;
&lt;li&gt;ждём снятия блокировки;&lt;/li&gt;
&lt;li&gt;не дождались: возвращаем старые данные кэша;&lt;/li&gt;
&lt;li&gt;дождались: выбираем значения ключа заново, возвращаем новые данные (построенный кэш другим процессом).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Удалось получить блокировку:&lt;ul&gt;
&lt;li&gt;строим кэш самостоятельно.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Такая схема позволяет исключить или свести к минимуму ситуации «заваливания» backend’а одинаковыми «тяжелыми» запросами, когда реально запрос достаточно выполнить лишь один раз. Остается последний вопрос, как обеспечить корректную блокировку? Очевидно, что так как проблема одновременного перестроения возникает на разных frontend’ах, то блокировка должна быть в общедоступном для них всех месте, то есть в memcached.&lt;/p&gt;
&lt;h4&gt;Блокировки в memcached&lt;/h4&gt;
&lt;p&gt;Рассмотрим два варианта реализации блокировки (мьютекса, двоичного семафора) с помощью memcached. Первый некорректный, он не может обеспечить корректного исключения параллельных процессов, но очевидный. Второй совершенно корректный, но не настолько очевиден.&lt;/p&gt;
&lt;p&gt;Пусть мы хотим заблокироваться по ключу &lt;code&gt;‘lock’&lt;/code&gt;: пытаемся получить значения ключа с помощью операции &lt;code&gt;get&lt;/code&gt;. Если ключ не найден, значит блокировки нет, и мы с помощью операции &lt;code&gt;set&lt;/code&gt; устанавливаем значение этого ключа, например, в единицу, а время жизни устанавливаем в небольшой интервал времени, который превышает максимальное время жизни блокировки, например, в 10 секунд. Теперь, если frontend завершится аварийно и не снимет блокировку, она автоматически уничтожится через 10 секунд. Итак, с помощью &lt;code&gt;set&lt;/code&gt; мы блокировку установили, выполнили все необходимые действия, после этого снимаем блокировку просто удаляя соответствующий ключ командой &lt;code&gt;del&lt;/code&gt;. Если на первой операции &lt;code&gt;get&lt;/code&gt; мы получили значение ключа, это означает, что блокировка уже установлена другим процессом, наша операция блокировки неуспешна.&lt;/p&gt;
&lt;p&gt;Описанный способ обладает недостатком: наличием состояния гонки (race condition). Два процесса могут одновременно сделать &lt;code&gt;get&lt;/code&gt;, оба могут получить ответ, что «ключа нет», оба сделают &lt;code&gt;set&lt;/code&gt;, и оба будут считать, что установили блокировку успешно. В ситуациях, как одновременное перестроение кэшей, этого может быть допустимо, т.к. здесь цель не исключить все другие процессы, а резко уменьшить количество одновременных запросов к БД, что может обеспечить и этот простой, некорректный вариант.&lt;/p&gt;
&lt;p&gt;Второй вариант корректен, и даже проще первого. Для захвата блокировки достаточно выполнить одну команду: &lt;code&gt;add&lt;/code&gt;, указав имя ключа и время жизни (такое же маленькое, как и в первом варианте). Команда &lt;code&gt;add&lt;/code&gt; будет успешной только в том случае, если ключа в memcached еще нет, то есть наш процесс и есть тот единственный процесс, которому удалось захватить блокировку. Тогда нам надо выполнить необходимые действия и освободить блокировку командой &lt;code&gt;del&lt;/code&gt;. Если &lt;code&gt;add&lt;/code&gt; вернет ошибку «такой ключ уже существует», значит, блокировка была захвачена раньше каким-то другим процессом.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;</description><category>memcached</category><category>web</category><category>БД</category><category>кэш</category><category>кэширование</category><category>нагрузка</category><category>разработка</category><guid>http://www.smira.ru/posts/20081028web-caching-memcached-4.html</guid><pubDate>Tue, 28 Oct 2008 06:25:11 GMT</pubDate></item><item><title>Кэширование и memcached</title><link>http://www.smira.ru/posts/20081016web-caching-memcached-1.html</link><dc:creator>Andrey</dc:creator><description>&lt;p&gt;&lt;/p&gt;&lt;p&gt;Этим постом хочу открыть небольшую серию постов по материалам &lt;a href="http://www.smira.ru/2008/10/08/highload-plus-plus-2008/"&gt;доклада&lt;/a&gt; на &lt;a href="http://highload.ru/"&gt;HighLoad++-2008&lt;/a&gt;. Впоследствии весь текст будет опубликован в виде одной большой PDF-ки. &lt;/p&gt;
&lt;h3&gt;Введение&lt;/h3&gt;
&lt;p&gt;Для начала, о названии серии постов: посты будут и о кэшировании в Web’е (в высоконагруженных Web-проектах), и о применении memcached для кэширования, и о других применениях memcached в Web-проектах. То есть все три составляющие названия в различных комбинациях будут освещены в этой серии постов.&lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;Кэширование сегодня является неотъемлемой частью любого Web-проекта, не обязательно высоконагруженного. Для каждого ресурса критичной для пользователя является такая характеристика, как время отклика сервера. Увеличение времени отклика сервера приводит к оттоку посетителей. Следовательно, необходимо минимизировать время отклика: для этого необходимо уменьшать время, требуемое на формирование ответа пользователю, а ответ пользователю требует получить данные из каких-то внешних ресурсов (backend). Этими ресурсами могут быть как базы данных, так и любые другие относительно медленные источники данных (например, удаленный файловый сервер, на котором мы уточняем количество свободного места). Для генерации одной страницы достаточно сложного ресурса нам может потребоваться совершить десятки подобных обращений. Многие из них будут быстрыми: 20 мс и меньше, однако всегда существует некоторое небольшое количество запросов, время вычисления которых может исчисляться секундами или минутами (даже в самой оптимизированной системе один могут быть, хотя их количество должно быть минимально). Если сложить всё то время, которое мы затратим на ожидание результатов запросов (если же мы будем выполнять запросы параллельно, то возьмем время вычисления самого долгого запроса), мы получим неудовлетворительное время отклика.&lt;/p&gt;
&lt;p&gt;Решением этой задачи является кэширование: мы помещаем результат вычислений в некоторое хранилище (например, memcached), которое обладает отличными характеристиками по времени доступа к информации. Теперь вместо обращений к медленным, сложным и тяжелым backend’ам нам достаточно выполнить запрос к быстрому кэшу.&lt;/p&gt;
&lt;h3&gt;Memcached и кэширование&lt;/h3&gt;
&lt;h4&gt;Принцип локальности&lt;/h4&gt;
&lt;p&gt;Кэш или подход кэширования мы встречаем повсюду в электронных устройствах, архитектуре программного обеспечения: кэш ЦП (первого и второго уровня), буферы жесткого диска, кэш операционной системы, буфер в автомагнитоле. Чем же определяется такой успех кэширования? Ответ лежит в принципе локальности: программе, устройству свойственно в определенный промежуток времени работать с некоторым подмножеством данных из общего набора. В случае оперативной памяти это означает, что если программа работает с данными, находящимися по адресу 100, то с большей степенью вероятности следующее обращение будет по адресу 101, 102 и т.п., а не по адресу 10000, например. То же самое с жестким диском: его буфер наполняется данными из областей, соседних по отношению к последним прочитанным секторам, если бы наши программы работали в один момент времени не с некоторым относительно небольшим набором файлов, а со всем содержимым жесткого диска, буферы были бы бессмысленны. Буфер автомагнитолы совершает упреждающее чтение с диска следующих минут музыки, потому что мы, скорее всего, будем слушать музыкальный файл последовательно, чем перескакивать по набору музыки и т.п.&lt;/p&gt;
&lt;p&gt;В случае web-проектов успех кэширования определяется тем, что на сайте есть всегда наиболее популярные страницы, некоторые данные используются на всех или почти на всех страницах, то есть существуют некоторые выборки, которые оказываются затребованы гораздо чаще других. Мы заменяем несколько обращений к backend’у на одно обращения для построения кэша, а затем все последующие обращения будет делать через быстро работающий кэш.&lt;/p&gt;
&lt;p&gt;Кэш всегда лучше, чем исходный источник данных: кэш ЦП на порядки быстрее оперативной памяти, однако мы не можем сделать оперативную память такой же быстрой, как кэш – это экономически неэффективно и технически сложно. Буфер жесткого диска удовлетворяет запросы за данными на порядки быстрее самого жесткого диска, однако буфер не обладает свойством запоминать данные при отключении питания – в этом смысле он хуже самого устройства. Аналогичная ситуация и с кэшированием в Web’е: кэш быстрее и эффективнее, чем backend, однако он обычно в случае перезапуска или падения сервера не может сохранить данные, а также не обладает логикой по вычислению каких-либо результатов: он умеет возвращать лишь то, что мы ранее в него положили.&lt;/p&gt;
&lt;h4&gt;Memcached&lt;/h4&gt;
&lt;p&gt;&lt;a href="http://danga.com/memcached/"&gt;Memcached&lt;/a&gt; представляет собой огромную хэш-таблицу в оперативной памяти, доступную по сетевому протоколу. Он обеспечивает сервис по хранению значений, ассоциированных с ключами. Доступ к хэшу мы получаем через простой сетевой протокол, клиентом может выступать программа, написанная на произвольном языке программирования (существуют клиенты для C/C++, PHP, Perl, Java и т.п.)&lt;/p&gt;
&lt;p&gt;Самые простые операции – получить значение указанного ключа (get), установить значение ключа (set) и удалить ключ (del). Для реализации цепочки атомарных операций (при условии конкурентного доступа к memcached со стороны параллельных процессов) используются дополнительные операции: инкремент/декремент значения ключа (incr/decr), дописать данные к значению ключа в начало или в конец (append/prepend), атомарная связка получения/установки значения (gets/cas) и другие.&lt;/p&gt;
&lt;p&gt;Memcached был реализован Брэдом Фитцпатриком (Brad Fitzpatrick) в рамках работы над проектом ЖЖ (LiveJournal). Он использовался для разгрузки базы данных от запросов при отдаче контента страниц. Сегодня memcached нашел своё применение в ядре многих крупных проектов, например, Wikipedia, YouTube, Facebook и другие.&lt;/p&gt;
&lt;h4&gt;Общая схема кэширования&lt;/h4&gt;
&lt;p&gt;&lt;img src="http://www.smira.ru/wp-content/uploads/2008/10/image002.gif" alt="Общая схема кэширования" title="Рисунок 1" width="548" height="248" class="size-full wp-image-103"&gt;&lt;/p&gt;
&lt;p&gt;В общем случае схема кэширования выглядит следующим образом: frontend’у (той части проекта, которая формирует ответ пользователю) требуется получить данные какой-то выборки. Frontend обращается к быстрому как гепард серверу memcached за кэшом выборки (get-запрос). Если соответствующий ключ будет обнаружен, работа на этом заканчивается. В противном случае следует обращение к тяжелому, неповоротливому, но мощному (как слон) backend’у, в роли которого чаще всего выступает база данных. Полученный результат сразу же записывается в memcached в качестве кэша (set-запрос). При этом обычно для ключа задается максимальное время жизни (срок годности), который соответствует моменту сброса кэша.&lt;/p&gt;
&lt;p&gt;Такая стандартная схема кэширования реализуется всегда. Вместо memcached в некоторых проектах могут использоваться локальные файлы, иные способы хранения (другая БД, кэш PHP-акселератора и т.п.) Однако, как будет показано далее, в высоконагруженном проекте данная схема может работать не самым эффективным образом. Тем не менее, в нашем дальнейшем рассказе мы будем опираться именно на эту схему.&lt;/p&gt;
&lt;h4&gt;Архитектура memcached&lt;/h4&gt;
&lt;p&gt;Каким же образом устроен memcached? Как ему удаётся работать настолько быстро, что даже десятки запросов к memcached, необходимых для обработки одной страницы сайта, не приводят к существенной задержке. При этом memcached крайне нетребователен к вычислительным ресурсам: на нагруженной инсталляции процессорное время, использованное им, редко превышает 10%.&lt;/p&gt;
&lt;p&gt;Во-первых,  memcached спроектирован так, чтобы все его операции имели алгоритмическую сложность O(1), т.е. время выполнения любой операции не зависит от количества ключей, которые хранит memcached. Это означает, что некоторые операции (или возможности) будут отсутствовать в нём, если их реализация требует всего лишь линейного (O(n)) времени. Так, в memcached отсутствуют возможность объединения ключей «в папки», т.е. какой-либо группировки ключей, также мы не найдем групповых операций над ключами или их значениями.&lt;/p&gt;
&lt;p&gt;Основными оптимизированными операциями является выделение/освобождение блоков памяти под хранение ключей, определение политики самых неиспользуемых ключей (LRU) для очистки кэша при нехватке памяти. Поиск ключей происходит через хэширование, поэтому имеет сложность O(1).&lt;/p&gt;
&lt;p&gt;Используется асинхронный ввод-вывод, не используются нити, что обеспечивает дополнительный прирост производительности и меньшие требования к ресурсам. На самом деле memcached может использовать нити, но это необходимо лишь для использования всех доступных на сервере ядер или процессоров в случае слишком большой нагрузки – на каждое соединение нить не создается в любом случае.&lt;/p&gt;
&lt;p&gt;По сути, можно сказать, что время отклика сервера memcached определяется только сетевыми издержками и практически равно времени передачи пакета от frontend’а до сервера memcached (RTT). Такие характеристики позволяют использовать memcached в высоконагруженных web-проектов для решения различных задач, в том числе и для кэширования данных.&lt;/p&gt;
&lt;p&gt;Потеря ключей&lt;/p&gt;
&lt;p&gt;Memcached не является надежным хранилищем – возможна ситуация, когда ключ будет удален из кэша раньше окончания его срока жизни. Архитектура проекта должна быть готова к такой ситуации и должна гибко реагировать на потерю ключей. Можно выделить три основных причины потери ключей:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ключ был удален раньше окончания его срока годности в силу нехватки памяти под хранение значений других ключей. Memcached использует политику LRU, поэтому такая потеря означает, что данный ключ редко использовался и память кэша освобождается для хранения более популярных ключей.&lt;/li&gt;
&lt;li&gt;Ключ был удален, так как истекло его время жизни. Такая ситуация строго говоря не является потерей, так как мы сами ограничили время жизни ключа, но для клиентского по отношению к memcached кода такая потеря неотличима от других случаев – при обращении к memcached мы получаем ответ «такого ключа нет».&lt;/li&gt;
&lt;li&gt;Самой неприятной ситуацией является крах процесса memcached или сервера, на котором он расположен. В этой ситуации мы теряем все ключи, которые хранились в кэше. Несколько сгладить последствия позволяет кластерная организация: множество серверов memcached, по которым «размазаны» ключи проекта: так последствия краха одного кэша будут менее заметны.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Все описанные ситуации необходимо иметь в виду при разработке программного обеспечения, работающего с memcached. Можно разделить данные, которые мы храним в memcached, по степени критичности их потери.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;«Можно потерять»&lt;/strong&gt;. К этой категории относятся кэши выборок из базы данных. Потеря таких ключей не так страшна, потому что мы можем легко восстановить их значения, обратившись заново к backend’у. Однако частые потери кэшей приводят к излишним обращениям к БД.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;«Не хотелось бы потерять»&lt;/strong&gt;. Здесь можно упомянуть счетчики посетителей сайта, просмотров ресурсов и т.п. Хоть и восстановить эти значения иногда напрямую невозможно, но значения этих ключей имеют ограниченный по времени смысл: через несколько минут их значение уже неактуально, и будет рассчитано новое значение.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;«Совсем не должны терять»&lt;/strong&gt;. Memcached удобен для хранения сессий пользователей – все сессии равнодоступны со всех серверов, входящих в кластер frontend’ов. Так вот содержимое сессий не хотелось бы терять никогда – иначе пользователей на сайте будет «разлогинивать». Как попытаться избежать? Можно дублировать ключи сессий на нескольких серверах memcached из кластера, так вероятность потери снижается.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;</description><category>memcached</category><category>кэш</category><category>кэширование</category><category>разработка</category><guid>http://www.smira.ru/posts/20081016web-caching-memcached-1.html</guid><pubDate>Thu, 16 Oct 2008 06:56:59 GMT</pubDate></item><item><title>Web, кеширование и memcached: часть 2</title><link>http://www.smira.ru/posts/20080623web-cache-memcached-2.html</link><dc:creator>Andrey</dc:creator><description>&lt;p&gt;&lt;/p&gt;&lt;p&gt;Начало серии постов &lt;a href="http://www.smira.ru/2008/06/22/web-cache-memcached-1/"&gt;здесь&lt;/a&gt;. Продолжаем разбираться с этим вопросом.&lt;/p&gt;
&lt;p&gt;Проблема №3. Одного мемкеша мало&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Если сайт большой, данных много, кешей тоже много, в один memcached физически не умещаемся. Пожалуйста: создаем кластер мемкешовых серверов, хешируем ключи для определения номера сервера, на котором ключ должен храниться. Всё вроде бы хорошо до тех пор, пока эти сервера не начинают падать или с ними не начинает теряться сетевая connectivity (что легко происходит, т.к. архитекутра кластерная, место на площадках ограничено, сервера физически разнесены, каналы забиты, ненадежны и т.п.) &lt;/p&gt;
&lt;!--more--&gt;

&lt;p&gt;Самый простой алгоритм хеширования вида:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;memcache_server_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;crc32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;memcache_servers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;начинает "веселиться", когда обнаруживает, что сервер упал. Когда обнаруживается проблема сетевого доступа к серверу, &lt;a href="http://pecl.php.net/package/memcache"&gt;драйвер memcachа&lt;/a&gt; убирает его из списка $memcache_servers. При этом count($memcache_servers) изменяется, какая-то часть ключей "сдвигается" на другие сервера, при этом "пропадает" куча кешей (хорошо, если кешей, которые не так жалко потерять, а если речь идёт о сессиях пользователей?).&lt;/p&gt;
&lt;p&gt;Необходимо отметить, что та же самая проблема будет, когда мы вводим в строй или выводим из пула сервера мемкеша. Интересная математическая задача: если раньше было N серверов мемкеша и схема распределения ключей по серверам взятием остатка от деления на N (как написано выше), а после каких-то событий стало K серверов, то какой процент ключей останется на том же сервере, на котором они были до изменения количества серверов?&lt;/p&gt;
&lt;p&gt;Когда куча ключей теряется, резко начинают перестраиваться кеши, вырастает нагрузка на БД, и т.д., и т.п., то есть падение одного сервера мемкеша заваливает весь "отказоустойчивый" кластер.&lt;/p&gt;
&lt;p&gt;Что делать? Есть &lt;a href="http://www.lastfm.ru/user/RJ/journal/2007/04/10/rz_libketama_-_a_consistent_hashing_algo_for_memcache_clients"&gt;другие алгоритмы хеширования&lt;/a&gt; ключей, устойчивые к удалению/добавлению серверов в пул. Для &lt;a href="http://pecl.php.net/package/memcache"&gt;PHP-шного модуля Memcache&lt;/a&gt; это:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;ini_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;memcache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hash_strategy&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;consistent&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Проблема №4. Одновременное перестроение кеша несколькими мордами&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Если мы выставляем ключу (кешу) некоторое время жизни, рано или поздно, memcache скажет, что такого ключа на сервере нет. А если это "популярный" кеш, например, использующийся на главной странице, то такую ситуацию могут "обнаружить" одновременно несколько морд. И они все попытаются построить этот кеш, то есть они все одновременно отправят запрос в БД, то есть они отправят &lt;em&gt;много&lt;/em&gt; запросов в БД, причём за короткий промежуток времени. Конечно, как только первый запрос завершится, морда запишет новое значение ключа и больше новых запросов за этой выборкой не будет. Но мы уже подвергли БД высокой нагрузке, хотя хотели этого избежать при помощи кеширования.&lt;/p&gt;
&lt;p&gt;Напрашивается решение: пока одна морда строит кеш, все остальные должны её "подождать". Как это реализовать? Например, с помощью блокировок в том же мемкеше. Перед тем, как начать строить кеш для ключа mykey, проверяем, не выставлен ли уже ключ mykey_lock, если он выставлен, засыпаем и ждём некоторое время, что ключ исчезнет, если он исчез - пробуем снова прочитать mykey, если он появился (другая морда его построила), то берем готовое значение. Не появился за какой-то разумный промежуток времени - начинаем строить сами (значит, никто другой не смог построить значение этого кеша). &lt;/p&gt;
&lt;p&gt;Перед тем, как сами начинаем строить кеш (например, если блокировки не было обнаружено), обязательно создаем блокировку: выставляем ключ блокировки mykey_lock, например, на 10 секунд, тем самым не подпуская другие морды к построению такой же выборки. Как выборку построили - записываем её в мемкеш, ключ блокировки удаляем.&lt;/p&gt;
&lt;p&gt;Такая схема позволяет избежать части коллизий, который возникают при одновременном построении кешей.&lt;/p&gt;</description><category>memcached</category><category>php</category><category>smotri.com</category><category>кэш</category><category>разработка</category><guid>http://www.smira.ru/posts/20080623web-cache-memcached-2.html</guid><pubDate>Sun, 22 Jun 2008 20:02:33 GMT</pubDate></item></channel></rss>