<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Блог Андрея Смирнова &#187; БД</title>
	<atom:link href="http://www.smira.ru/tag/%d0%b1%d0%b4/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.smira.ru</link>
	<description></description>
	<lastBuildDate>Wed, 24 Aug 2011 05:09:27 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Проблема одновременного перестроения кэшей</title>
		<link>http://www.smira.ru/2008/10/28/web-caching-memcached-4/</link>
		<comments>http://www.smira.ru/2008/10/28/web-caching-memcached-4/#comments</comments>
		<pubDate>Tue, 28 Oct 2008 06:25:11 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Разработка]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[БД]]></category>
		<category><![CDATA[кэш]]></category>
		<category><![CDATA[кэширование]]></category>
		<category><![CDATA[нагрузка]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=160</guid>
		<description><![CDATA[Серия постов про &#171;Web, кэширование и memcached&#187; продолжается. Начало здесь: 1, 2 и 3. В этих постах мы поговорили о memcached, его архитектуре, возможном применении, выборе ключа кэширования, кластеризации, атомарных операциях и реализации счетчиков в memcached. Сегодня мы рассмотрим проблему одновременного перестроения кэша, которая возникает при большом количестве одновременных обращений к кэшу, который был только [...]]]></description>
			<content:encoded><![CDATA[<p>Серия постов про &laquo;Web, кэширование и memcached&raquo; продолжается. Начало здесь: <a href="http://www.smira.ru/2008/10/16/web-caching-memcached-1/">1</a>, <a href="http://www.smira.ru/2008/10/21/web-caching-memcached-2/">2</a> и <a href="http://www.smira.ru/2008/10/24/web-caching-memcached-3/">3</a>.
В этих постах мы поговорили о memcached, его архитектуре, возможном применении, выборе ключа кэширования, кластеризации, атомарных операциях и реализации счетчиков в <a href="http://danga.com/memcached/">memcached</a>.</p>

<p>Сегодня мы рассмотрим проблему одновременного перестроения кэша, которая возникает при большом количестве одновременных обращений к кэшу, который был только что сброшен или потерян, что может привести к перегрузке БД.</p>

<p><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" /></p>

<p>Следующий пост будет посвящен тэгированию кэшей.</p>

<p><span id="more-160"></span></p>

<h2>Одновременное перестроение кэшей</h2>

<p>Данная проблема характерна в первую очередь для высоконагруженных проектов. Рассмотрим следующую ситуацию: у нас есть выборка из БД, которая используется на многих страницах или особо популярных страницах (например, на главной странице). Эта выборка закэширована с некоторым «сроком годности», т.е. кэш будет сброшен по прошествии некоторого интервала времени. При этом сама выборка является относительно сложной, её вычисление заметно нагружает backend (БД). В какой-то момент времени ключ в memcached будет удален, т.к. истечет срок его жизни (срок жизни был установлен у кэша), в этот момент несколько frontend’ов (несколько, т.к. выборка часто используется) обратятся в memcached по этому ключу, обнаружат его отсутствие и попытаются построить кэш заново, осуществив выборку из БД. То есть в БД одновременно попадет несколько одинаковых запросов, каждый из которых заметно нагружает базу данных, при превышении некоторого порога запрос не будет выполнен за разумное время, еще больше frontend’ов обратятся к кэшу, обнаружат его отсутствие и отправят еще больше запросов в базу данных, с которыми база данных тем более не справится. В результате сервер БД получил критическую нагрузку, и «прилёг». Что делать, как избежать такой ситуации?</p>

<p>Проблема с перестроением кэшей становится проблемой только тогда, когда имеют место два фактора: много обращений к кэшу в единицу времени и сложный запрос. Причем один фактор может компенсировать другой: на относительно непопулярной, но очень сложной и долгой выборке (которых вообще-то не должно быть) мы можем получить аналогичную ситуацию. Итак, что же делать?</p>

<p>Можно предложить следующую схему: мы больше не ограничиваем время жизни ключа с кэшом в memcached – он будет там находиться до тех пор, пока не будет вытеснен другими ключами. Но вместе с данными кэша мы записываем и реальное время его жизни, например:</p>

<pre><code>{
    годен до: 2008-11-03 11:53,
    данные кэша:
    {
       ...
    }
}
</code></pre>

<p>Теперь при получении ключа из memcached мы можем проверить, истёк ли срок жизни кэша с помощью поля «годен до». Если срок жизни истёк, кэш надо перестроить, но мы будем делать это с блокировкой (о блокировках речь пойдет в следующем разделе), если не удастся заблокироваться, мы можем либо подождать еще (раз блокировка уже есть, значит кэш кто-то перестраивает), либо вернуть старое значение кэша. Если заблокироваться удастся, мы строим кэш самостоятельно, при этом другие frontend’ы не будут перестраивать этот же кэш, так как увидят нашу блокировку. Основное преимущество хранения в memcached без указания срока годности – именно возможность получить старое значение кэша в случае, если кэш уже перестраивается кем-то. Что именно делать – ждать, пока кэш построит кто-то другой, и получать новое значение из memcached, или возвращать старое значение, – зависит от задачи, насколько приемлемо старое значение и сколько можно провести времени в состоянии ожидания. Чаще всего можно позволить себе 2-3 секундное ожидание с проверкой удаления блокировки и, если кэш так и не построился (что маловероятно, получается что выборка происходит больше чем за 2-3 секунды), вернуть старое значение, освобождая frontend для других задач.</p>

<h3>Пример такого алгоритма</h3>

<ol>
<li>Получаем доступ к кэшу cache, его срок жизни истёк.</li>
<li>Пытаемся заблокироваться по ключу user cache_lock.

<ul>
<li>Не удалось получить блокировку:

<ul>
<li>ждём снятия блокировки;</li>
<li>не дождались: возвращаем старые данные кэша;</li>
<li>дождались: выбираем значения ключа заново, возвращаем новые данные (построенный кэш другим процессом).</li>
</ul></li>
<li>Удалось получить блокировку:

<ul>
<li>строим кэш самостоятельно.</li>
</ul></li>
</ul></li>
</ol>

<p>Такая схема позволяет исключить или свести к минимуму ситуации «заваливания» backend’а одинаковыми «тяжелыми» запросами, когда реально запрос достаточно выполнить лишь один раз. Остается последний вопрос, как обеспечить корректную блокировку? Очевидно, что так как проблема одновременного перестроения возникает на разных frontend’ах, то блокировка должна быть в общедоступном для них всех месте, то есть в memcached.</p>

<h3>Блокировки в memcached</h3>

<p>Рассмотрим два варианта реализации блокировки (мьютекса, двоичного семафора) с помощью memcached. Первый некорректный, он не может обеспечить корректного исключения параллельных процессов, но очевидный. Второй совершенно корректный, но не настолько очевиден.</p>

<p>Пусть мы хотим заблокироваться по ключу <code>‘lock’</code>: пытаемся получить значения ключа с помощью операции <code>get</code>. Если ключ не найден, значит блокировки нет, и мы с помощью операции <code>set</code> устанавливаем значение этого ключа, например, в единицу, а время жизни устанавливаем в небольшой интервал времени, который превышает максимальное время жизни блокировки, например, в 10 секунд. Теперь, если frontend завершится аварийно и не снимет блокировку, она автоматически уничтожится через 10 секунд. Итак, с помощью <code>set</code> мы блокировку установили, выполнили все необходимые действия, после этого снимаем блокировку просто удаляя соответствующий ключ командой <code>del</code>. Если на первой операции <code>get</code> мы получили значение ключа, это означает, что блокировка уже установлена другим процессом, наша операция блокировки неуспешна.</p>

<p>Описанный способ обладает недостатком: наличием состояния гонки (race condition). Два процесса могут одновременно сделать <code>get</code>, оба могут получить ответ, что «ключа нет», оба сделают <code>set</code>, и оба будут считать, что установили блокировку успешно. В ситуациях, как одновременное перестроение кэшей, этого может быть допустимо, т.к. здесь цель не исключить все другие процессы, а резко уменьшить количество одновременных запросов к БД, что может обеспечить и этот простой, некорректный вариант.</p>

<p>Второй вариант корректен, и даже проще первого. Для захвата блокировки достаточно выполнить одну команду: <code>add</code>, указав имя ключа и время жизни (такое же маленькое, как и в первом варианте). Команда <code>add</code> будет успешной только в том случае, если ключа в memcached еще нет, то есть наш процесс и есть тот единственный процесс, которому удалось захватить блокировку. Тогда нам надо выполнить необходимые действия и освободить блокировку командой <code>del</code>. Если <code>add</code> вернет ошибку «такой ключ уже существует», значит, блокировка была захвачена раньше каким-то другим процессом.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/10/28/web-caching-memcached-4/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Индексы и селективность (PostgreSQL)</title>
		<link>http://www.smira.ru/2008/06/03/index-selectivity-postgresql/</link>
		<comments>http://www.smira.ru/2008/06/03/index-selectivity-postgresql/#comments</comments>
		<pubDate>Tue, 03 Jun 2008 09:00:09 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Разработка]]></category>
		<category><![CDATA[postgresql]]></category>
		<category><![CDATA[БД]]></category>
		<category><![CDATA[индекс]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=60</guid>
		<description><![CDATA[Индекс по полю в БД потенциально может ускорить SELECT операцию с условием по данному полю, может ускорить заброс вида: ORDER BY поле LIMIT 20, но индекс замедляет существенно операции изменения таблицы и т.п. Когда нужен индекс, когда он поможет и будет использован при SELECTах? Всё зависит от селективности индекса, т.е. от кол-ва строк, которые мы [...]]]></description>
			<content:encoded><![CDATA[<p>Индекс по полю в БД потенциально может ускорить SELECT операцию с условием по данному полю, может ускорить заброс вида: ORDER BY поле LIMIT 20, но индекс замедляет существенно операции изменения таблицы и т.п.</p>

<p>Когда нужен индекс, когда он поможет и будет использован при SELECTах? Всё зависит от селективности индекса, т.е. от кол-ва строк, которые мы получим если зададим условие</p>

<pre><code>проиндексированное_поле = значение
</code></pre>

<p>Отличный кандидат для индексирования &#8211; селективность 1, т.е. уникальный индекс (например, id), когда по указанному значению мы найдем максимум одну запись. Хорошо, когда селективность составляет &lt; 5% (например, поле city_id у пользователя). При этом PostgreSQL умён, он считает не селективность &laquo;вообще&raquo;, а селективность в виде гистограммы по отдельным значениям поля. Т.е. если мы задаем условие вида</p>

<pre><code>страна = Россия
</code></pre>

<p>то получим 10% записей из БД, а если условие</p>

<pre><code>страна = Уругвай
</code></pre>

<p>то получим 2 записи, и это PostgreSQL понимает.</p>

<p>Так вот, если селективность плохая (получим много записей), PostgreSQL предпочтёт выполнить полное сканирование БД, не используя индекс.  И такой индекс только мешает.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/06/03/index-selectivity-postgresql/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>

