<?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>Блог Андрея Смирнова</title>
	<atom:link href="http://www.smira.ru/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.smira.ru</link>
	<description></description>
	<lastBuildDate>Tue, 04 May 2010 18:38:21 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Профайлинг Twisted-приложений</title>
		<link>http://www.smira.ru/2010/02/15/profiling-twisted-applications/</link>
		<comments>http://www.smira.ru/2010/02/15/profiling-twisted-applications/#comments</comments>
		<pubDate>Mon, 15 Feb 2010 20:11:56 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Twisted]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[hotshot]]></category>
		<category><![CDATA[kcachegrind]]></category>
		<category><![CDATA[profiling]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[twisted]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=525</guid>
		<description><![CDATA[ Часто сам забываю, как профилировать легко и быстро Twisted-приложения (с некоторым изменениями подойдет для любых Python-приложений). Кроме Twisted нам понадобится еще KCachegrind

Запускаем наше приложение с включенным профайлингом:


twistd -n --savestats --profile=myprog.hotshot myprog


Подаем нагрузку, профайл собирается. Теперь с помощью утилиты hotshot2cg из поставки KCachegrind превращаем hotshot-профайл в calltree-профайл, который уже умеет KCachegrind &#171;кушать&#187;.


hotshot2cg myprog.hotshot > myprog.calltree


Запускаем [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.smira.ru/wp-content/uploads/2010/02/kcachegrind.png"><img src="http://www.smira.ru/wp-content/uploads/2010/02/kcachegrind-300x182.png" alt="" title="kcachegrind" width="300" height="182" class="alignleft size-medium wp-image-534" /></a> Часто сам забываю, как профилировать легко и быстро Twisted-приложения (с некоторым изменениями подойдет для любых Python-приложений). Кроме Twisted нам понадобится еще <a href="http://kcachegrind.sourceforge.net/">KCachegrind</a></p>

<p>Запускаем наше приложение с включенным профайлингом:</p>

<pre>
twistd -n --savestats --profile=myprog.hotshot myprog
</pre>

<p>Подаем нагрузку, профайл собирается. Теперь с помощью утилиты <code>hotshot2cg</code> из поставки KCachegrind превращаем hotshot-профайл в calltree-профайл, который уже умеет KCachegrind &laquo;кушать&raquo;.</p>

<pre>
hotshot2cg myprog.hotshot > myprog.calltree
</pre>

<p>Запускаем KCachegrind, открываем в нем полученный профайл:</p>

<pre>
kcachegrind myprog.calltree
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2010/02/15/profiling-twisted-applications/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>MySQL, ROW/STATEMENT/MIXED-репликация и триггеры</title>
		<link>http://www.smira.ru/2010/02/15/mysql-row-statement-mixed-replication-triggers/</link>
		<comments>http://www.smira.ru/2010/02/15/mysql-row-statement-mixed-replication-triggers/#comments</comments>
		<pubDate>Mon, 15 Feb 2010 19:48:10 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Разработка]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[trigger]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=527</guid>
		<description><![CDATA[Описанная особенность MySQL попалась мне на глаза слишком поздно, пишу, чтобы кто-то не напоролся на те же грабли. Начнем с начала. Итак, необходимо было отслеживать изменения MySQL-базы данных и складывать эти изменения в очередь (не в БД) для дальнейшей обработки внешней системой. Для отслеживания изменений подходят триггеры, но они активируются в процессе выполнения запросов транзакции [...]]]></description>
			<content:encoded><![CDATA[<p>Описанная особенность MySQL попалась мне на глаза слишком поздно, пишу, чтобы кто-то не напоролся на те же грабли. Начнем с начала. Итак, необходимо было отслеживать изменения MySQL-базы данных и складывать эти изменения в очередь (не в БД) для дальнейшей обработки внешней системой. Для отслеживания изменений подходят триггеры, но они активируются в процессе выполнения запросов транзакции и в случае последующего &laquo;rollback&raquo; не будут откатываться (что совершенно нормально для триггеров, влияющих только на состояние БД, т.к. состояние БД будет корректно откатываться).  Поэтому необходимо выполнять триггеры только для успешных транзакций: проще всего это достигнуть с помощью репликации &#8211; на слейв передаются только запросы зафиксированных транзакций. Таким образом, мастер-БД не содержит триггеров, после репликации данные попадают на слейв, таблицы на котором обвешаны триггерами, те активируются и данные попадают в очередь. Казалось бы, все замечательно?</p>

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

<p>Однако MySQL не был бы MySQL, если бы не какие-нибудь приколы на пути. Читаем раздел <a href="http://dev.mysql.com/doc/refman/5.1/en/replication-features-triggers.html">16.3.1.29</a> документации: триггеры на слейве выполняются только при использовании STATEMENT-based репликации, при использовании ROW-based репликации они выполняются на мастере. ROW-based репликация &#8211; одно из нововведений 5.1, при котором на слейв передаются не запросы (текст запроса), измененный в БД записи (подробнее см. раздел <a href="http://dev.mysql.com/doc/refman/5.1/en/replication-formats.html">16.1.2</a>). Но это было бы еще полбеды &#8211; попробовал, увидел, что триггеры не выполняются, и пошел разбираться. Однако в MySQL придумали еще режим репликации MIXED: при этом сервер сам переключается между STATEMENT и ROW-based репликацией (я не нашел, по какому условию). Далее, в версиях MySQL от 5.1.12 до 5.1.29 режимом репликации по умолчанию <a href="http://dev.mysql.com/doc/refman/5.1/en/server-options.html#option_mysqld_binlog-format">выбирается как раз MIXED</a>. Как вы уже догадываетесь, в MIXED-режиме триггеры на слейве то выполняются, то нет, при этом внешне это совершенно незаметно. Так прекрасно работавший слейв в один прекрасный день вдруг перестает поставлять обновления в очередь, хотя сам по себе слейв не отстал и содержит все данные.</p>

<p>Итого, несколько часов на поиск проблемы, работа по пересинхронизации данных во внешней системе&#8230; Спасибо, MySQL!</p>

<p>Кратко: чтобы триггеры на слейве всегда выполнялись, надо добавить в my.cnf (на мастере):</p>

<pre>
[mysqld]
binlog-format=STATEMENT
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2010/02/15/mysql-row-statement-mixed-replication-triggers/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>HL++ (2009): Twisted Framework</title>
		<link>http://www.smira.ru/2009/10/13/hl-2009-twisted-framework/</link>
		<comments>http://www.smira.ru/2009/10/13/hl-2009-twisted-framework/#comments</comments>
		<pubDate>Tue, 13 Oct 2009 18:42:27 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[Qik]]></category>
		<category><![CDATA[Twisted]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[highload]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[twisted]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=517</guid>
		<description><![CDATA[Сегодня выступал на HighLoad++ с докладом Twisted Framework &#8211; фреймворк для написания сетевых приложений в Python. 

Введение

Последнее время в области web происходит смещение внимания с тяжелых application-серверов, которые тратят на обработку запроса сотни миллисекунд, а то и секунды, к более легковесным сервисам, передающим меньшие объемы данных с минимальной задержкой. Переход от генерации десятков и сотен [...]]]></description>
			<content:encoded><![CDATA[<p>Сегодня выступал на <a href="http://highload.ru/">HighLoad++</a> с докладом <a href="http://www.highload.ru/papers2009/12261.html">Twisted Framework &#8211; фреймворк для написания сетевых приложений в Python</a>. </p>

<h2>Введение</h2>

<p>Последнее время в области web происходит смещение внимания с тяжелых application-серверов, которые тратят на обработку запроса сотни миллисекунд, а то и секунды, к более легковесным сервисам, передающим меньшие объемы данных с минимальной задержкой. Переход от генерации десятков и сотен килобайт HTML-кода в ответ на запрос к передаче изменений в данных, запакованных в JSON и измеряемых сотнями байт. В качестве примеров таких сервисов можно привести Gmail, FriendFeed, Twitter Live Search и т.п.</p>

<p>Для обеспечения минимальной задержки для пользователя необходимо либо поддерживать постоянное соединение (например, Adobe Flash, RTMP) или использовать технику HTTP long polling в сочетании с keep alive. Так или иначе на стороне сервера это приводит к появлению большого количества одновременных соединений (тысячи, десятки тысяч), по каждому из которых передается не такой большой объем данных. Эту ситуацию называют обычно проблемой <a href="http://www.kegel.com/c10k.html">C10k</a>.</p>

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

<p>Для обработки соединений архитектурный выбор на стороне сервера не такой большой: процесс на соединение, нить на соединение, комбинированный вариант процесс-нити или асинхронный ввод-вывод (возможно, в сочетании с дополнительными процессами или нитями). При наличии более 10 тысяч одновременных соединений с точки зрения расхода ресурсов совершенно невозможно представить создание 10 тысяч процессов; 10 тысяч нитей также вряд ли будет разумным решением. Необходимо дополнительно учесть, что при наличии такого большого числа соединений объем работы по каждому из них относительно невелик, большинство их них простаивают в ожидании поступления новых данных. Поэтому бóльшая часть процессов или нитей будет просто находиться в состоянии ожидания, расходуя впустую  системные ресурсы.
Асинхронный ввод-вывод позволяет осуществлять неблокирующийся сетевой ввод-вывод по тысячам открытых сокетов в рамках одной нити выполнения (одного процесса). Механизмы реализации в разных ОС разные, например: select(), poll(), epoll(), kqueue() и т.п. Примеры приложений, использующих асинхронный ввод-вывод:</p>

<ul>
<li>nginx (используются дополнительные процессы для обслуживания задач, требующих большего объема CPU);</li>
<li>haproxy;</li>
<li>memcached;</li>
<li>и другие.</li>
</ul>

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

<p><a href="http://www.twistedmatrix.com/">Twisted Framework</a> — это обширный набор классов и модулей для реализации асинхронных сетевых приложений. Twisted Framework — это:</p>

<ul>
<li>ядро, абстрагирующее все операции асинхронного ввода-вывода и использующее соответствующий механизм конкретной ОС;</li>
<li>концепция Deferred, которая позволяет реализовать в простой форме обслуживание запроса: асинхронные сетевые обращения (например, к БД, memcached), обработку ошибочных ситуаций; Deferred является аналогом обычных конструкций последовательного программирования для асинхронной модели программирования;</li>
<li>обширный набор уже реализованных сетевых протоколов: HTTP, DNS, SMTP, IMAP, memcached, Jabber, ICQ и т.д.; еще большее количество протоколов доступно в виде дополнительных модулей;</li>
<li>дополнительная инфраструктура: unit-testы с поддержкой Deferred, пулы нитей, процессов и т.д.; </li>
<li>качественная концепция разработки — полное покрытие unit-testами, строгий review любого изменения.</li>
</ul>

<p>Основная часть доклада будет посвящена конкретным примерам приложений, реализованными с помощью Twisted — с архитектурой, конкретными параметрами производительности, приемами оптимизации, преимуществами и недостатками Twisted для решения данной задачи:</p>

<ul>
<li><a href="http://www.smira.ru/2008/04/09/rit-2008/">RTMP-сервер pyFMS</a>, сервер вещаний сервиса Smotri.Com (сотни трансляций, десятки тысяч зрителей);</li>
<li>backend-сервер <a href="http://www.mdc.ru/">проекта MDC</a> &#8211; хранение и обработка истории общения пользователей, хранение настроек и т.п.;</li>
<li><a href="http://www.smira.ru/2009/07/12/qik-push-engine-api-private-beta/">Qik Push Engine</a> &#8211; сервер немедленной доставки изменений информации о видео, созданных пользователями сервиса, в том числе push-нотификация о появившихся live-стримах, масштабирование, обработка больших объемов информации.</li>
</ul>

<p>Дополнительная информация:</p>

<ul>
<li><a href="http://twistedmatrix.com/projects/core/documentation/howto/">Документация Twisted</a></li>
<li><a href="http://www.smira.ru/category/development/python-development/">О Python</a>, а также о <a href="http://www.smira.ru/category/development/twisted-development/">Twisted</a></li>
<li><a href="http://habrahabr.ru/blogs/twisted/">Блог на Хабрахабре про Twisted</a></li>
<li><a href="http://burus.org/2008/12/16/twisted-classic-examples/">Александр Бурцев о Twisted</a></li>
<li><a href="http://www.smira.ru/2009/02/10/deferred-async-programming/">Deferred в Twisted</a> и <a href="http://www.smira.ru/2009/02/24/more-about-deferred/">не только</a> .</li>
</ul>

<h2>Презентация</h2>

<div style="width:425px;text-align:left" id="__ss_2211313"><a style="font:14px Helvetica,Arial,Sans-serif;display:block;margin:12px 0 3px 0;text-decoration:underline;" href="http://www.slideshare.net/Smirnov.Andrey/twisted-framework-python-2211313" title="Twisted Framework - сетевые приложения в Python">Twisted Framework &#8211; сетевые приложения в Python</a><object style="margin:0px" width="425" height="355"><param name="movie" value="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=smirnov-twisted-python-091013134034-phpapp02&#038;stripped_title=twisted-framework-python-2211313" /><param name="allowFullScreen" value="true"/><param name="allowScriptAccess" value="always"/><embed src="http://static.slidesharecdn.com/swf/ssplayer2.swf?doc=smirnov-twisted-python-091013134034-phpapp02&#038;stripped_title=twisted-framework-python-2211313" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="355"></embed></object><div style="font-size:11px;font-family:tahoma,arial;height:26px;padding-top:2px;">View more <a style="text-decoration:underline;" href="http://www.slideshare.net/">documents</a> from <a style="text-decoration:underline;" href="http://www.slideshare.net/Smirnov.Andrey">Smirnov.Andrey</a>.</div></div>

<ul>
<li><a href='http://www.smira.ru/wp-content/uploads/2009/10/smirnov-twisted-python.pdf'>Скачать презентацию (PDF)</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/10/13/hl-2009-twisted-framework/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
		<item>
		<title>Mongrel vs. Phusion Passenger: выбор очевиден</title>
		<link>http://www.smira.ru/2009/10/05/mongrel-vs-phusion-passenger-obvious-choice/</link>
		<comments>http://www.smira.ru/2009/10/05/mongrel-vs-phusion-passenger-obvious-choice/#comments</comments>
		<pubDate>Mon, 05 Oct 2009 19:41:00 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Qik]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[mongrel]]></category>
		<category><![CDATA[passenger]]></category>
		<category><![CDATA[phusion]]></category>
		<category><![CDATA[rails]]></category>
		<category><![CDATA[ruby]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=507</guid>
		<description><![CDATA[Предыдущая конфигурация:


nginx (главный proxy), который раздает трафик в
haproxy (ради возможности балансировать по нагрузке), который распределяет нагрузку по нескольким webapp-серверам
с 16-ю mongrelами на каждом


Проблемы:


&#171;Утекающая&#187; память, периодический out of memory на серверах, лечится только перезапуском mongrelов.
Запросы, занимающие десятки секунд из-за неверной балансировки (в нагруженный mongrel все-таки попадает несколько &#171;тяжелых&#187; запросов).
Сложность управления кластером монгрелов &#8211; постоянные проблемы при [...]]]></description>
			<content:encoded><![CDATA[<p>Предыдущая конфигурация:</p>

<ul>
<li><a href="http://sysoev.ru/nginx/">nginx</a> (главный proxy), который раздает трафик в</li>
<li><a href="http://haproxy.1wt.eu/">haproxy</a> (ради возможности балансировать по нагрузке), который распределяет нагрузку по нескольким webapp-серверам</li>
<li>с 16-ю <a href="http://mongrel.rubyforge.org/">mongrelами</a> на каждом</li>
</ul>

<p>Проблемы:</p>

<ol>
<li>&laquo;Утекающая&raquo; память, периодический out of memory на серверах, лечится только перезапуском <a href="http://mongrel.rubyforge.org/">mongrelов</a>.</li>
<li>Запросы, занимающие десятки секунд из-за неверной балансировки (в нагруженный <a href="http://mongrel.rubyforge.org/">mongrel</a> все-таки попадает несколько &laquo;тяжелых&raquo; запросов).</li>
<li>Сложность управления кластером монгрелов &#8211; постоянные проблемы при перезапуске, &laquo;не стартующие&raquo; <a href="http://mongrel.rubyforge.org/">mongrelы</a> и т.п.</li>
</ol>

<p>Новая конфигурация:</p>

<ul>
<li><a href="http://sysoev.ru/nginx/">nginx</a> (proxy) остался</li>
<li><a href="http://www.modrails.com/">Phusion Passenger</a> + <a href="http://www.rubyenterpriseedition.com/">Ruby Enterprise Edition</a> на каждой машине.</li>
</ul>

<p>Результат:</p>

<p><img src="http://www.smira.ru/wp-content/uploads/2009/10/webapp01-passenger-mongrel.png" alt="webapp01-passenger-mongrel" title="webapp01-passenger-mongrel" width="603" height="250" class="aligncenter size-full wp-image-508" /></p>

<p>Комментарий: переход на <a href="http://www.modrails.com/">Phusion Passenger</a> на Week 39, объем занятой памяти &#8211; это белая область на графике, растущая сверху вниз. До перехода на Passenger объем свободной памяти стремительно уменьшался, иногда доходя до нуля, после перехода остается более-менее стабильным. Использование CPU осталось на прежнем уровне (как и ожидалось).</p>

<p>После перехода исчезли запросы, которые по непонятным причинам занимали десятки секунд &#8211; время выполнения коррелирует со сложностью запроса.</p>

<p>Так что если вы еще не переключились, мы идем к вам <img src='http://www.smira.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>

<p>P.S. Отдельное спасибо <a href="http://github.com/glebpom">glebpom</a> за подсказку.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/10/05/mongrel-vs-phusion-passenger-obvious-choice/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>HL++2009: Twisted Framework &#8211; фреймворк для написания сетевых приложений в Python</title>
		<link>http://www.smira.ru/2009/09/25/hl2009-twisted-framework-python/</link>
		<comments>http://www.smira.ru/2009/09/25/hl2009-twisted-framework-python/#comments</comments>
		<pubDate>Fri, 25 Sep 2009 18:32:21 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=499</guid>
		<description><![CDATA[

На HighLoad++-2009 буду выступать с докладом Twisted Framework &#8211; фреймворк для написания сетевых приложений в Python. Конференция будет проходит 12-13 октября 2009 г. в Инфопространстве. Приглашаю всех желающих!

Тезисы доклада:


Архитектура сетевых сервисов, нити, процессы, асинхронный ввод-вывод.
Тенденции в изменении структуры нагрузки на сетевые сервисы: AJAX, Comet/BOSH, клиент-серверная архитектура, проблема 10k.
Асинхронный ввод-вывод и параллельное программирование: достоинства и недостатки. [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://www.smira.ru/wp-content/uploads/2009/09/highload.png" alt="highload" title="highload" width="370" height="71" class="size-full wp-image-502" /></p>

<p>На <a href="http://highload.ru/">HighLoad++-2009</a> буду выступать с докладом <a href="http://www.highload.ru/papers2009/12261.html">Twisted Framework &#8211; фреймворк для написания сетевых приложений в Python</a>. Конференция будет проходит 12-13 октября 2009 г. в Инфопространстве. Приглашаю всех желающих!</p>

<p>Тезисы доклада:</p>

<ol>
<li>Архитектура сетевых сервисов, нити, процессы, асинхронный ввод-вывод.</li>
<li>Тенденции в изменении структуры нагрузки на сетевые сервисы: AJAX, Comet/BOSH, клиент-серверная архитектура, проблема 10k.</li>
<li>Асинхронный ввод-вывод и параллельное программирование: достоинства и недостатки. Поддержка локального контекста, deadlock, lock contention, starvation, масштабирование на многоядерную архитектуру и т.д.</li>
<li>Twisted Framework с высоты птичьего полета.</li>
<li>Аналоги Twisted в других языках программирования: Ruby — EventMachine, Perl — POE.</li>
<li>Центральная концепция Twisted: Deferred — как сохранить контекст выполнения в однопоточном коде с асинхронным вводом-выводом.</li>
<li>Аналогии между последовательным кодом и асинхронным кодом с использованием Deferred.</li>
<li>Twisted и использование нитей: модель worker, «оборачивание» legacy кода.</li>
<li>Реальные примеры Twisted-приложений, цифры, факты, архитектурные решения, преимущества и недостатки:

<ul>
<li>pyFMS — сервер RTMP-вещаний, нагрузка, оптимизация Python-кода;</li>
<li>MDC-сервер, масштабирование;</li>
<li>Qik Push Engine, обслуживание тысяч клиентов, тестирование клиентов. </li>
</ul></li>
<li>Качество кода Twisted, модель разработки, перспективы развития проекта, экосистема Twisted. Что может Twisted дать моему проекту?</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/09/25/hl2009-twisted-framework-python/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Qik Push Engine API: приглашаем разработчиков</title>
		<link>http://www.smira.ru/2009/07/12/qik-push-engine-api-private-beta/</link>
		<comments>http://www.smira.ru/2009/07/12/qik-push-engine-api-private-beta/#comments</comments>
		<pubDate>Sun, 12 Jul 2009 13:56:40 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Qik]]></category>
		<category><![CDATA[Twisted]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[engine]]></category>
		<category><![CDATA[push]]></category>
		<category><![CDATA[twisted]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=461</guid>
		<description><![CDATA[ Qik &#8211; это сервис стриминга (вещания) и загрузки видео с мобильных телефонов. Загруженное видео можно посмотреть на сайте или на его специальной версии с мобильного телефона. Доступна интеграция с другими сервисами, такими как Twitter, Facebook и другие. Клиенты для практически всех современных моделей телефонов: iPhone, Windows Mobile, Symbian, Android, Blackberry и другие.

Qik Push Engine [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://qik.com/"><img src="http://www.smira.ru/wp-content/uploads/2009/07/qik_logo.png" alt="qik_logo" title="qik_logo" width="177" height="92" class="alignleft size-full wp-image-462" /></a> <a href="http://qik.com/">Qik</a> &#8211; это сервис стриминга (вещания) и загрузки видео с мобильных телефонов. Загруженное видео можно посмотреть на <a href="http://qik.com/">сайте</a> или на его <a href="http://m.qik.com/">специальной версии</a> с мобильного телефона. Доступна интеграция с другими сервисами, такими как Twitter, Facebook и другие. Клиенты для практически всех современных моделей телефонов: iPhone, Windows Mobile, Symbian, Android, Blackberry и другие.</p>

<p>Qik Push Engine &#8211; это механизм, который позволяет получать мгновенные оповещения о новых/изменившихся Qik-видео. Например, можно посмотреть постоянно обновляющийся список live-видео, все видео из района Новопеределкино или все видео со словом &laquo;кошка&raquo;. На основе Qik Push Engine API можно построить интересные приложения, интегрированные с Qik, или добавить функциональность в уже существующие. Можно написать собственную систему нотификации, desktop-widget 
или что-то еще.</p>

<p>Сегодня мы открываем API для работы c Qik Push Engine. Это первая ласточка в большом списке API, открывающих доступ к платформе стриминга Qik. Если вам интересно посмотреть Qik Push Engine в действии, заходите на одну из <a href="http://engine.qik.com/examples/all.videos.html">страниц примеров</a>.</p>

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

<h2>Основы работы с API</h2>

<p><a href="http://qikapi.pbworks.com/">Qik Push Engine API</a> доступно в виде набора удаленных процедур по протоколу JSON-RPC (over HTTP). В ближайшее время будет открыт REST-подобный интерфейс. Точкой входа для JSON-RPC является http://engine.qik.com/api/jsonrpc. Пока проект находится в закрытом бета-тестировании для доступа к API необходимо указать ключ разработчика, добавив параметр apikey: http://engine.qik.com/api/jsonrpc?apikey=xxxxxxx. Ключ разработчика можно получить, отправив письмо с запросом на <a href="mailto:api@qik.com">api@qik.com</a>.</p>

<p>Пример HTTP-сессии:</p>

<pre>
POST /api/jsonrpc
Host : engine.stage.qik.com
Content-Type:  application/json; charset=UTF-8
Content-Length: 46

{"method": "qik.session.create", "params": []}
</pre>

<p>Ответ:</p>

<pre>
Content-Type: text/json 

["a56c1603-1fbb-4140-8b35-8c36abbd8b27"]
</pre>

<h2>Пример подписки на поток событий</h2>

<p>Здесь и далее я буду использовать простую запись вызова команд, которая похожа на вызов обычных функций, опуская детали JSON-RPC взаимодействия. Итак, пусть мы хотим подписаться на список всех live-стримов, которые есть данный момент.</p>

<p>Первым делом создадим сессию:
<pre>
qik.session.create() -> "a56c1603-1fbb-4140-8b35-8c36abbd8b27"
</pre></p>

<p>Подписываемся в рамках сессии на view всех публичных live-стримов, при этом указываем максимальное количество элементов во view (limit). Элементы view будут упорядочены в порядке убывания даты начала стрима (самые новые первыми):
<pre>
qik.stream.subscribe&#95;public_live("a56c1603-1fbb-4140-8b35-8c36abbd8b27", 10) -> 
 [ "174/('PublicLiveStreamView', 'Stream', [('stoptime', None)], 'alllive;limit=10')", [ ... ] ]
</pre></p>

<p>В ответ на запрос мы получаем массив из двух элементов: первым является ключ подписки (длинная строка, начинающаяся с &laquo;174/..&raquo;). Не ищите смысла в ключе подписки, его необходимо просто сохранить, он потребуется в дальнейшем.</p>

<p>Вторым элементом является начальное состояние view, то есть текущий список live-стримов в нашем случае. Мы получим не больше 10 элементов (так как указали limit 10 при вызове метода <code>qik.stream.subscribe_public_live</code>). Состояние view выглядит примерно следующим образом:</p>


<div class="wp_syntax"><div class="code"><pre class="javascript" style="font-family:monospace;"><span style="color: #009900;">&#91;</span>
   <span style="color: #009900;">&#123;</span><span style="color: #3366CC;">&quot;url&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;http://qik.com/video/2158468&quot;</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;live&quot;</span><span style="color: #339933;">:</span> <span style="color: #003366; font-weight: bold;">true</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;user_id&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">340699</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;small_thumbnail_url&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;http://media.qik.com/media.thumbnails.128/c8ad8fe065ad4ad7ac8491874c043eac.jpg&quot;</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;title&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;Untitled&quot;</span><span style="color: #339933;">,</span> <span style="color: #3366CC;">&quot;duration&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">0</span><span style="color: #339933;">,</span>
    <span style="color: #3366CC;">&quot;created_at&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;2009-07-11 15:56:03&quot;</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;views&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">0</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;id&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">2158468</span><span style="color: #009900;">&#125;</span><span style="color: #339933;">,</span> 
&nbsp;
   <span style="color: #009900;">&#123;</span><span style="color: #3366CC;">&quot;url&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;http://qik.com/video/2158466&quot;</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;live&quot;</span><span style="color: #339933;">:</span> <span style="color: #003366; font-weight: bold;">true</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;user_id&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">340119</span><span style="color: #339933;">,</span> 
    <span style="color: #3366CC;">&quot;small_thumbnail_url&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;http://media.qik.com/media.thumbnails.128/984c33e1ead441038f315e1fff109fc5.jpg&quot;</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;title&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;Testando!&quot;</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;duration&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">0</span><span style="color: #339933;">,</span> <span style="color: #3366CC;">&quot;created_at&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;2009-07-11 15:55:34&quot;</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;views&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">0</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;id&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">2158466</span><span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#93;</span></pre></div></div>


<p>Смысл большинства полей очевиден, приведу лишь некоторые комментарии:</p>

<ul>
<li>все даты приведены в UTC;</li>
<li>поле <code>duration</code> хранит длительность видео в секундах.</li>
</ul>

<p>Так как мы подписались на live-стримы, у всех видео <code>live == true</code>.</p>

<p>Теперь нам необходимо получать новые события для тех view, на которые мы подписались. Для этого используются HTTP long polling-запросы. Клиент отправляет запрос серверу, а сервер отправляет ответ, когда появляются события или когда истечет таймаут, если событий не появилось. </p>

<p>Для реализации long polling в цикле вызываем метод <code>qik.session.get_events</code>, указывая желаемый таймаут (не рекомендуется использовать таймаут более 90 секунд).</p>

<pre>
qik.session.get_events("a56c1603-1fbb-4140-8b35-8c36abbd8b27", 60) -> 
  [ {...} ,{...} ]
</pre>

<p>В ответ мы можем получить пустой массив событий, если истек таймаут, или некоторый набор событий следующего вида:</p>


<div class="wp_syntax"><div class="code"><pre class="javascript" style="font-family:monospace;"><span style="color: #009900;">&#91;</span>
   <span style="color: #009900;">&#123;</span>
     <span style="color: #3366CC;">&quot;action&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;update&quot;</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;old&quot;</span><span style="color: #339933;">:</span> <span style="color: #009900;">&#123;</span><span style="color: #3366CC;">&quot;id&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">104252</span><span style="color: #339933;">,</span> <span style="color: #3366CC;">&quot;title&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;Untitled&quot;</span><span style="color: #339933;">,</span> <span style="color: #3366CC;">&quot;views&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">0</span><span style="color: #339933;">,</span> ...<span style="color: #009900;">&#125;</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;obj&quot;</span><span style="color: #339933;">:</span> <span style="color: #009900;">&#123;</span><span style="color: #3366CC;">&quot;id&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">104252</span><span style="color: #339933;">,</span> <span style="color: #3366CC;">&quot;title&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;driving to key largo&quot;</span><span style="color: #339933;">,</span> <span style="color: #3366CC;">&quot;views&quot;</span><span style="color: #339933;">:</span> <span style="color: #CC0000;">5</span><span style="color: #339933;">,</span> ...<span style="color: #009900;">&#125;</span><span style="color: #339933;">,</span> 
     <span style="color: #3366CC;">&quot;key&quot;</span><span style="color: #339933;">:</span> <span style="color: #3366CC;">&quot;175/('PublicRecentStreamView', 'Stream', [], 'allrecent;limit=50')&quot;</span>
    <span style="color: #009900;">&#125;</span><span style="color: #339933;">,</span>
<span style="color: #009900;">&#93;</span></pre></div></div>


<p>В каждом событии обязательно передается следующая инормация:</p>

<ul>
<li><code>key</code> &#8211; ключ подписки, это то же самое значение, которое мы получили при подписке на view, ключ позволяет отличать события для разных view в рамках одной сессии;</li>
<li><code>action</code> &#8211; действие (изменение), произошедшее с view:

<ul>
<li><code>ping</code> &#8211; ничего не произошло, просто view сообщает, что все хорошо <img src='http://www.smira.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </li>
<li><code>update</code> &#8211; элемент view изменился (как в примере выше), передается старое и новое состояние объекта в полях <code>old</code> и <code>obj</code></li>
<li><code>insert</code> &#8211; во view добавился новый элемент, его состояние будет записано в поле <code>obj</code></li>
<li><code>delete</code> &#8211; элемент исчез из view (может быть, видео было удалено, или просто перестало удовлетворять критериям view, или ушло за границу &laquo;limit&raquo;), последнее состояние объекта будет записано в поле <code>obj</code>.</li>
</ul></li>
</ul>

<p>В ответ на события мы можем обновить информацию на экране для пользователя, выполнить какие-либо еще действия. Но обязательно надо отправить новый запрос <code>qik.session.get_events</code>, чтобы получить новые события.</p>

<p>В конце работы не забываем &laquo;убраться&raquo; за собой, убиваем сессию:</p>

<pre>
qik.session.destroy("a56c1603-1fbb-4140-8b35-8c36abbd8b27") 
</pre>

<p>Замечания:</p>

<ul>
<li>В рамках одной сессии можно подписаться на произвольное количество view. </li>
<li>Если это необходимо, можно авторизовать сессию от имени пользователя с помощью <code>qik.session.authorize</code>. </li>
<li>Существует большое количество вспомогательных запросов, которым сессия вообще не требуется, например <code>qik.user.public_profile</code>.</li>
<li>Можно отписаться от view при помощи конмады<code>qik.session.unsubscribe</code>.</li>
</ul>

<h2>Reference JS-клиент</h2>

<p>Для демонстрации возможностей Qik Push Engine и проверки его работы был реализован легкий JavaScript-клиент. Его можно увидеть в работе на примерах, полный список которых приведен ниже.</p>

<p>Если вы запустите пример в Firefox с включенным Firebug, в консоли Firebug будет появляться подробная информация о выполняемых запросах к Qik Push Engine API, полученных ответах и т.п.</p>

<p>Исходный код клиента доступен в необфусцированном виде, со всеми комментариями. Код предоставляет по принципам public domain, то есть можете делать с ним все, что захотите &#8211; использовать в своих приложениях, модифицировать, переписывать на другие языкаи программирования и т.п.</p>

<p>Пробежимся быстро по компонентам JS-клиента.</p>

<h3><a href="http://engine.qik.com/examples/js/prototype.js">prototype.js</a></h3>

<p>Совершенно стандартный <a href="http://www.prototypejs.org/">Prototype</a>, ничего интересного.</p>

<h3><a href="http://engine.qik.com/examples/js/deferred.js">deferred.js</a></h3>

<p><code>Deferred</code> &#8211; это одна из основных концепций Twisted Framework (http://twistedmatrix.com). Deferred не является обязательным для взаимодействия с Qik Push Engine API, но позволяет сильно упростить сложные взаимодействия в асинхронном коде. </p>

<p>В качестве дополнительного материала о Deferred можно почитать про <a href="http://www.smira.ru/2009/02/10/deferred-async-programming/">основы работы с Deferred</a>, <a href="http://www.smira.ru/2009/02/24/more-about-deferred/">более сложные случаи использования Deferred</a>, <a href="http://www.smira.ru/2009/05/29/deferred-in-javascript-for-prototype/">данную реализацию Deferred для Prototype</a>, а также взглянуть на Deferred в других JS-фреймворках: <a href="http://mochikit.com/doc/html/MochiKit/Async.html">MochiKit</a>, 
 <a href="http://www.dojotoolkit.org/book/dojo-book-0-9/part-3-programmatic-dijit-and-dojo/miscellaneous/communication-between-threads-do">Dojo</a>.</p>

<p><code>DeferredManager</code> &#8211; простая конструкция на основе Deferred, позволяет запускать не более N асинхронных действий в один момент времени.</p>

<h3><a href="http://engine.qik.com/examples/js/api.js">core.js</a></h3>

<p>Ядро клиента, объявляется namespace <code>QikEngine</code>, отладочные функции, определение конфигурации, создания объекта для доступа к API и т.п.</p>

<h3><a href="http://engine.qik.com/examples/js/api.js">api.js</a></h3>

<p><code>QikEngine.API</code> &#8211; простая обертка JSON-RPC API Qik Push Engine, возвращающая <code>Deferred</code> в качестве результата любого обращения к API. Через <code>Deferred</code> же отправляются ошибки и результаты выполнения методов.</p>

<p><code>QikEngine.Session</code> &#8211; класс, оборачивающий понятие сессии, создание, уничтожение, авторизация в рамках сессии.</p>

<h3><a href="http://engine.qik.com/examples/js/events.js">events.js</a></h3>

<p><code>QikEngine.Events</code> &#8211; получение новых событий с помощью HTTP long polling, технология, похожая на BOSH/Comet. Распределение событий подписчикам (конкретным view). </p>

<h3><a href="http://engine.qik.com/examples/js/view.js">view.js</a></h3>

<p><code>QikEngine.View</code> &#8211; абстрактный класс, представляющий view на стороне клиента. Обслуживание обновлений, отображение в элементах HTML, обработка событий и т.п. Списки элементов хранятся прямо в DOM-контейнере в виде HTML-узлов.</p>

<p>Потомки <code>QikEngine.View</code> для реализации конкретных view:  <code>QikEngine.PublicUserStreamView</code>, <code>QikEngine.PublicRecentStreamsView</code> и т.д.</p>

<h3><a href="http://engine.qik.com/examples/js/updater.js">updater.js</a></h3>

<p>Очень простой интерфейс между данными, полученными из Qik Push Engine (например, информацией о стримах) и HTML-элементами. Адаптация данных, например, перевод дат из UTC в локальный формат, форматирование значений и т.п.</p>

<h3><a href="http://engine.qik.com/examples/js/usercache.js">usercache.js</a></h3>

<p><code>QikEngine.UserCache</code> &#8211; механизм загрузки информации о пользователях (по их ID). Использует <code>DeferredManager</code> для ограничения количества параллельных запросов.</p>

<h2>Что делать дальше?</h2>

<ul>
<li>смотрите <a href="http://qikapi.pbworks.com/">описание API</a>;</li>
<li>пишите письмо на api@qik.com, расскажите о своем проекте и получайте ключ для доступа к API.</li>
</ul>

<h2>Полный список примеров</h2>

<ul>
<li><a href="http://engine.qik.com/examples/all.videos.html">все последние видео на Qik/live-видео</a></li>
<li><a href="http://engine.qik.com/examples/profile.html">открытые стримы пользователя</a></li>
<li><a href="http://engine.qik.com/examples/user.videos.html">профайл пользователя с точки зрения другого авторизованного пользователя</a></li>
<li><a href="http://engine.qik.com/examples/user.home.html">домашняя страница пользователя: собственные стримы, followerы, стримы от following</a></li>
</ul>

<p>Во всех примерах списки видео обновляются автоматически при внесении изменений на сайте или с мобильного телефона, например, если открою доступ Васе к моему приватному видео, он тут же увидит это видео в списке моих стримов и т.п. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/07/12/qik-push-engine-api-private-beta/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>AMQP по-русски</title>
		<link>http://www.smira.ru/2009/06/08/amqp-in-russian/</link>
		<comments>http://www.smira.ru/2009/06/08/amqp-in-russian/#comments</comments>
		<pubDate>Mon, 08 Jun 2009 07:33:57 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[MDC]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Twisted]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[СпамоБорец]]></category>
		<category><![CDATA[amqp]]></category>
		<category><![CDATA[message]]></category>
		<category><![CDATA[queue]]></category>
		<category><![CDATA[twisted]]></category>
		<category><![CDATA[txamqp]]></category>
		<category><![CDATA[разработка]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=392</guid>
		<description><![CDATA[ Сегодня довольно мало информации о протоколе AMQP (Advanced Message Queueing Protocol) и его применении, особенно русском языке. А вообще это &#8211; замечательный, уже достаточно широко поддерживаемый открытый протокол для передачи сообщений между компонентами системы с низкой задержкой и на высокой скорости. При этом семантика обмена сообщениями настраивается под нужды конкретного проекта. Такие решения существовали [...]]]></description>
			<content:encoded><![CDATA[<p><img alt="AMQP logo" src="http://www.smira.ru/wp-content/uploads/2009/06/global.logo_.jpeg" title="AMQP logo" class="alignleft" width="71" height="71" /> Сегодня довольно мало информации о протоколе <a href="http://www.amqp.org/">AMQP</a> (Advanced Message Queueing Protocol) и его применении, особенно русском языке. А вообще это &#8211; замечательный, уже достаточно широко поддерживаемый открытый протокол для передачи сообщений между компонентами системы с низкой задержкой и на высокой скорости. При этом семантика обмена сообщениями настраивается под нужды конкретного проекта. Такие решения существовали и ранее, но это первый стандарт, для которого существует большое количество свободных реализаций.</p>

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

<p>Сегодня тема доставки информации в реальном времени является крайне актуальной (достаточно вспомнить хотя бы Twitter, Google Wave). И здесь системы передачи сообщений могут служить внутренним механизмом обмена данными, который обеспечивает доставку данных (изменений данных) клиентам.</p>

<p>Я не ставлю своей целью сегодня рассказать о том, как писать приложения для AMQP. Хочу лишь немного рассказать о том, что это совсем не страшно, не очень сложно, и действительно работает, хотя стандарт находится еще в развитии, выходят новые версии протокола, брокеров и т.п. Но это уже вполне production-quality. Расскажу лишь базовые советы, чтобы помочь &laquo;въехать&raquo; в протокол.</p>

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

<p>Для начала, маленькая коллекция ссылок (в основном, на английском): что такое вообще обмен сообщениями и почему AMQP такой (<a href="http://www.zeromq.org/whitepapers:amqp-analysis">Messaging in general and AMQP design</a>); сравнение различных реализаций обмена сообщениями, в частности основанных на AMQP (<a href="http://wiki.secondlife.com/wiki/Message_Queue_Evaluation_Notes">Message Queue Comparison</a>); клиентская библиотека AMQP для Twisted Framework (Python) с поддержкой Thrift (<a href="http://us.pycon.org/2009/conference/schedule/event/28/">Thrift, AMQP in Twisted</a>); руководство от Red Hat о том, что такое messaging и как работать с AMQP, описывает их &laquo;коробочный&raquo; продукт на основе AMQP, но подходит и для любых AMQP-брокеров (<a href="http://www.redhatpress.org/docs/en-US/Red_Hat_Enterprise_MRG/1.0/pdf/Messaging_Tutorial/Messaging_Tutorial.pdf">AMQP Programming Tutorial for C++, Java, and Python</a>); достаточно много документации, описаний архитектурных решений на сайте <a href="http://www.zeromq.org/">ZeroMQ</a>, который не совсем AMQP-брокер, но общая архитектура, детали реализации представляют отдельный интерес; обзорная статья от Duncan McGregor о txAMQP и AMQP в общем (<a href="http://feedproxy.google.com/~r/ElectricDuncan/~3/NKiCEOnzXe0/sinfonia-on-messaging-with-txamqp.html">A Simfonia on Messaging with txAMQP</a>, <a href="http://feedproxy.google.com/~r/ElectricDuncan/~3/f3QMwXP6OwI/sinfonia-on-messaging-with-txamqp-part.html">II</a>, <a href="http://feedproxy.google.com/~r/ElectricDuncan/~3/Xi5GBHBooNg/sinfonia-on-messaging-with-txamqp-part_18.html">III</a>)</p>

<p>Далее необходимо выбрать AMQP-брокер, который вы будете использовать. При выборе необходимо рассматривать как собственно характеристики сервера: скорость работы, надежность, легкость установки и поддержки, но также внимательно смотреть на версию AMQP-протокола, которая поддерживается брокером, &#8211; она должна совпадать с версией клиентской AMQP-библиотеки. Из брокеров я бы посоветовал <a href="http://www.rabbitmq.com/">RabbitMQ</a>, написанный на Erlang, и <a href="http://qpid.apache.org/">Qpid</a>, версии на C++ (AMQP 0-10) и Java (0-8, 0-9).</p>

<p>Сам протокол AMQP устроен достаточно интересно: на самом нижнем уровне определяется формат кодирования данных в бинарный вид для передачи по TCP-соединению, выше лежит формат передачи RPC-запросов между сервером и клиентом. Сама семантика работы с сообщениями, создания очередей и т.п. описывается в XML-спецификации, которая по сути задает RPC-интерфейс сервера и клиента (примеры таких XML-файлов для версий <a href="http://jira.amqp.org/confluence/download/attachments/720900/amqp0-8.xml?version=1">0-8</a> и <a href="http://jira.amqp.org/confluence/download/attachments/720900/amqp.0-10.xml?version=1">0-10</a>). Этот XML является последней и конечной спецификацией протокола. Более того, версии протокола 0-8 и 0-10 отличаются настолько сильно, что поддерживать их одновременно вряд ли возможно в одной программе. Что еще более интересно, иногда такие spec-файлы для разных брокеров AMQP, формально поддерживающих одну и ту же версию протокола, отличаются настолько, что не являются взаимозаменяемыми. Но это скорее небольшие технические проблемы.</p>

<p>AMQP основан на трех понятиях:</p>

<ol>
<li>Сообщение (message) &#8211; единица передаваемых данных, основная его часть (содержание) никак не интерпретируется сервером, к сообщению могут быть прицеплены структурированные заголовки.</li>
<li>Точка обмена (exchange) &#8211; в нее отправляются сообщения. Точка обмена распределяет сообщения в одну или несколько очередей. При этом в точке обмена сообщения не хранятся. Точки обмена бывают трех типов: fanout &#8211; сообщение передается во все прицепленные к ней очереди; direct &#8211; сообщение передается в очередь с именем, совпадающим с ключом маршрутизации (routing key) (ключ маршрутизации указывается при отправке сообщения); topic &#8211; нечто среднее между fanout и exchange, сообщение передается в очереди, для которых совпадает маска на ключ маршрутизации, например, <code>app.notification.sms.*</code> &#8211; в очередь будут доставлены все сообщения, отправленные с ключами, начинающимися на <code>app.notification.sms</code>.</li>
<li>Очередь (queue) &#8211; здесь хранятся сообщения до тех пор, пока не будет забраны клиентом. Клиент всегда забирает сообщения из одной или нескольких очередей.</li>
</ol>

<p>Меня в AMQP привлек эффективный бинарный протокол, ориентированность протокола на минимальные задержки и гибкость настройки. Я его использовал для рассылки сообщений всем серверам кластера (fanout exchange) и организации аналогов &laquo;почтовых ящиков&raquo;, в которые доставляются сообщения, адресованные определенным клиентам (direct exchange). Для получения сообщений нет необходимости опрашивать сервер, достаточно подписаться на сообщения из очереди, и сервер передаст их в тот момент, когда они появятся.</p>

<p>В качестве клиентской библиотеки я выбрал библиотеку txAMQP для Twisted Framework (Python). В общем и целом все работает, но где-то требуются небольшие &laquo;доделки&raquo; и &laquo;подкрутки&raquo;, которые я планирую опубликовать на launchpad. В AMQP и вокруг AMQP есть много интересного и перспективного. К примеру, брокер RabbitMQ умеет масштабироваться и работать в едином кластере. Мне кажется, это очень полезная и перспективная технология.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/06/08/amqp-in-russian/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>FMSPy, релиз Alpha (0.1)</title>
		<link>http://www.smira.ru/2009/06/07/flash-media-server-in-python-alpha-release/</link>
		<comments>http://www.smira.ru/2009/06/07/flash-media-server-in-python-alpha-release/#comments</comments>
		<pubDate>Sun, 07 Jun 2009 18:30:58 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[Twisted]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[flash]]></category>
		<category><![CDATA[fmspy]]></category>
		<category><![CDATA[rtmp]]></category>
		<category><![CDATA[разработка]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=415</guid>
		<description><![CDATA[ Flash Media Server written in Python (FMSPy) &#8211; это еще один RTMP-сервер для приложений на Adobe Flash/Flex/Air. FMSPy является аналогом Adobe Flash Media Server, с гораздо меньшими возможностями, однако FMSPy  &#8211; совершенно бесплатный проект с открытым исходным кодом. Проект находится на ранней стадии развития, но в активной разработке.

Итак, что есть на сегодняшний день:


Реализация [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.smira.ru/wp-content/uploads/2009/05/fmspy.png"><img src="http://www.smira.ru/wp-content/uploads/2009/05/fmspy.png" alt="FMSPy" title="FMSPy" width="96" height="96" class="alignleft size-full wp-image-419" /></a> <a href="http://fmspy.org/" title="FMSPy">Flash Media Server written in Python</a> (<a href="http://fmspy.org/" title="FMSPy">FMSPy</a>) &#8211; это <em>еще один</em> RTMP-сервер для приложений на Adobe Flash/Flex/Air. FMSPy является аналогом Adobe Flash Media Server, с гораздо меньшими возможностями, однако FMSPy  &#8211; совершенно бесплатный проект с открытым исходным кодом. Проект находится на ранней стадии развития, но в активной разработке.</p>

<p>Итак, что есть на сегодняшний день:</p>

<ul>
<li>Реализация <a href="http://ru.wikipedia.org/RTMP" title="RTMP">RTMP</a>-протокола: кодирование/декодирование пакетов, разрезание и склеивание из chunks и т.п.</li>
<li>Поддержка базового RPC (Invoke) клиент-сервер и сервер-клиент. То есть из Flash-приложения можно вызывать с помощью класса <a href="http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/net/NetConnection.html" title="NetConnection">NetConnection</a> методы приложения на стороне сервера, и наоборот со стороны сервера вызывать методы приложения.</li>
<li>Инфраструктура для написания приложений (в качестве плагинов к FMSPy) со своим API на <a href="http://www.python.org/" title="Python">Python</a>.</li>
</ul>

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

<p>В ближайших релизах:</p>

<ul>
<li>Стриминг (вещание) с вебкамеры, стриминг видео/аудио с сервера (FLV, MP4, MP3).</li>
<li>Поддержка серверных <a href="http://www.adobe.com/support/flash/action_scripts/actionscript_dictionary/actionscript_dictionary648.html" title="Shared Object">Shared Object</a>.</li>
<li>Анализ загрузки, полуавтоматическая кластеризация для распределения нагрузки.</li>
</ul>

<p><a href="http://fmspy.org/" title="FMSPy">FMSPy</a> написан на <a href="http://www.python.org/" title="Python">Python</a> с использованием <a href="http://twistedmatrix.com/" title="Twisted">Twisted Framework</a>, приложения на FMSPy реализуются также на Python и им доступны все возможности, которые есть в Twisted: асинхронная сетевая модель, соединения с БД, <a href="http://www.smira.ru/tag/memcached" title="Memcached">memcached</a>, различные сервисы и т.п.</p>

<h2>Запуск и установка</h2>

<p>Если у Вас уже установлен Python и setuptools (чаще всего на Unix/Linux это так), достаточно выполнить от имени root:</p>

<pre><code>easy_install fmspy
</code></pre>

<p>Easy_install автоматически установит все необходимые зависимости (если они еще не установлены). Более подробно об установке можно почитать в <a href="http://fmspy.org/manual/en/userguide.html#installation" title="FMSPy установка">документации</a>.</p>

<p>После установки запуск в отладочном режиме (на консоли) осуществляется следующим образом:</p>

<pre><code>twistd -n fmspy
</code></pre>

<p>Для окончания работы сервера достаточно нажать Ctrl+C.</p>

<h2>Примеры</h2>

<p>Вместе с FMSPy устанавливается два примера: эхотест и простенький чат. После запуска откройте страницу <a href="http://localhost:3000/examples/">http://localhost:3000/examples/</a> и выберите интересующий вас. </p>

<p><a href="http://www.smira.ru/wp-content/uploads/2009/06/screenshot.png" target="_blank"><img src="http://www.smira.ru/wp-content/uploads/2009/06/screenshot-263x300.png" alt="echotest" title="echotest" width="263" height="300" class="alignleft size-medium wp-image-454" /></a> <a href="http://www.smira.ru/wp-content/uploads/2009/06/screenshot1.png"  target="_blank"><img src="http://www.smira.ru/wp-content/uploads/2009/06/screenshot1-297x300.png" alt="chat" title="chat" width="297" height="300" class="alignleft size-medium wp-image-456" /></a></p>

<h2>Вместо заключения</h2>

<p>Пробуйте, тестируйте, присоединяйтесь к разработке. Любая помощь приветствуется: написание документации, патчи, идеи новых фич, графические материалы! Все это лучше всего отправить в <a href="http://fmspy.org/" title="FMSPy">трекер</a>.  </p>

<p>Впереди новые релизы, также в ближайшее время статья о написании приложений для FMSPy.</p>

<p>Ссылки:</p>

<ul>
<li><a href="http://fmspy.org/" title="FMSPy">Сайт проекта, трекер</a></li>
<li><a href="http://fmspy.org/wiki/Documentation">Документация</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/06/07/flash-media-server-in-python-alpha-release/feed/</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
		<item>
		<title>Deferred для JavaScript (Prototype)</title>
		<link>http://www.smira.ru/2009/05/29/deferred-in-javascript-for-prototype/</link>
		<comments>http://www.smira.ru/2009/05/29/deferred-in-javascript-for-prototype/#comments</comments>
		<pubDate>Fri, 29 May 2009 15:35:06 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Twisted]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[deferred]]></category>
		<category><![CDATA[javasc]]></category>
		<category><![CDATA[prototype]]></category>
		<category><![CDATA[разработка]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=384</guid>
		<description><![CDATA[

Продолжая тему Deferred для JavaScript предлагаю еще одно переписывание Deferred, теперь в терминах Prototype. Подробнее о самом Deferred можно почитать в двух моих прошлых заметках: Асинхронное программирование: концепция Deferred и Deferred: все подробности. Если кратко, самое распространенное и полезное применение Deferred в JavaScript &#8211; это работа с AJAX или другими RPC-over-HTTP вызовами, когда необходимо совершить [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://www.smira.ru/wp-content/uploads/2009/05/prototype_twisted.png" alt="Prototype and Twisted" title="Prototype and Twisted" width="200" height="200" class="alignleft size-full wp-image-410" /></p>

<p><a href="http://www.smira.ru/2008/11/01/open-source-deferred-qooxdoo/">Продолжая</a> тему <a href="http://twistedmatrix.com/projects/core/documentation/howto/async.html">Deferred</a> для JavaScript предлагаю еще одно переписывание Deferred, теперь в терминах <a href="http://www.prototypejs.org/">Prototype</a>. Подробнее о самом Deferred можно почитать в двух моих прошлых заметках: <a href="http://www.smira.ru/2009/02/10/deferred-async-programming/">Асинхронное программирование: концепция Deferred</a> и <a href="http://www.smira.ru/2009/02/24/more-about-deferred/">Deferred: все подробности</a>. Если кратко, самое распространенное и полезное применение Deferred в JavaScript &#8211; это работа с AJAX или другими RPC-over-HTTP вызовами, когда необходимо совершить цепочку логически связанных вызовов, корректно обрабатывать возникающие ошибки и т.п. С моей точки зрения, Deferred крайне необходим в таких ситуациях.</p>

<p>Перейдем к примерам: обращение к некоторому JSON-RPC API на основе Prototype&#8217;овского <a href="http://www.prototypejs.org/api/ajax/request">Ajax.Request</a> можеть быть обернуто в Deferred следующим образом:</p>

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


<div class="wp_syntax"><div class="code"><pre class="javascript" style="font-family:monospace;"><span style="color: #003366; font-weight: bold;">var</span> Api <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">Class</span>.<span style="color: #660066;">create</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#123;</span>
    initialize <span style="color: #339933;">:</span> <span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span>url<span style="color: #009900;">&#41;</span>
    <span style="color: #009900;">&#123;</span>
        <span style="color: #000066; font-weight: bold;">this</span>.<span style="color: #660066;">url</span> <span style="color: #339933;">=</span> url<span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span><span style="color: #339933;">,</span>
&nbsp;
    call <span style="color: #339933;">:</span> <span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span>method<span style="color: #339933;">,</span> params<span style="color: #009900;">&#41;</span>
    <span style="color: #009900;">&#123;</span>
        <span style="color: #003366; font-weight: bold;">var</span> requestBody <span style="color: #339933;">=</span> $H<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#123;</span> <span style="color: #3366CC;">'method'</span> <span style="color: #339933;">:</span> method<span style="color: #339933;">,</span> <span style="color: #3366CC;">'params'</span> <span style="color: #339933;">:</span> params <span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span>.<span style="color: #660066;">toJSON</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
        <span style="color: #003366; font-weight: bold;">var</span> d <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">new</span> Deferred<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
        <span style="color: #003366; font-weight: bold;">var</span> onSuccess <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span>transport<span style="color: #009900;">&#41;</span>
        <span style="color: #009900;">&#123;</span>
            result <span style="color: #339933;">=</span> transport.<span style="color: #660066;">responseText</span>.<span style="color: #660066;">evalJSON</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
            <span style="color: #000066; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'faultCode'</span> <span style="color: #000066; font-weight: bold;">in</span> result <span style="color: #339933;">&amp;&amp;</span> <span style="color: #3366CC;">'faultString'</span> <span style="color: #000066; font-weight: bold;">in</span> result<span style="color: #009900;">&#41;</span>
            <span style="color: #009900;">&#123;</span>
                <span style="color: #003366; font-weight: bold;">var</span> err <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">new</span> Error<span style="color: #009900;">&#40;</span>result.<span style="color: #660066;">faultString</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
                err.<span style="color: #660066;">faultCode</span> <span style="color: #339933;">=</span> result.<span style="color: #660066;">faultCode</span><span style="color: #339933;">;</span>
                err.<span style="color: #660066;">faultString</span> <span style="color: #339933;">=</span> result.<span style="color: #660066;">faultString</span><span style="color: #339933;">;</span>
                d.<span style="color: #660066;">errback</span><span style="color: #009900;">&#40;</span>err<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
            <span style="color: #009900;">&#125;</span>
            <span style="color: #000066; font-weight: bold;">else</span>
            <span style="color: #009900;">&#123;</span>
                result <span style="color: #339933;">=</span> result<span style="color: #009900;">&#91;</span><span style="color: #CC0000;">0</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
                console.<span style="color: #660066;">log</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">&quot;Got result: &quot;</span><span style="color: #339933;">,</span> result<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
                d.<span style="color: #660066;">callback</span><span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
            <span style="color: #009900;">&#125;</span>
        <span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
&nbsp;
        <span style="color: #003366; font-weight: bold;">var</span> onFailure <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span>transport<span style="color: #009900;">&#41;</span>
        <span style="color: #009900;">&#123;</span>
            d.<span style="color: #660066;">errback</span><span style="color: #009900;">&#40;</span><span style="color: #003366; font-weight: bold;">new</span> Error<span style="color: #009900;">&#40;</span><span style="color: #3366CC;">&quot;API transport error: &quot;</span> <span style="color: #339933;">+</span> transport.<span style="color: #000066;">status</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span>
        <span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
&nbsp;
        <span style="color: #003366; font-weight: bold;">var</span> onException <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span>error<span style="color: #009900;">&#41;</span>
        <span style="color: #009900;">&#123;</span>
            d.<span style="color: #660066;">errback</span><span style="color: #009900;">&#40;</span>error<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
        <span style="color: #009900;">&#125;</span>
&nbsp;
        <span style="color: #003366; font-weight: bold;">new</span> Ajax.<span style="color: #660066;">Request</span><span style="color: #009900;">&#40;</span><span style="color: #000066; font-weight: bold;">this</span>.<span style="color: #660066;">url</span><span style="color: #339933;">,</span> <span style="color: #009900;">&#123;</span>
                method <span style="color: #339933;">:</span> <span style="color: #3366CC;">'post'</span><span style="color: #339933;">,</span>
                postBody <span style="color: #339933;">:</span> requestBody<span style="color: #339933;">,</span>
                requestHeaders <span style="color: #339933;">:</span> <span style="color: #009900;">&#123;</span> <span style="color: #3366CC;">'Content-Type'</span> <span style="color: #339933;">:</span> <span style="color: #3366CC;">'application/json'</span> <span style="color: #009900;">&#125;</span><span style="color: #339933;">,</span>
                onSuccess <span style="color: #339933;">:</span> onSuccess<span style="color: #339933;">,</span>
                onFailure <span style="color: #339933;">:</span> onFailure<span style="color: #339933;">,</span>
                onException <span style="color: #339933;">:</span> onException<span style="color: #339933;">,</span>
            <span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
        <span style="color: #000066; font-weight: bold;">return</span> d<span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span><span style="color: #339933;">,</span>
<span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>


<p>Здесь любое обращение к <code>Api.call</code> будет возвращать новый Deferred, который будет содержать результат удаленного вызова либо исключение (транспортное или от сервера, к которому мы обращаемся). Пусть есть RPC-вызовы <code>sum</code> и <code>mult</code>, которые, соответственно, складывают и перемножают свои аргументы. Тогда вычисление выражения <code>(2+3)*7</code> с использованием нашего класса <code>Api</code> будет выглядеть так:</p>


<div class="wp_syntax"><div class="code"><pre class="javascript" style="font-family:monospace;">  <span style="color: #003366; font-weight: bold;">var</span> api <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">new</span> Api<span style="color: #339933;">;</span>
  api.<span style="color: #660066;">call</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'sum'</span><span style="color: #339933;">,</span> <span style="color: #009900;">&#91;</span><span style="color: #CC0000;">2</span><span style="color: #339933;">,</span> <span style="color: #CC0000;">3</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span>
     .<span style="color: #660066;">addCallback</span><span style="color: #009900;">&#40;</span>
               <span style="color: #003366; font-weight: bold;">function</span> <span style="color: #009900;">&#40;</span>sum_of_2_and_3<span style="color: #009900;">&#41;</span> 
               <span style="color: #009900;">&#123;</span> 
                     <span style="color: #000066; font-weight: bold;">return</span> api.<span style="color: #660066;">call</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'mult'</span><span style="color: #339933;">,</span> <span style="color: #009900;">&#91;</span>sum_of_2_and_3<span style="color: #339933;">,</span> <span style="color: #CC0000;">7</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> 
               <span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span>
     .<span style="color: #660066;">addCallback</span><span style="color: #009900;">&#40;</span>
               <span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span>
               <span style="color: #009900;">&#123;</span> 
                     <span style="color: #000066;">alert</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'(2+3)*7 = '</span> <span style="color: #339933;">+</span> result<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> 
               <span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span>
     .<span style="color: #660066;">addErrback</span><span style="color: #009900;">&#40;</span>
              <span style="color: #003366; font-weight: bold;">function</span> <span style="color: #009900;">&#40;</span>error<span style="color: #009900;">&#41;</span> 
              <span style="color: #009900;">&#123;</span> 
                     <span style="color: #000066;">alert</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'Mmm.. something wrong happened: '</span> <span style="color: #339933;">+</span> error<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> 
              <span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>


<p>Ну и самое главное: </p>

<ul>
<li>исходный код <a href='http://www.smira.ru/wp-content/uploads/2009/05/deferred.js'>Deferred.js</a>;</li>
<li>лицензия MIT, как и у самого <a href="http://mochikit.com/doc/html/MochiKit/Async.html">первого варианта Deferred для JavaScript</a>.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/05/29/deferred-in-javascript-for-prototype/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Пожар на газопроводе и информационный вакуум</title>
		<link>http://www.smira.ru/2009/05/11/fire-in-moscow-no-information/</link>
		<comments>http://www.smira.ru/2009/05/11/fire-in-moscow-no-information/#comments</comments>
		<pubDate>Mon, 11 May 2009 11:10:11 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Личное]]></category>
		<category><![CDATA[вакуум]]></category>
		<category><![CDATA[информация]]></category>
		<category><![CDATA[москва]]></category>
		<category><![CDATA[пожар]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=380</guid>
		<description><![CDATA[В ночь с 9-е на 10-е мая произошел разрыв газопровода, об этом знают все. Мы живем в Ново-Переделкино, примерно в шести километрах от места аварии. Как раз в момент взрыва (около 0:40) мы вышли на балкон, услышали сильный хлопок и небо осветилось. Повернув голову, мы увидели огромный гриб пламени, поднимающийся в небо. Раздался сильный гул, [...]]]></description>
			<content:encoded><![CDATA[<p>В ночь с 9-е на 10-е мая <a href="http://rian.ru/incidents/20090510/170573053.html">произошел разрыв газопровода</a>, об этом знают все. Мы живем в Ново-Переделкино, примерно в шести километрах от места аварии. Как раз в момент взрыва (около 0:40) мы вышли на балкон, услышали сильный хлопок и небо осветилось. Повернув голову, мы увидели огромный гриб пламени, поднимающийся в небо. Раздался сильный гул, который не прекращался. Определить расстояние на глаз было невозможно, казалось, что пожар совсем близко, километрах в двух за Чоботовским лесом. Первая реакция &#8211; позвонить 01, но ни с городского телефона, ни с сотового двух разных операторов это не удавалось &#8211; сразу короткие гудки. Может быть, очень много позвонило в тот момент, может быть, уже какие-то кабели были повреждены на месте пожара. Сегодня я уже знаю, где место пожара, и что пожарная часть располагается буквально в 200 метрах, звонить было вряд ли полезно &#8211; все знали о произошедшем. Но если бы, не дай Бог, был где-то еще пожар, как дозвониться? Мне кажется, что телефоны оперативных служб должны быть организованы так, чтобы возможность дозвониться оставалась.</p>

<p>Через еще несколько минут возник вакуум информации, где горит? Что горит? По виду столба пламени можно было догадаться, что горит газ, но знать наверняка вряд ли было возможно. Есть ли угроза для нас, для нашего дома? По моим первым предположениям, что горит прямо за лесом, в случае, если огонь перекинется на лес, нам надо было будить нашу маленькую дочку и уезжать отсюда. По телевидению в новостных каналах тишина, никакой информации. Помогает поиск по блогам, уже через несколько минут первая информация &#8211; пожар где-то внутри МКАД, в районе Мичуринского проспекта. Становится чуточку легче. Спасибо вам, блоггеры!</p>

<p>Еще через несколько минут около часа ночи прекращает работать интернет (Стрим), с ним погибает и телевидение №1 (Стрим-ТВ).  Тут же умирает и телевидение №2 (кабельное в доме). Интересно, как они связаны? По радио тишина. Остается последний источник информации, сотовый телефон, ленты новостных агентств. Через их сайты удается узнать какую-то информацию, действительно пожар около поста ДПС на пересечении Мичуринского и МКАД. Всё, можно спать.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/05/11/fire-in-moscow-no-information/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ffmpeg, ошибка вида lame: output buffer too small</title>
		<link>http://www.smira.ru/2009/04/27/ffmpeg-lame-output-buffer-too-small/</link>
		<comments>http://www.smira.ru/2009/04/27/ffmpeg-lame-output-buffer-too-small/#comments</comments>
		<pubDate>Mon, 27 Apr 2009 12:50:57 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Разработка]]></category>
		<category><![CDATA[ffmpeg]]></category>
		<category><![CDATA[lame]]></category>
		<category><![CDATA[mp3]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=368</guid>
		<description><![CDATA[Для истории, проблема один-в-один как описана в http://drupal.org/node/376012.

FFmpeg, собранный с libmp3lame (для кодирования mp3) версии 3.98, сообщает следующее:

lame: output buffer too small (buffer index: 9404, free bytes: 388)


Он считает это фатальной ошибкой, завершается:

Audio encoding failed


Лечится downgrade libmp3lame до версии 3.97.

Проверено на ArchLinux.
]]></description>
			<content:encoded><![CDATA[<p>Для истории, проблема один-в-один как описана в <a href="http://drupal.org/node/376012">http://drupal.org/node/376012</a>.</p>

<p>FFmpeg, собранный с libmp3lame (для кодирования mp3) версии 3.98, сообщает следующее:</p>

<pre><code>lame: output buffer too small (buffer index: 9404, free bytes: 388)
</code></pre>

<p>Он считает это фатальной ошибкой, завершается:</p>

<pre><code>Audio encoding failed
</code></pre>

<p>Лечится downgrade libmp3lame до версии 3.97.</p>

<p>Проверено на ArchLinux.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/04/27/ffmpeg-lame-output-buffer-too-small/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Отдых в ВКС-Кантри (Владимирская область)</title>
		<link>http://www.smira.ru/2009/04/19/vks-country/</link>
		<comments>http://www.smira.ru/2009/04/19/vks-country/#comments</comments>
		<pubDate>Sun, 19 Apr 2009 14:33:22 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Личное]]></category>
		<category><![CDATA[владимир]]></category>
		<category><![CDATA[отдых]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=350</guid>
		<description><![CDATA[Мы провели 5 дней в доме отдыха ВКС-Кантри. В качестве варианта для отдыха рассматривали окрестности Костромы, Ярославля, Липецка, Воронежа, Курска, Орла. В район Костромы-Ярославля не поехали из-за Рыбинского водохранилища, которое поздно освобождается ото льда и сильно понижает температуру в регионе, а температура этой весной и так низкая. В Липецке, Воронеже не нашлось дома отдыха, который [...]]]></description>
			<content:encoded><![CDATA[<p>Мы провели 5 дней в доме отдыха <a href="http://www.95km.ru/">ВКС-Кантри</a>. В качестве варианта для отдыха рассматривали окрестности Костромы, Ярославля, Липецка, Воронежа, Курска, Орла. В район Костромы-Ярославля не поехали из-за Рыбинского водохранилища, которое поздно освобождается ото льда и сильно понижает температуру в регионе, а температура этой весной и так низкая. В Липецке, Воронеже не нашлось дома отдыха, который бы принял нас с 10-месячной дочкой. Вывод после поисков: очень сложно найти координаты домов отдыха, чаще натыкаешься на сайты-&raquo;ловушки&raquo; туристических компаний, настоящие телефоны найти сложно. Даже если телефон найден, надеяться, что кто-то ответит не стоит. Такое ощущение, что им клиенты не нужны. Практически случайно наткнулись на <a href="http://www.95km.ru/">ВКС-Кантри</a>, подкупило наличие достаточно разумного собственного сайта, где есть всё, начиная от цен на номера, заканчивая фотографиями практически каждого номера. Без проблем принимают с маленькими детьми, есть детские кроватки, стульчики в столовой. По телефону администратор отвечала на все вопросы около одиннадцати часов вечера. Забронировали номер с понедельника на неделю.</p>

<p>Из плюсов: приветливый персонал, желание помочь и создать все необходимые условия. Отличная, вкусная еда &#8211; шведский стол три раза в день. Разумные цены на номера, дополнительные услуги &#8211; прокат, шашлыки. Чистые, приятные номера &#8211; полное ощущение, что мы были практически первыми, кто жил в этом номере.</p>

<p><a href="http://picasaweb.google.com/lh/photo/ZF0WOtW8D_5EjkkTKzPeWw?feat=embedwebsite"><img src="http://lh4.ggpht.com/_PYyUBx37UPg/SeoWsgRTAKI/AAAAAAAACwY/rW8awKz5NhQ/s288/img_6961.jpg" /></a></p>

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

<p>Территории как таковой почти нет: дорожки почти отсутствуют, леса рядом нет, дом отдых расположен рядом с рекой Киржач, петляющей в равнинной местности. На берегу Киржача устроили шашлыки:</p>

<p><a href="http://picasaweb.google.com/lh/photo/_LyBQZAeB55s_mdYuXfcsg?feat=embedwebsite"><img src="http://lh6.ggpht.com/_PYyUBx37UPg/SeoWxZIuY5I/AAAAAAAACxI/Vxd5RLGCrAk/s288/img_6994.jpg" /></a></p>

<p>Для желающих поехать в помощь трек пути от M7 к дому отдыха:</p>

<p><div  style="text-align: left;"  class="xmlgmdiv" id="xmlgmdiv_3"><iframe class="xmlgm" id="xmlgm_3" src="http://www.smira.ru/wp-content/plugins/xml-google-maps/xmlgooglemaps_show.php?gpxid=3" style="border: 0px; width: 664px; height: 400px;" name="Google_Gpx_Maps" frameborder="0"></iframe></div><a href="http://www.smira.ru/wp-content/uploads/2009/04/wayto1.gpx">Скачать трек</a></p>

<p>В общем и целом нам очень понравилось, немного подвела погода, уехали на два дня пораньше, потому что в снег и дождь делать особо нечего. Опишу пару поездок по окрестностям, которые можно осуществить, стартуя из <a href="http://www.95km.ru/">ВКС-Кантри</a>.</p>

<h2>Суздаль</h2>

<p>Доехать до Суздаля очень просто: прямо по M7 до Владимир, заезжаем во Владимир (не на объездную), далее минуя центр налево по северной границе Владимира идет некое подобие владимирской кольцевой автодороги, с которой поворот на Суздаль, до Суздаля ведет замечательная двухполосная дорога, прыгающая вниз-вверх по холмам.</p>

<p>Сам Суздаль производит впечатление странное. Полуразрушенный, загаженный, живущий как будто бы производством медовухи и приемом туристов. Город замер и заморозился лет на сто. Но почему же столько мусора? Почему все разваливается, заборы заваливаются? Убрать мусор, всё подкрасить, помыть, положить новый асфальт. Было бы фантастическое для туристов место. А так &#8211; стыдно.</p>

<p><a href="http://picasaweb.google.com/lh/photo/_MZiIcDufQU-JokW_rm_nA?feat=embedwebsite"><img src="http://lh4.ggpht.com/_PYyUBx37UPg/SeoW5mPDw3I/AAAAAAAACyw/owa5xQHvPFM/s288/img_7060.jpg" /></a></p>

<h2>Село Воскресение, место гибели Гагарина, Киржач, Андреевское</h2>

<p>Еще один день, галопом по окрестностям. Трек с точками ниже. Село Воскресение &#8211; сегодня скорее дачный поселок. От дороги Покров-Киржач к селу ведет замечательная лесная грунтовая дорога, не поехали по ней дальше, но, судя по всему, она достаточно длинная. В самом селе восстанавливают церковь, была она очень красивая:</p>

<p><a href="http://picasaweb.google.com/lh/photo/RyfPkYI7_15YxnLCIw-ESw?feat=embedwebsite"><img src="http://lh6.ggpht.com/_PYyUBx37UPg/SeoXHQVCvZI/AAAAAAAAC0w/FAqBFprQwEg/s288/img_7104.jpg" /></a></p>

<p>Дальше по дороге Покров-Киржач село Новоселово, перед началом села необозначенный в этом направлении движения поворот к монументу на месте гибели <a href="http://ru.wikipedia.org/wiki/Гагарин,_Юрий">Гагарина</a> и Серегина. Сам памятник ухоженный, как-то охраняется. Грустное место, самолет разбился в лесу.</p>

<p><a href="http://picasaweb.google.com/lh/photo/r0tFCCDeWMjhwUFHKzKB0w?feat=embedwebsite"><img src="http://lh6.ggpht.com/_PYyUBx37UPg/SeoXLPEvAQI/AAAAAAAAC1I/8ivYbv-LoYU/s288/img_7128.jpg" /></a></p>

<p>Дорога Покров-Киржач неплохого качества, малозагруженная, петляет по лесам и холмам, сплошное удовольствие! Сам Киржач &#8211; небольшой городок. В нем есть действующий женский монастырь, несколько церквей. Хороший ресторан &laquo;Шерна&raquo;. В нем чувствуется атмосфера маленького провинциального города, нет ощущения &laquo;мертвого&raquo; города, как в Суздале.</p>

<p>Последний пункт нашего путешествия &#8211; усадьба &laquo;Андреевское&raquo; Воронцовых и Воронцовых-Дашковых, XVIII век. Для этого возвращаемся на M7, едем до поворота на &laquo;детский санаторий&raquo;, далее по дороге еще около 7 км, на дороге незаметные четыре лежачих полицейских, лучше ехать за местной машиной. В усадьбе сегодня располагается детский санаторий &laquo;Болдино&raquo;. Сама усадьба сохранилась, видимо, практически полностью, церковь, приусадебный парк. Здание обветшало, требуется ремонт или реставрация, еще остался дух дворянской усадьбы, хотя и совсем немного его осталось.</p>

<p><a href="http://picasaweb.google.com/lh/photo/j4ZAPcv8RLv7lu4JifVaGw?feat=embedwebsite"><img src="http://lh4.ggpht.com/_PYyUBx37UPg/SeoXQBgRTNI/AAAAAAAAC18/dxwN9IJIrfE/s288/img_7156.jpg" /></a></p>

<p><div  style="text-align: left;"  class="xmlgmdiv" id="xmlgmdiv_4"><iframe class="xmlgm" id="xmlgm_4" src="http://www.smira.ru/wp-content/plugins/xml-google-maps/xmlgooglemaps_show.php?gpxid=4" style="border: 0px; width: 664px; height: 400px;" name="Google_Gpx_Maps" frameborder="0"></iframe></div><a href="http://www.smira.ru/wp-content/uploads/2009/04/andreevskoye.gpx">Скачать трек</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/04/19/vks-country/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Продам  свою Opel Astra 1.6 автомат Cosmo (2006 г.)</title>
		<link>http://www.smira.ru/2009/03/18/opel-astra-16-cosmo-2006/</link>
		<comments>http://www.smira.ru/2009/03/18/opel-astra-16-cosmo-2006/#comments</comments>
		<pubDate>Wed, 18 Mar 2009 05:42:59 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Личное]]></category>
		<category><![CDATA[astra]]></category>
		<category><![CDATA[opel]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=340</guid>
		<description><![CDATA[Продам свою машинку (купил универсал в связи с увеличением семьи  .

Opel Astra 1.6 (105 л.с.) хэтчбек 5-дверный, цвет метро (темно-синий),
2006 г. выпуска (август), пробег 71 тыс. км.

Комлектация Cosmo, биксенон, парктроник, регулировка водительского
сиденья в 6-и направлениях, подлокотник, 17&#8243; колеса, система
QuickHeat, задние электростеклоподъемники, салон кожа+ткань, магнитола
MP3, сигнализация, европейская сборка. (Всё заводское, заказывалась
комплектация).

Я &#8211; единственный владелец, все [...]]]></description>
			<content:encoded><![CDATA[<p>Продам свою машинку (купил универсал в связи с увеличением семьи <img src='http://www.smira.ru/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> .</p>

<p>Opel Astra 1.6 (105 л.с.) хэтчбек 5-дверный, цвет метро (темно-синий),
2006 г. выпуска (август), пробег 71 тыс. км.</p>

<p>Комлектация Cosmo, биксенон, парктроник, регулировка водительского
сиденья в 6-и направлениях, подлокотник, 17&#8243; колеса, система
QuickHeat, задние электростеклоподъемники, салон кожа+ткань, магнитола
MP3, сигнализация, европейская сборка. (Всё заводское, заказывалась
комплектация).</p>

<p>Я &#8211; единственный владелец, все ТО у официального дилера (Genser), машина не кредитная.</p>

<p>Из дефектов: скол краски на капоте (ржи нет, требуется покраска
летом), маленькая царапина на панели (в салоне), пара мелких дефектов
на сиденьях (курил <img src='http://www.smira.ru/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> .</p>

<p>Старая фотка машины, лучше пока фоток нет, на
фотке капот &laquo;задран&raquo;, этого уже давно нет <img src='http://www.smira.ru/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>

<p><a href="http://www.smira.ru/wp-content/uploads/2009/03/astra.jpg"><img src="http://www.smira.ru/wp-content/uploads/2009/03/astra-300x193.jpg" alt="Астра" title="Астра" width="300" height="193" class="alignnone size-medium wp-image-341" /></a></p>

<p><strong>SOLD! Продано&#8230; Прощай, Марта! Надеюсь, ты достанешься хорошему человеку&#8230;</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/03/18/opel-astra-16-cosmo-2006/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>СпамоБорец: релиз 0.2.0</title>
		<link>http://www.smira.ru/2009/02/27/spamfighter-dot-two-release/</link>
		<comments>http://www.smira.ru/2009/02/27/spamfighter-dot-two-release/#comments</comments>
		<pubDate>Fri, 27 Feb 2009 10:13:11 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[Twisted]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[СпамоБорец]]></category>
		<category><![CDATA[open-source]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[разработка]]></category>
		<category><![CDATA[спамоборец]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=327</guid>
		<description><![CDATA[СпамоБорец &#8211; веб-сервис, предоставляющий функции по классификации произвольных текстовых сообщений, и, в частности, выделения спама из общего потока сообщений.



В качестве сообщений могут рассматриваться, например, следующие виды общения, которые сегодня есть в социальных сетях (и веб-сайтах, имеющих элементы социальной сети):


личные сообщения;
чаты;
комментарии к произвольным объектам;
девизы, сообщения &#171;о себе&#187; и т.п. на страницах профиля пользователя;
письма в службу поддержки. [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://spam-fighter.ru/" title="СпамоБорец">СпамоБорец</a> &#8211; веб-сервис, предоставляющий функции по классификации произвольных текстовых сообщений, и, в частности, выделения спама из общего потока сообщений.</p>

<p><img src="http://www.smira.ru/wp-content/uploads/2009/02/security-guard.png" alt="СпамоБорец" title="СпамоБорец" width="256" height="256" class="alignnone size-full wp-image-328" /></p>

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

<ul>
<li>личные сообщения;</li>
<li>чаты;</li>
<li>комментарии к произвольным объектам;</li>
<li>девизы, сообщения &laquo;о себе&raquo; и т.п. на страницах профиля пользователя;</li>
<li>письма в службу поддержки. </li>
</ul>

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

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

<p>Данный релиз является первым публичным релизом проекта <a href="http://spam-fighter.ru/" title="СпамоБорец">СпамоБорец</a>. Качество и полнота функционала соответствует на сегодняшний день альфа-версии. </p>

<p>На сегодня доступен следующий функционал:</p>

<ul>
<li>веб-сервис с JSON-RPC и XML-RPC API (<a href="http://spam-fighter.ru/docs/api/index.html">описание API</a>);</li>
<li>создание и запуск из <a href="http://spam-fighter.ru/docs/guide/running.html">отдельного окружения</a>;</li>
<li>firewall из правил анализа сообщений;</li>
<li>гибкая настройка содержимого сообщений;</li>
<li>анализ текста сообщений на флуд;</li>
<li>анализ частоты сообщений по тексту и атрибутам автора: логин, IP и т.п.;</li>
<li>простая Байесовская модель анализа текста (тестовая);</li>
<li>валидация сообщений по длине, наличию атрибутов, регулярному выражению;</li>
<li>логирование сообщений, проходящих через сервер;</li>
<li>система администрирования и настройки (qooxdoo, см. <a href="http://spam-fighter.ru/wiki/rus/Screenshots">скриншоты</a>);</li>
<li>реализация клиентов API на PHP и тестовый модуль для Wordpress.</li>
</ul>

<h2>Как установить</h2>

<p>Нужен python и setuptools:</p>

<pre><code>easy_install spamfighter
</code></pre>

<p>После этого выбираем каталог для окружения (например, <code>~/spamfighter</code>) и запускаем:</p>

<pre><code>spamfighter-create ~/spamfighter
</code></pre>

<p>Далее следуем инструкциям на экране. Более подробную информация об установке можно найти в <a href="http://spam-fighter.ru/docs/guide/setup.html">документации по СпамоБорцу</a>. </p>

<h2>Что дальше?</h2>

<p>Этот пост &#8211; уведомление о выходе новой версии, за ним последует более подробный рассказ &laquo;для чего это нужно&raquo; и &laquo;как это использовать&raquo;. Надеемся на позитивный feedback и участие в жизни <a href="http://spam-fighter.ru/" title="СпамоБорец">проекта</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/02/27/spamfighter-dot-two-release/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Deferred: все подробности</title>
		<link>http://www.smira.ru/2009/02/24/more-about-deferred/</link>
		<comments>http://www.smira.ru/2009/02/24/more-about-deferred/#comments</comments>
		<pubDate>Tue, 24 Feb 2009 06:14:29 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[Twisted]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[deferred]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[twisted]]></category>
		<category><![CDATA[разработка]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=315</guid>
		<description><![CDATA[В предыдущей статье были описаны основные принципы работы Deferred и его применение в асинхронном программировании.
Сегодня мы постараемся рассмотреть в деталях функционирование Deferred и примеры его использования.

Итак, Deferred &#8211; это отложенный результат, результат выполнения, который станет известен через некоторое время. 
Результатом, хранящимся в Deferred, может быть произвольное значение (успешное выполнение) или ошибка (исключение),
которое произошло в процессе [...]]]></description>
			<content:encoded><![CDATA[<p>В <a href="http://www.smira.ru/2009/02/10/deferred-async-programming/" title="Асинхронное программирование: концепция Deferred">предыдущей статье</a> были описаны основные принципы работы Deferred и его применение в асинхронном программировании.
Сегодня мы постараемся рассмотреть в деталях функционирование Deferred и примеры его использования.</p>

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

<p>Рассмотрим поподробнее цепочку обработчиков:</p>

<p><img src="http://www.smira.ru/wp-content/uploads/2009/02/deferred1.png" alt="Deferred" title="Deferred" width="263" height="332" class="size-full wp-image-319" /></p>

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

<p>Обработчики располагаются по &laquo;слоям&raquo; или уровням, выполнение происходит четко по уровням сверху вниз. При этом на каждом
уровне расположены обработчики callback и errback, один из элементов может отсутствовать. На каждом уровне может
быть выполнен либо callback, либо errback, но не оба. Выполнение обработчиков происходит только один раз, повторного
входа быть не может. </p>

<p>Функции-обработчики callback являются функциями с одним аргументом &#8211; результатом выполнения:</p>


<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> callback<span style="color: black;">&#40;</span>result<span style="color: black;">&#41;</span>:
    ...</pre></div></div>


<p>Функции-обработчики errback принимают в качестве параметра исключение, &laquo;завернутое&raquo; в класс <a href="http://twistedmatrix.com/documents/current/api/twisted.python.failure.Failure.html" title="API reference Failure">Failure</a>:</p>


<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> errback<span style="color: black;">&#40;</span>failure<span style="color: black;">&#41;</span>:
    ...</pre></div></div>


<p>Выполнение Deferred начинается с того, что в Deferred появляется результат: успешное выполнение или исключение. 
В зависимости от результата выбирается соответствующая ветвь обработчиков: callback или errback. После этого происходит поиск
ближайшего уровня обработчиков, в котором существует соответствующий обработчик. В нашем примере на рисунке
был получен успешный результат выполнения и результат был передан обработчику <code>callback1</code>.</p>

<p>Дальнейшее выполнение приводит к вызову обработчиков на нижележащих уровнях. Если callback или errback завершается
возвратом значения, которое не является Failure, выполнение считается успешным и полученный результат отправляется
на вход callback-обработчику на следующем уровне. Если же в процессе выполнения обработчика было выкинуто исключение
или возвращено значение типа Failure, управление будет передано errback на следующем уровне, который получит
исключение в качестве параметра.</p>

<p>В нашем примере обработчик <code>callback1</code> выполнился успешно, его результат был передан обработчику <code>callback2</code>, в котором
было выкинуто исключение, которое привело к переходу к цепочке errback-обработчиков, на третьем уровне обработчик
errback отсутствовал и исключение было передано в <code>errback4</code>, который обработал исключение, вернул успешный результат
выполнения, который теперь является результатом Deferred, однако больше обработчиков нет. Если к Deferred будет
добавлен еще один уровень обработчиков, они смогут получить доступ к этому результату.</p>

<p>Как и все другие объекты Python, объект Deferred живет до тех пор, пока на него есть ссылки из других объектов. Обычно
объект, вернувший Deferred, сохраняет его, т.к. ему надо по завершении асинхронной операции передать в Deferred полученный
результат. Чаще всего другие участники (добавляющие обработчики событий) не сохраняют ссылки на Deferred, таким образом
объект Deferred будет уничтожен по окончании цепочки обработчиков. Если происходит уничтожение Deferred, в котором
осталось необработанное исключение (выполнение завершилось исключением и больше обработчиков нет), на экран печатается
отладочное сообщение с traceback исключения. Эта ситуация аналогична &laquo;выскакиванию&raquo; необработанного исключения на верхний
уровень в обычной синхронной программе.</p>

<h2>Deferred в квадрате</h2>

<p>Возвращаемым значением callback и errback может быть также другой Deferred, тогда выполнение цепочки обработчиков
текущего Deferred приостанавливается до окончания цепочки обработчиков вложенного Deferred.</p>

<p><img src="http://www.smira.ru/wp-content/uploads/2009/02/deferred2.png" alt="deferred-in-deferred" title="deferred-in-deferred" width="563" height="384" class="alignnone size-full wp-image-320" /></p>

<p>В приведенном на рисунке примере обработчик callback2 возвращает не обычный результат, а другой Deferred &#8211; Deferred2. При
этом выполнение текущего Deferred приостанавливается до получения результата выполнения Deferred2. Результат Deferred2 &#8211; 
успешный или исключение &#8211; становится результатом, передаваемым на следующий уровень обработчиков первого Deferred. В нашем примере
Deferred2 завершился с исключением, которое будет передано на вход обработчику <code>errback2</code> первого Deferred.</p>

<h2>Обработка исключений в errback</h2>

<p>Каждый обработчик исключений errback является аналогом блока try..except, а блок except обычно реагирует на некоторый тип
исключений, такое поведение очень просто воспроизвести с помощью <a href="http://twistedmatrix.com/documents/current/api/twisted.python.failure.Failure.html" title="API reference Failure">Failure</a>:</p>


<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> errback<span style="color: black;">&#40;</span>failure<span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">&quot;&quot;&quot;
    @param failure: ошибка (исключение), завернутое в failure
    @type failure: C{Failure}
    &quot;&quot;&quot;</span>
    failure.<span style="color: black;">trap</span><span style="color: black;">&#40;</span><span style="color: #008000;">KeyError</span><span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">&quot;Got key error: %r&quot;</span> <span style="color: #66cc66;">%</span> failure.<span style="color: black;">value</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #ff4500;">0</span></pre></div></div>


<p>Метод <code>trap</code> класса <code>Failure</code> проверяет, является ли завернутое в него исключение наследником или непосредственно классом
<code>KeyError</code>. Если это не так, оригинальное исключение снова выбрасывается, прерывая выполнение текущего errback, что приведет
к передаче управления следующему errback в цепочке обработчиков, что имитирует поведение блока <code>except</code> при несоответствии
типа исключения (управление передается следующему блоку). В свойстве <code>value</code> хранится оригинальное исключение, которое
можно использовать для получения дополнительной информации об ошибке.</p>

<p>Необходимо обратить внимание, что обработчик errback должен завершиться одним из двух способов:</p>

<ol>
<li>Вернуть некоторое значение, которое станет входным значением следующего callback, что по смыслу означает, что
исключение было обработано.</li>
<li>Выкинуть оригинальное или новое исключение &#8211; исключение не было обработано или было перевыброшено новое исключение,
цепочка обработчиков errback продолжается.</li>
</ol>

<p>Существует и третий вариант &#8211; вернуть Deferred, тогда дальнейшее выполнение обработчиков будет зависеть от результата
Deferred.</p>

<p>В нашем примере мы исключение обработали и передали в качестве результата 0 (например, отсутствие некоторого ключа эквивалентно
его нулевому значению).</p>

<h2>Готовимся к асинхронности заранее</h2>

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


<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> f<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #ff4500;">33</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> g<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">return</span> f<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span><span style="color: #66cc66;">*</span><span style="color: #ff4500;">2</span></pre></div></div>


<p>Если по каким-то причинам функция <code>f</code> не сможет вернуть результат сразу, она начнет возвращать Deferred:</p>


<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> f<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">return</span> Deferred<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>.<span style="color: black;">callback</span><span style="color: black;">&#40;</span><span style="color: #ff4500;">33</span><span style="color: black;">&#41;</span></pre></div></div>


<p>Но теперь и функция <code>g</code> вынуждена возращать Deferred, зацепляясь за цепочку обработчиков:</p>


<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> g<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">return</span> f<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>.<span style="color: black;">addCallback</span><span style="color: black;">&#40;</span><span style="color: #ff7700;font-weight:bold;">lambda</span> result: result<span style="color: #66cc66;">*</span><span style="color: #ff4500;">2</span><span style="color: black;">&#41;</span></pre></div></div>


<p>Аналогичная схема &laquo;превращения&raquo; происходит и с реальными функциями: мы получаем результаты в виде Deferred от нижележащих в дереве
вызовов функции, навешиваем на их Deferred свои обработчики callback, которые соответствуют старому, синхронному коду нашей функции, если у 
нас были обработчики исключений, добавляются и обработчики errback.</p>

<p>На практике лучше сначала выявить те места кода, которые будут асинхронными и будут использовать Deferred, чем переделывать синхронный
код в асинхронный. Асинхронный код начинается с тех вызовов, которые не могут построить результат непосредственно:</p>

<ul>
<li>сетевой ввод-вывод;</li>
<li>обращение к сетевым сервисам СУБД, memcached;</li>
<li>удаленные вызовы RPC;</li>
<li>операции, выполнение которых будет выделено в нить в модели Worker и т.п.</li>
</ul>

<p>В процессе написания приложения часто ясно, что в данной точке будет асинхронное обращение, но его еще нет (не реализован интерфейс с СУБД, 
например). В такой ситуации можно использовать функции <code>defer.success</code> или <code>defer.fail</code> для создания Deferred, в котором уже содержится
результат. Вот как можно короче переписать функцию <code>f</code>:</p>


<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">from</span> twisted.<span style="color: black;">internet</span> <span style="color: #ff7700;font-weight:bold;">import</span> defer
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> f<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">return</span> defer.<span style="color: black;">success</span><span style="color: black;">&#40;</span><span style="color: #ff4500;">33</span><span style="color: black;">&#41;</span></pre></div></div>


<p>Если мы не знаем, будет ли вызываемая функция возвращать результат синхронно или вернет Deferred, и не хотим зависеть от её
поведения, мы можем завернуть обращение к ней в <code>defer.maybeDeferred</code>, которое любой вариант сделает эквивалентным вызову
Deferred:</p>


<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">from</span> twisted.<span style="color: black;">internet</span> <span style="color: #ff7700;font-weight:bold;">import</span> defer
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> g<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">return</span> defer.<span style="color: black;">maybeDeferred</span><span style="color: black;">&#40;</span>f<span style="color: black;">&#41;</span>.<span style="color: black;">addCallback</span><span style="color: black;">&#40;</span><span style="color: #ff7700;font-weight:bold;">lambda</span> result: result<span style="color: #66cc66;">*</span><span style="color: #ff4500;">2</span><span style="color: black;">&#41;</span></pre></div></div>


<p>Такой вариант функции <code>g</code> будет работать как с синхронной, так и с асинхронной <code>f</code>.</p>

<h2>Вместо заключения</h2>

<p>Рассказывать о Deferred можно еще очень долго, в качестве дополнительного чтения могу опять порекомендовать
список материалов в конце <a href="http://www.smira.ru/2009/02/10/deferred-async-programming/" title="Асинхронное программирование: концепция Deferred">предыдущей статьи</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/02/24/more-about-deferred/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Асинхронное программирование: концепция Deferred</title>
		<link>http://www.smira.ru/2009/02/10/deferred-async-programming/</link>
		<comments>http://www.smira.ru/2009/02/10/deferred-async-programming/#comments</comments>
		<pubDate>Tue, 10 Feb 2009 06:49:28 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[Twisted]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[callback]]></category>
		<category><![CDATA[deferred]]></category>
		<category><![CDATA[errback]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[twisted]]></category>
		<category><![CDATA[асинхронное]]></category>
		<category><![CDATA[програмирование]]></category>
		<category><![CDATA[разработка]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=296</guid>
		<description><![CDATA[Асинхронная концепция программирования заключается в том, что результат
выполнения функции доступен не сразу же, а через некоторое время в виде
некоторого асинхронного (нарушающего обычный порядок выполнения) вызова. Зачем
такое может быть полезно?  Рассмотрим несколько примеров.

Первый пример &#8211; сетевой сервер, веб-приложение. Чаще всего как таковых
вычислений на процессоре такие приложения не выполняют. Большая часть времени
(реального, не процессорного) тратится на [...]]]></description>
			<content:encoded><![CDATA[<p>Асинхронная концепция программирования заключается в том, что результат
выполнения функции доступен не сразу же, а через некоторое время в виде
некоторого асинхронного (нарушающего обычный порядок выполнения) вызова. Зачем
такое может быть полезно?  Рассмотрим несколько примеров.</p>

<p>Первый пример &#8211; сетевой сервер, веб-приложение. Чаще всего как таковых
вычислений на процессоре такие приложения не выполняют. Большая часть времени
(реального, не процессорного) тратится на ввод-вывод: чтение запроса от
клиента, обращение к диску за данными, сетевые обращение к другим подсистемам
(БД, кэширующие сервера, RPC и т.п.), запись ответа клиенту. Во время этих
операций ввода-вывода процессор простаивает, его можно загрузить обработкой
запросов других клиентов.  Возможны различные способы решить эту задачу: отдельный
процесс на каждое соединение (<a href="http://httpd.apache.org/">Apache</a> mpm&#95;prefork, <a href="http://www.postgresql.org">PostgreSQL</a>, <a href="http://www.php.net">PHP</a> FastCGI),
отдельный поток (нить) на каждое соединение или комбинированный вариант
процесс/нить (<a href="http://httpd.apache.org/">Apache</a> mpm&#95;worker, <a href="http://mysql.org">MySQL</a>). Подход с использованием процессов или
нитей перекладывает мультиплексирование процессора между обрабатываемыми
соединениями на ОС, при этом расходуется относительно много ресурсов (память,
переключения контекста и т.п.), такой вариант не подходит для обработки
большого количества одновременных соединений, но идеален для ситуации, когда
объем вычислений достаточно высок (например, в СУБД). К плюсам модели нитей и
процессов можно добавить потенциальное использование всех доступных процессоров
в многопроцессорной архитектуре.</p>

<p>Альтернативой является использование однопоточной модели с использованием
примитивов асинхронного ввода-вывода, предоставляемых ОС (select, poll, и
т.п.). При этом объем ресурсов на каждое новое обслуживаемое соединение не
такой большой (новый сокет, какие-то структуры в памяти приложения). Однако
программирование существенно усложняется, т.к. данные из сетевых сокетов
поступают некоторыми &laquo;отрывками&raquo;, причем за один цикл обработки данные поступают от разных
соединений, находящихся в разных состояниях, часть соединений могут быть
входящими от клиентов, часть &#8211; исходящими к внешним ресурсам (БД, другой сервер
и т.п.). Для упрощения разработки используются различные концепции: callback,
конечные автоматы и другие. Примеры сетевых серверов, использующих асинхронный
ввод-вывод: <a href="http://sysoev.ru/nginx/">nginx</a>, <a href="http://lighttpd.net/">lighttpd</a>, 
<a href="http://haproxy.1wt.eu/">HAProxy</a>, <a href="https://developer.skype.com/SkypeGarage/DbProjects/PgBouncer">pgBouncer</a>, и т.д. Именно при такой
однопоточной модели возникает необходимость в асинхронном программировании.
Например, мы хотим выполнить запрос в БД. С точки зрения программы выполнение
запроса &#8211; это сетевой ввод-вывод: соединение с сервером, отправка запроса,
ожидание ответа, чтение ответа сервера БД. Поэтому если мы вызываем
функцию &laquo;выполнить запрос БД&raquo;, то она сразу вернуть результат не сможет
(иначе она должна была бы заблокироваться), а вернет лишь нечто, что позволит
впоследствие получить результат запроса или, возможно, ошибку (нет соединения
с сервером, некорректный запрос и т.п.) Этим возвращаемым значением удобно
сделать именно Deferred.</p>

<p>Второй пример связан с разработкой обычных десктопных приложений. Предположим,
мы решили сделать аналог <a href="http://www.miranda-im.org/">Miranda</a> (<a href="http://qip.ru/">QIP</a>, <a href="http://mdc.ru/">MDC</a>, &#8230;), то есть свой мессенджер.
В интерфейсе программы есть контакт-лист, где можно удалить контакт. Когда
пользователь выбирает это действие, он ожидает что контакт исчезнет на экране 
и что он действительно удалится из контакт-листа. На самом деле операция
удаления из серверного контакт-листа опирается на сетевое взаимодействие
с сервером, при этом пользовательский интерфейс не должен быть заблокирован
на время выполнения этой операции, поэтому в любом случае после выполнения
операции потребуется некоторое асинхронное взаимодействие с результатом операции.
Можно использовать механизм сигналов-слотов, callback&#8217;ов или что-то еще,
но лучше всего подойдет Deferred: операция удаления из контакт-листа возвращает
Deferred, в котором обратно придет либо положительный результат (всё хорошо),
либо исключение (точная ошибка, которую надо сообщить пользователю): в случае
ошибки контакт надо восстановить контакт в контакт-листе.</p>

<p>Примеры можно приводить долго и много, теперь о том, что же такое Deferred.
Deferred &#8211; это сердце framework&#8217;а асинхронного сетевого программирования 
<a href="http://twistedmatrix.com/">Twisted</a> в Python. Это простая и стройная концепция, которая
позволяет перевести синхронное программирование в асинхронный код, не изобретая
велосипед для каждой ситуации и обеспечивая высокое качества кода.
Deferred &#8211; это просто возвращаемый результат функции, когда этот результат
неизвестен (не был получен, будет получен в другой нити и т.п.) Что мы можем
сделать с Deferred? Мы можем &laquo;подвеситься&raquo; в цепочку обработчиков, которые
будут вызваны, когда результат будет получен. При этом Deferred может нести не 
только положительный результат выполнения, но и исключения, сгенерированные
функцией или обработчиками, есть возможность исключения обработать, перевыкинуть
и т.д. Фактически, для синхронного кода есть более-менее однозначная параллель в
терминах Deferred. Для эффективной разработки с Deferred оказываются полезными
такие возможности языка программирования, как замыкания, лямбда-функци.</p>

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

<p>Приведем пример синхронного кода и его альтернативу в терминах Deferred:</p>


<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">try</span>:
    <span style="color: #808080; font-style: italic;"># Скачать по HTTP некоторую страницу</span>
    page = downloadPage<span style="color: black;">&#40;</span>url<span style="color: black;">&#41;</span>
    <span style="color: #808080; font-style: italic;"># Распечатать содержимое</span>
    <span style="color: #ff7700;font-weight:bold;">print</span> page
<span style="color: #ff7700;font-weight:bold;">except</span> HTTPError, e:
    <span style="color: #808080; font-style: italic;"># Произошла ошибка</span>
    <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">&quot;An error occured: %s&quot;</span>, e</pre></div></div>


<p>В асинхронном варианте с Deferred он был бы записан следующим образом:</p>


<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> printContents<span style="color: black;">&#40;</span>contents<span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">&quot;&quot;&quot;
    Callback, при успешном получении текста страницы,
    распечатываем её содержимое.
    &quot;&quot;&quot;</span>
    <span style="color: #ff7700;font-weight:bold;">print</span> contents
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> handleError<span style="color: black;">&#40;</span>failure<span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">&quot;&quot;&quot;
    Errback (обработчик ошибок), просто распечатываем текст ошибки.
    &quot;&quot;&quot;</span>
&nbsp;
    <span style="color: #808080; font-style: italic;"># Мы готовы обработать только HTTPError, остальные исключения</span>
    <span style="color: #808080; font-style: italic;"># &quot;проваливаются&quot; ниже.</span>
    failure.<span style="color: black;">trap</span><span style="color: black;">&#40;</span>HTTPError<span style="color: black;">&#41;</span>
    <span style="color: #808080; font-style: italic;"># Распечатываем само исключение</span>
    <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">&quot;An error occured: %s&quot;</span>, failure
&nbsp;
<span style="color: #808080; font-style: italic;"># Теперь функция выполняется асинхронно и вместо непосредственного</span>
<span style="color: #808080; font-style: italic;"># результата мы получаем Deferred</span>
deferred = downloadPage<span style="color: black;">&#40;</span>url<span style="color: black;">&#41;</span>
<span style="color: #808080; font-style: italic;"># Навешиваем на Deferred-объект обработчики успешных результатов</span>
<span style="color: #808080; font-style: italic;"># и ошибок (callback, errback).</span>
deferred.<span style="color: black;">addCallback</span><span style="color: black;">&#40;</span>printContents<span style="color: black;">&#41;</span>
deferred.<span style="color: black;">addErrback</span><span style="color: black;">&#40;</span>handleError<span style="color: black;">&#41;</span></pre></div></div>


<p>На практике обычно мы возвращаем Deferred из функций, которые получают
Deferred в процессе своей работы, навешиваем большое количество обработчиков,
обрабатываем исключения, некоторые исключения возвращаем через Deferred (выбрасываем
наверх). В качестве более сложного примера приведем код в асинхронном
варианте для примера атомарного счетчика из статьи про <a href="http://www.smira.ru/2009/01/21/data-structures-in-memcached-memcachedb/#atomic-counter">структуры данных в memcached</a>,
здесь мы предполагаем, что доступ к memcached как сетевому сервису идет через Deferred, т.е.
методы класса Memcache возвращают Deferred (который вернет либо результат операции, либо ошибку):</p>


<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">class</span> MCCounter<span style="color: black;">&#40;</span>MemcacheObject<span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, mc, name<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Конструктор.
&nbsp;
        @param name: имя счетчика
        @type name: C{str}
        &quot;&quot;&quot;</span>
        <span style="color: #008000;">super</span><span style="color: black;">&#40;</span>MCCounter, <span style="color: #008000;">self</span><span style="color: black;">&#41;</span>.<span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span>mc<span style="color: black;">&#41;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">key</span> = <span style="color: #483d8b;">'counter'</span> + name
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> increment<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, value=<span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Увеличить значение счетчика на указанное значение.
&nbsp;
        @param value: инкремент
        @type value: C{int}
        @return: Deferred, результат операции
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">def</span> tryAdd<span style="color: black;">&#40;</span>failure<span style="color: black;">&#41;</span>:
            <span style="color: #808080; font-style: italic;"># Обрабатываем только KeyError, всё остальное &quot;вывалится&quot;</span>
            <span style="color: #808080; font-style: italic;"># ниже</span>
            failure.<span style="color: black;">trap</span><span style="color: black;">&#40;</span><span style="color: #008000;">KeyError</span><span style="color: black;">&#41;</span>
&nbsp;
            <span style="color: #808080; font-style: italic;"># Пытаемся создать ключ, если раз его еще нет</span>
            d = <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">add</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span>, value, <span style="color: #ff4500;">0</span><span style="color: black;">&#41;</span>
            <span style="color: #808080; font-style: italic;"># Если вдруг кто-то еще создаст ключ раньше нас,</span>
            <span style="color: #808080; font-style: italic;"># мы это обработаем</span>
            d.<span style="color: black;">addErrback</span><span style="color: black;">&#40;</span>tryIncr<span style="color: black;">&#41;</span>
            <span style="color: #808080; font-style: italic;"># Возвращаем Deferred, он &quot;вклеивается&quot; в цепочку</span>
            <span style="color: #808080; font-style: italic;"># Deferred, в контексте которого мы выполняемся</span>
            <span style="color: #ff7700;font-weight:bold;">return</span> d
&nbsp;
        <span style="color: #ff7700;font-weight:bold;">def</span> tryIncr<span style="color: black;">&#40;</span>failure<span style="color: black;">&#41;</span>:
            <span style="color: #808080; font-style: italic;"># Всё аналогично функции tryAdd</span>
            failure.<span style="color: black;">trap</span><span style="color: black;">&#40;</span><span style="color: #008000;">KeyError</span><span style="color: black;">&#41;</span>
&nbsp;
            d = <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">incr</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span>, value<span style="color: black;">&#41;</span>
            d.<span style="color: black;">addErrback</span><span style="color: black;">&#40;</span>tryAdd<span style="color: black;">&#41;</span>
            <span style="color: #ff7700;font-weight:bold;">return</span> d
&nbsp;
        <span style="color: #808080; font-style: italic;"># Пытаемся выполнить инкремент, получаем Deferred</span>
        d = <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">incr</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span>, value<span style="color: black;">&#41;</span>
        <span style="color: #808080; font-style: italic;"># Обрабатываем ошибку</span>
        d.<span style="color: black;">addErrback</span><span style="color: black;">&#40;</span>tryAdd<span style="color: black;">&#41;</span>
        <span style="color: #808080; font-style: italic;"># Возвращаем Deferred вызывающему коду, он может тем самым:</span>
        <span style="color: #808080; font-style: italic;">#  а) узнать, когда операция действительно завершится</span>
        <span style="color: #808080; font-style: italic;">#  б) обработать необработанные нами ошибки (например, разрыв соединения)</span>
        <span style="color: #ff7700;font-weight:bold;">return</span> d
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> value<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Получить значение счетчика.
&nbsp;
        @return: текущее значение счетчика
        @rtype: C{int}
        @return: Deferred, значение счетчика
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">def</span> handleKeyError<span style="color: black;">&#40;</span>failure<span style="color: black;">&#41;</span>:
            <span style="color: #808080; font-style: italic;"># Обрабатываем только KeyError</span>
            failure.<span style="color: black;">trap</span><span style="color: black;">&#40;</span><span style="color: #008000;">KeyError</span><span style="color: black;">&#41;</span>
&nbsp;
            <span style="color: #808080; font-style: italic;"># Ключа нет - возвращаем 0, он станет результатом</span>
            <span style="color: #808080; font-style: italic;"># вышележащего Deferred</span>
            <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #ff4500;">0</span>
&nbsp;
        <span style="color: #808080; font-style: italic;"># Пытаемся получить значение ключа</span>
        d = <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">get</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span><span style="color: black;">&#41;</span>
        <span style="color: #808080; font-style: italic;"># Будем обрабатывать ошибку отсутствия ключа</span>
        d.<span style="color: black;">addErrback</span><span style="color: black;">&#40;</span>handleKeyError<span style="color: black;">&#41;</span>
        <span style="color: #808080; font-style: italic;"># Возвращаем Deferred, наверх там можно будет повеситься</span>
        <span style="color: #808080; font-style: italic;"># на его callback и получить искомое значение счетчика</span>
        <span style="color: #ff7700;font-weight:bold;">return</span> d</pre></div></div>


<p>На практике приведенный выше код можно написать &laquo;короче&raquo;, объединяя часто используемые операции, например:</p>


<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;">    <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">get</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span><span style="color: black;">&#41;</span>.<span style="color: black;">addErrback</span><span style="color: black;">&#40;</span>handleKeyError<span style="color: black;">&#41;</span></pre></div></div>


<p>Практически для каждой конструкции синхронного кода можно найти аналог в асинхронной концепции с Deferred:</p>

<ul>
<li>последовательности синхронных операторов соответствует цепочка callback с асинхронными вызовами;</li>
<li>вызову одной подпрграммы с вводом-выводом из другой соответствует возврат Deferred из Deferred (ветвление Deferred);</li>
<li>глубокой цепочки вложенности, распространению исключений по стеку соответствует цепочка функций, возвращающие друг другу
Deferred;</li>
<li>блокам try..except соответствуют обработчики ошибок (errback), которые могут &laquo;пробрасывать&raquo; исключения дальше,
любое исключение в callback переводит выполнение в errback;</li>
<li>для &laquo;параллельного&raquo; выполнения асинхронных операций есть <code>DeferredList</code>.</li>
</ul>

<p>Нити часто применяются в асинхронных программах для осуществления вычислительных процедур, осуществления блокирующегося 
ввода-вывода (когда не существует асинхронного аналога). Всё это легко моделируется в простой модели &#8216;worker&#8217;, тогда 
нет необходимости при грамотной архитектуре в явной синхронизации, при этом всё элегантно включается в общий поток
вычислений с помощью Deferred:</p>


<div class="wp_syntax"><div class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> doCalculation<span style="color: black;">&#40;</span>a, b<span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">&quot;&quot;&quot;
    В этой функции осуществляются вычисления, синхронные операции ввода-вывода,
    не затрагивающие основной поток.
    &quot;&quot;&quot;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">return</span> a/b
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> printResult<span style="color: black;">&#40;</span>result<span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">print</span> result
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> handleDivisionByZero<span style="color: black;">&#40;</span>failure<span style="color: black;">&#41;</span>:
    failure.<span style="color: black;">trap</span><span style="color: black;">&#40;</span><span style="color: #008000;">ZeroDivisionError</span><span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">print</span> <span style="color: #483d8b;">&quot;Ooops! Division by zero!&quot;</span>
&nbsp;
deferToThread<span style="color: black;">&#40;</span>doCalculation, <span style="color: #ff4500;">3</span>, <span style="color: #ff4500;">2</span><span style="color: black;">&#41;</span>.<span style="color: black;">addCallback</span><span style="color: black;">&#40;</span>printResult<span style="color: black;">&#41;</span>.<span style="color: black;">addCallback</span><span style="color: black;">&#40;</span>
    <span style="color: #ff7700;font-weight:bold;">lambda</span> _: deferToThread<span style="color: black;">&#40;</span>doCalculation, <span style="color: #ff4500;">3</span>, <span style="color: #ff4500;">0</span><span style="color: black;">&#41;</span>.<span style="color: black;">addErrback</span><span style="color: black;">&#40;</span>handleDivisionByZero<span style="color: black;">&#41;</span><span style="color: black;">&#41;</span></pre></div></div>


<p>В приведенном выше примере функция <code>deferToThread</code> переносит выполнение указанной функции в отдельную нить и возвращает
Deferred, через который будет асинхронно получен результат выполнения функции или исключение, если они будет выброшено.
Первое деление (3/2) выполняется в отдельной нити, затем распечатывается его результат на экран, а затем запускается
еще одно вычисление (3/0), которое генерирует исключение, обрабатываемое функцией <code>handleDivisionByZero</code>.</p>

<p>В одной статье не описать и части того, что хотелось бы сказать о Deferred, мне удалось не написать ни слова о том,
как же они работают. Если успел заинтересовать &#8211; читайте материалы ниже, а я обещаю написать еще.</p>

<h2>Дополнительные материалы</h2>

<ul>
<li>Документация Twisted Framework:

<ul>
<li><a href="http://twistedmatrix.com/projects/core/documentation/howto/async.html">Основы асинхронного программирования</a></li>
<li><a href="http://twistedmatrix.com/projects/core/documentation/howto/defer.html">Использование Deferred</a>, <a href="http://twistedmatrix.com/projects/core/documentation/howto/gendefer.html">Откуда берутся Deferred?</a>, <a href="http://twistedmatrix.com/projects/core/documentation/howto/deferredindepth.html">Детальное описание Deferred</a></li>
</ul></li>
<li>Deferred в других языках программирования:

<ul>
<li>JavaScript: <a href="http://javascript.ru/tutorial/async/deferred-deep">Использование Deferred в JavaScript</a>, <a href="http://code.netstream.ru/wiki/QooxdooTwistedDeferred">Deferred в qooxdoo</a>, <a href="http://ajaxian.com/archives/dojos-deferred-api">Deferred в Dojo</a>, <a href="http://mochikit.com/doc/html/MochiKit/Async.html#fn-deferred">Deferred в MochiKit</a></li>
<li>C++: <a href="http://twistedmatrix.com/pipermail/twisted-python/2009-January/019119.html">1</a>, <a href="http://www.twistedmatrix.com/pipermail/twisted-python/2008-October/018548.html">2</a>, <a href="http://web.me.com/namezys/main/Блог/Записи/2009/1/26_Я_дописал_sequence._То_есть_питоновские_деферы.html">3</a></li>
<li>&#8230;</li>
</ul></li>
<li><a href="http://burus.org/2008/12/16/twisted-classic-examples/">Александр Бурцев о Twisted</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/02/10/deferred-async-programming/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Структуры данных в memcached/MemcacheDB</title>
		<link>http://www.smira.ru/2009/01/21/data-structures-in-memcached-memcachedb/</link>
		<comments>http://www.smira.ru/2009/01/21/data-structures-in-memcached-memcachedb/#comments</comments>
		<pubDate>Wed, 21 Jan 2009 07:34:33 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Преподавание]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[memcachedb]]></category>
		<category><![CDATA[лог]]></category>
		<category><![CDATA[массив]]></category>
		<category><![CDATA[разработка]]></category>
		<category><![CDATA[список]]></category>
		<category><![CDATA[счетчик]]></category>
		<category><![CDATA[таблица]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=259</guid>
		<description><![CDATA[Достаточно часто нам приходится хранить данные в
memcached или
MemcacheDB. Это могут быть относительно
простые данные, например, закэшированные выборки из базы данных,
а иногда необходимо хранить и обрабатывать более сложные структуры данных,
которые обновляются одновременно из нескольких процессов, обеспечивать
быстрое чтение данных и т.п. Реализация таких структур данных уже не укладывается
в комбинацию команд memcached get/set. В данной статье будут описаны
способы хранения [...]]]></description>
			<content:encoded><![CDATA[<p>Достаточно часто нам приходится хранить данные в
<a href="http://danga.com/memcached/">memcached</a> или
<a href="http://memcachedb.org/">MemcacheDB</a>. Это могут быть относительно
простые данные, например, закэшированные выборки из базы данных,
а иногда необходимо хранить и обрабатывать более сложные структуры данных,
которые обновляются одновременно из нескольких процессов, обеспечивать
быстрое чтение данных и т.п. Реализация таких структур данных уже не укладывается
в комбинацию команд memcached <code>get</code>/<code>set</code>. В данной статье будут описаны
способы хранения некоторых структур данных в memcached с примерами кода и описанием
основных идей.</p>

<p>Memcached и MemcacheDB в данной статье рассматриваются
вместе, потому что имеют общий интерфейс доступа и логика работы большей части
структур данных будет одинаковой, далее будем называть их просто &laquo;memcached&raquo;.
Зачем нам нужно хранить структуры данных в memcached? Чаще всего для
распределенного доступа к данным из разных процессов, с разных серверов и т.п.
А иногда для решения задачи хранения данных достаточно интерфейса,
предоставляемого MemcacheDB, и необходимость в использовании СУБД отпадает.</p>

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

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

<p>Примеры кода будут написаны на Python, в них будет использоваться следующий
интерфейс доступа к memcached:</p>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">class</span> Memcache<span style="color: black;">&#40;</span><span style="color: #008000;">object</span><span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">def</span> get<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, key<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Получить значение ключа.
&nbsp;
        @param key: ключ
        @type key: C{str}
        @return: значение ключа
        @raises KeyError: ключ отсутствует
        &quot;&quot;&quot;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> add<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, key, value, expire=<span style="color: #ff4500;">0</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Установить значение ключа, если он еще не существует.
&nbsp;
        @param key: ключ
        @type key: C{str}
        @param value: значение ключа
        @param expire: &quot;срок годности&quot; ключа в секундах (0 - бесконечно)
        @type expire: C{int}
        @raises KeyError: ключ уже существует
        &quot;&quot;&quot;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> incr<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, key, value=<span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Увеличить на единицу значение ключа.
&nbsp;
        @param key: ключ
        @type key: C{str}
        @param value: инкремент
        @type value: C{int}
        @return: новое значение ключа (после увеличения)
        @rtype: C{int}
        @raises KeyError: ключ не существует
        &quot;&quot;&quot;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> append<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, key, value<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Добавить суффикс к значению существующего ключа.
&nbsp;
        @param key: ключ
        @type key: C{str}
        @param value: суффикс
        @raises KeyError: ключ не существует
        &quot;&quot;&quot;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> delete<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, key<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Удалить ключ.
&nbsp;
        @param key: ключ
        @type key: C{str}
        &quot;&quot;&quot;</span></pre></td></tr></table></div>


<p>Все наши структуры данных будут наследниками базового класса следующего вида:</p>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">class</span> MemcacheObject<span style="color: black;">&#40;</span><span style="color: #008000;">object</span><span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, mc<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;&quot;
        Конструктор.
&nbsp;
        @param mc: драйвер memcached
        @type mc: L{Memcache}
        &quot;&quot;&quot;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">mc</span> = mc</pre></td></tr></table></div>


<h2>Блокировка</h2>

<h3>Задача</h3>

<p>Блокировка (mutex, или в данной ситуации скорее spinlock) &#8211; это не совсем структура данных,
но она может быть использована как &laquo;кирпичик&raquo; при построении сложных структур данных в memcached.
Блокировка используется для исключения одновременного доступа к некоторым ключам, возможности
блокировки отсутствуют в API memcached, поэтому она эмулируется с помощью других операций.</p>

<p>Итак, блокировка определяется своим именем, работает распределённо (то есть из любого процесса,
имеющего доступ к memcached), имеет автоматическое &laquo;умирание&raquo; (на случай, если процесс, поставивший
блокировку, не сможет её снять, например, по причине аварийного завершения).</p>

<p>Операции над блокировкой:</p>

<ul>
<li>попробовать заблокироваться (try-lock);</li>
<li>разблокировать (unlock).</li>
</ul>

<h3>Решение</h3>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">class</span> MCLock<span style="color: black;">&#40;</span>MemcacheObject<span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, mc, name, timeout=<span style="color: #ff4500;">5</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Конструктор.
&nbsp;
        @param name: имя блокировки
        @type name: C{str}
        @param timeout: таймаут аварийного снятия блокировки (секунды)
        @type timeout: C{int}
        &quot;&quot;&quot;</span>
        <span style="color: #008000;">super</span><span style="color: black;">&#40;</span>MCLock, <span style="color: #008000;">self</span><span style="color: black;">&#41;</span>.<span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span>mc<span style="color: black;">&#41;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">key</span> = <span style="color: #483d8b;">'lock_'</span> + name
        <span style="color: #008000;">self</span>.<span style="color: black;">locked</span> = <span style="color: #008000;">False</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">timeout</span> = timeout
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> try_lock<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Попробовать заблокироваться.
&nbsp;
        @return: удалось ли заблокироваться?
        @rtype: C{bool}
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">assert</span> <span style="color: #ff7700;font-weight:bold;">not</span> <span style="color: #008000;">self</span>.<span style="color: black;">locked</span>
        <span style="color: #ff7700;font-weight:bold;">try</span>:
            <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">add</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span>, <span style="color: #ff4500;">1</span>, <span style="color: #008000;">self</span>.<span style="color: black;">timeout</span><span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
            <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #008000;">False</span>
&nbsp;
        <span style="color: #008000;">self</span>.<span style="color: black;">locked</span> = <span style="color: #008000;">True</span>
        <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #008000;">True</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> unlock<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Снять блокировку.
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">assert</span> <span style="color: #008000;">self</span>.<span style="color: black;">locked</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">delete</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span><span style="color: black;">&#41;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">locked</span> = <span style="color: #008000;">False</span></pre></td></tr></table></div>


<h3>Обсуждение</h3>

<p>Корректность работы блокировки основывается на операции <code>add</code>, которая гарантирует что ровно один процесс
сможет &laquo;первым&raquo; установить значение ключа, все остальные процессы получат ошибку. Приведенный выше класс может
быть обернут более удобно, например, для <a href="http://docs.python.org/reference/datamodel.html#context-managers">with-обработчика Python</a>.
Более подробно вопрос блокировок обсуждался в посте про 
<a href="http://www.smira.ru/2008/10/28/web-caching-memcached-4/">одновременное перестроение кэшей</a>.</p>

<h2>Атомарный счетчик</h2>

<h3>Задача</h3>

<p><a name="atomic-counter">Необходимо</a> вычислять количество событий, которые происходят распределённо на разных
серверах, также получать текущее значение счетчика. При этом счетчик не должен &laquo;терять&raquo;
события и начислять &laquo;лишние&raquo; значения.</p>

<p>Тип данных: целое число.</p>

<p>Начальное значение: ноль.</p>

<p>Операции:</p>

<ul>
<li>увеличить значение счетчика на указанное значение;</li>
<li>получить текущее значение счетчика.</li>
</ul>

<h3>Решение</h3>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">class</span> MCCounter<span style="color: black;">&#40;</span>MemcacheObject<span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, mc, name<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Конструктор.
&nbsp;
        @param name: имя счетчика
        @type name: C{str}
        &quot;&quot;&quot;</span>
        <span style="color: #008000;">super</span><span style="color: black;">&#40;</span>MCCounter, <span style="color: #008000;">self</span><span style="color: black;">&#41;</span>.<span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span>mc<span style="color: black;">&#41;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">key</span> = <span style="color: #483d8b;">'counter'</span> + name
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> increment<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, value=<span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Увеличить значение счетчика на указанное значение.
&nbsp;
        @param value: инкремент
        @type value: C{int}
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">while</span> <span style="color: #008000;">True</span>:
            <span style="color: #ff7700;font-weight:bold;">try</span>:
                <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">incr</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span>, value<span style="color: black;">&#41;</span>
                <span style="color: #ff7700;font-weight:bold;">return</span>
            <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
                <span style="color: #ff7700;font-weight:bold;">pass</span>
&nbsp;
            <span style="color: #ff7700;font-weight:bold;">try</span>:
                <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">add</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span>, value, <span style="color: #ff4500;">0</span><span style="color: black;">&#41;</span>
                <span style="color: #ff7700;font-weight:bold;">return</span>
            <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
                <span style="color: #ff7700;font-weight:bold;">pass</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> value<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Получить значение счетчика.
&nbsp;
        @return: текущее значение счетчика
        @rtype: C{int}
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">try</span>:
            <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">get</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span><span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
            <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #ff4500;">0</span></pre></td></tr></table></div>


<h3>Обсуждение</h3>

<p>Реализация счетчика достаточно очевидная, самый сложный момент &#8211; это &laquo;вечный&raquo; цикл в методе <code>increment</code>.
Он необходим для корректной инициализации значения счетчика в условиях конкурентного доступа к нему. 
Если операция <code>incr</code> завершилась ошибкой отсутствия ключа, нам необходимо создать ключ счетчика, но этот
код могут одновременно выполнять несколько процессов, тогда одному из них удастся <code>add</code>, а второй получит ошибку
и инкрементирует уже созданный счетчик с помощью <code>incr</code>. Зацикливание невозможно, т.к. одна из операций <code>incr</code> или
<code>add</code> должна быть успешной: после создания ключа в memcached операция <code>incr</code> будет успешной всё время существования ключа.</p>

<p>Как быть с ситуацией, когда ключ со счетчиком из memcached пропадет? Возможны две ситуации, когда ключ исчезнет:</p>

<ol>
<li>memcached не хватает памяти для размещения других ключей;</li>
<li>процесс memcached аварийно завершился (сервер &laquo;упал&raquo;).</li>
</ol>

<p>(В случае MemcacheDB, настроенной репликации и т.п. падение сервера не должно приводить к потере ключей.)
В первом случае надо лишь правильно обслуживать memcached &#8211; памяти должно хватать для счетчиков, иначе мы начинаем
терять значения, а это неприемлемо в данной ситуации (но совершенно нормально, например, для хранения кэшей в memcached).
Второй случай всегда возможен, если такая ситуация часто возникает, можно дублировать счетчики в нескольких
memcached-серверах.</p>

<h2>Счетчик посетителей</h2>

<h3>Задача</h3>

<p>Необходимо вычислить количество уникальных обращений к сайту в течение некоторого периода T. Например, T может быть
равно 5 минутам. Пусть мы можем сказать, когда было последнее обращение данного посетителя к сайту &#8211; более T минут 
назад или менее (то есть является ли оно уникальным в течение периода T).</p>

<p>Операции над счетчиком:</p>

<ul>
<li>увеличить значение счетчика на единицу (обращение уникального в течение периода T посетителя);</li>
<li>получить текущее число посетителей.</li>
</ul>

<h3>Решение</h3>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #dc143c;">time</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">&quot;&quot;&quot;
    Текущее время в секундах с любого момента времени (например, UNIX Epoch).
&nbsp;
    @return: текущее время в секундах
    @rtype: C{int}
    &quot;&quot;&quot;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">class</span> MCVisitorCounter<span style="color: black;">&#40;</span>MemcacheObject<span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, mc, name, T<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Конструктор.
&nbsp;
        @param name: имя счетчика
        @type name: C{str}
        @param T: период счетчика, секунды
        @type T: C{int}
        &quot;&quot;&quot;</span>
        <span style="color: #008000;">super</span><span style="color: black;">&#40;</span>MCVisitorCounter, <span style="color: #008000;">self</span><span style="color: black;">&#41;</span>.<span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span>mc<span style="color: black;">&#41;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">keys</span> = <span style="color: black;">&#40;</span><span style="color: #483d8b;">'counter'</span> + name + <span style="color: #483d8b;">'_0'</span>, <span style="color: #483d8b;">'counter'</span> + name + <span style="color: #483d8b;">'_1'</span><span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> _active<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Получить индекс текущего счетчика.
&nbsp;
        @return: 0 или 1
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #ff4500;">0</span> <span style="color: #ff7700;font-weight:bold;">if</span> <span style="color: black;">&#40;</span><span style="color: #dc143c;">time</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span> <span style="color: #66cc66;">%</span> <span style="color: black;">&#40;</span><span style="color: #ff4500;">2</span><span style="color: #66cc66;">*</span>T<span style="color: black;">&#41;</span><span style="color: black;">&#41;</span> <span style="color: #66cc66;">&lt;</span> T <span style="color: #ff7700;font-weight:bold;">else</span> <span style="color: #ff4500;">1</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> increment<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Увеличить значение счетчика.
        &quot;&quot;&quot;</span>
        active = <span style="color: #008000;">self</span>._active<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">while</span> <span style="color: #008000;">True</span>:
            <span style="color: #ff7700;font-weight:bold;">try</span>:
                <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">incr</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">keys</span><span style="color: black;">&#91;</span><span style="color: #ff4500;">1</span>-active<span style="color: black;">&#93;</span>, <span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span>
                <span style="color: #ff7700;font-weight:bold;">return</span>
            <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
                <span style="color: #ff7700;font-weight:bold;">pass</span>
&nbsp;
            <span style="color: #ff7700;font-weight:bold;">try</span>:
                <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">add</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">keys</span><span style="color: black;">&#91;</span><span style="color: #ff4500;">1</span>-active<span style="color: black;">&#93;</span>, <span style="color: #ff4500;">1</span>, <span style="color: #ff4500;">2</span><span style="color: #66cc66;">*</span>T<span style="color: black;">&#41;</span>
                <span style="color: #ff7700;font-weight:bold;">return</span>
            <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
                <span style="color: #ff7700;font-weight:bold;">pass</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> value<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Получить значение счетчика.
&nbsp;
        @return: текущее значение счетчика
        @rtype: C{int}
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">try</span>:
            <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">get</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">keys</span><span style="color: black;">&#91;</span><span style="color: #008000;">self</span>._active<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span><span style="color: black;">&#93;</span><span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
            <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #ff4500;">0</span></pre></td></tr></table></div>


<h3>Обсуждение</h3>

<p><img src="http://www.smira.ru/wp-content/uploads/2009/01/ms1.png" alt="Работа счетчика посетителей" title="Работа счетчика посетителей" width="639" height="273" class="alignnone size-full wp-image-274" /></p>

<p>Основная структура работы со счетчиком в memcached в данной задаче повторяет решение задачи атомарного счетчика.
Базовая идея &#8211; использование теневого и текущего счетчика: теневой счетчик  получает обновления (увеличивается), а 
текущий используется для получения значения числа посетителей (его значение было накоплено, когда она был теневым).
Каждый период T текущий и теневой счетчик меняются местами. Ключ теневого счетчика создается со 
сроком годности 2*T секунд, то есть его время жизни &#8211; T секунд, пока он был теневым (и увеличивался), и Т секунд, пока
его значение использовалось для чтения. К моменту того, как этот ключ снова станет теневым счетчиком, он должен
быть уничтожен memcached, т.к. срок его годности истек. Идея теневого и текущего счетчика напоминает, например,
двойную буферизацию при выводе на экран в играх.</p>

<p>Необходимо обратить внимание на функцию <code>_active()</code>: она реализована именно через вычисление остатка от деления
текущего времени в секундах, а не, например, через периодическое (раз в Т секунд) смену значения <code>active</code>, т.к.
если время на всех серверах синхронизировано с разумной точностью, то и результат функции <code>_active()</code> будет на
всех серверах одинаковым, что важно для корректного распределенного функционирования данной структуры данных.</p>

<p>Описанный подход будет работать корректно только при достаточно большом количестве посетителей, когда временной интервал между
очередной сменой значения функции <code>_active()</code> и обращением к функции <code>increment()</code> будет как можно меньше. То есть
когда <code>increment()</code> вызывается часто, то есть когда посетителей достаточно много, например, 1000 человек при периоде Т=3 минуты. Иначе
создание и уничтожение ключей не будет синхронизировано с моментом смены значения <code>_active()</code> и начнут пропадать 
посетители, накопленные в теневом счетчике, который будет уничтожен в процессе накопления значений и 
создан заново. В этой ситуации его время жизни 2<em>T будет &laquo;сдвинуто&raquo; относительно моментов 0, T функции
time() % (2</em>T). В силу того, что memcached рассматривает срок годности ключей дискретно относительно секунд (с точностью не более одной секунды), 
любой сдвиг времени жизни ключа может составлять 1,2,&#8230;,T-1 секунду (отсутствие сдвига &#8211; 0 секунд). Дискретность
срока годности означает, что если ключ был создан в 25 секунд 31 миллисекунду со сроком годности 10 секунд,
то он будет уничтожен на 35-й (или 36-й) секунде 0 миллисекунд (а не 31-й миллисекунде). Для компенсация сдвига
в целое число секунд необходимо исправить строчку 43 примера следующим образом:</p>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>43
</pre></td><td class="code"><pre class="python" style="font-family:monospace;">                <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">add</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">keys</span><span style="color: black;">&#91;</span><span style="color: #ff4500;">1</span>-active<span style="color: black;">&#93;</span>, <span style="color: #ff4500;">1</span>, <span style="color: #ff4500;">2</span><span style="color: #66cc66;">*</span>T - <span style="color: #dc143c;">time</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span> <span style="color: #66cc66;">%</span> T<span style="color: black;">&#41;</span></pre></td></tr></table></div>


<p>Значение счетчика посетителей не изменяется в течение периода времени T, для более приятного отображения можно
к значению счетчика при выводе добавить нормально распределенную величину с небольшой дисперсией и математическим ожиданием
около 5% от значения счетчика.</p>

<p>Немного более сложный вариант такого счетчика (с интерполяцией во времени), но с точно такой же идеей был 
описан в посте про <a href="http://www.smira.ru/2008/10/24/web-caching-memcached-3">атомарность операции и счетчики в memcached</a>.</p>

<h2>Лог событий</h2>

<h3>Задача</h3>

<p>Задача этой структуры данных &#8211; хранение событий, произошедших в распределенной системе 
за последние T секунд. Каждое событие имеет момент времени,
когда оно произошло, остальное содержимое события определяется логикой приложения.</p>

<p>Операции над логом событий:</p>

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

<h3>Решение</h3>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #dc143c;">time</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">&quot;&quot;&quot;
    Текущее время в секундах с любого момента времени (например, UNIX Epoch).
&nbsp;
    @return: текущее время в секундах
    @rtype: C{int}
    &quot;&quot;&quot;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">class</span> Event:
    <span style="color: #483d8b;">&quot;&quot;&quot;
    Событие, помещаемое в лог событий.
    &quot;&quot;&quot;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> when<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Момент времени, когда произошло событие (секунды).
        &quot;&quot;&quot;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> serialize<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Сериализовать событие.
&nbsp;
        @return: сериализованное представление
        @rtype: C{str}
        &quot;&quot;&quot;</span>
&nbsp;
    @static
    <span style="color: #ff7700;font-weight:bold;">def</span> deserialize<span style="color: black;">&#40;</span>serialized<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Десериализовать набор событий.
&nbsp;
        @param serialized: сериализованное представление одного 
                           или нескольких событий
        @type serialized: C{str}
        @return: массив десериализованных событий
        @rtype: C{list(Event)}
        &quot;&quot;&quot;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">class</span> MCEventLog<span style="color: black;">&#40;</span>MemcacheObject<span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, mc, name, timeChunk=<span style="color: #ff4500;">10</span>, numChunks=<span style="color: #ff4500;">10</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Конструктор.
&nbsp;
        @param name: имя лога событий
        @type name: C{str}
        @param timeChunk: емкость одного ключа лога в секундах
        @type timeChunk: C{int}
        @param numChunks: число выделяемых ключей под лог
        @type numChunks: C{int}
        &quot;&quot;&quot;</span>
        <span style="color: #008000;">super</span><span style="color: black;">&#40;</span>MCEventLog, <span style="color: #008000;">self</span><span style="color: black;">&#41;</span>.<span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span>mc<span style="color: black;">&#41;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">keyTemplate</span> = <span style="color: #483d8b;">'messagelog'</span> + name + <span style="color: #483d8b;">'_%d'</span><span style="color: #66cc66;">;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">timeChunk</span> = timeChunk
        <span style="color: #008000;">self</span>.<span style="color: black;">numChunks</span> = numChunks
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> put<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, event<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Поместить событие в лог.
&nbsp;
        @param event: событие
        @type event: L{Event}
        &quot;&quot;&quot;</span>
        serialized = event.<span style="color: black;">serialize</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>
        key = <span style="color: #008000;">self</span>.<span style="color: black;">keyTemplate</span> <span style="color: #66cc66;">%</span> <span style="color: black;">&#40;</span>event.<span style="color: black;">when</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span> // <span style="color: #008000;">self</span>.<span style="color: black;">timeChunk</span> <span style="color: #66cc66;">%</span> <span style="color: #008000;">self</span>.<span style="color: black;">numChunks</span><span style="color: black;">&#41;</span>
&nbsp;
        <span style="color: #ff7700;font-weight:bold;">while</span> <span style="color: #008000;">True</span>:
            <span style="color: #ff7700;font-weight:bold;">try</span>:
                <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">append</span><span style="color: black;">&#40;</span>key, serialized<span style="color: black;">&#41;</span>
                <span style="color: #ff7700;font-weight:bold;">return</span>
            <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
                <span style="color: #ff7700;font-weight:bold;">pass</span>
&nbsp;
            <span style="color: #ff7700;font-weight:bold;">try</span>:
                <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">add</span><span style="color: black;">&#40;</span>key, serialized, <span style="color: #008000;">self</span>.<span style="color: black;">timeChunk</span> <span style="color: #66cc66;">*</span> <span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">numChunks</span>-<span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span><span style="color: black;">&#41;</span>
                <span style="color: #ff7700;font-weight:bold;">return</span>
            <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
                <span style="color: #ff7700;font-weight:bold;">pass</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> fetch<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, first=<span style="color: #008000;">None</span>, last=<span style="color: #008000;">None</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Получить события из лога за указанный период (или все события).
&nbsp;
        @param first: минимальное время возвращаемого сообщения
        @type first: C{int}
        @param last: максимальное время возвращаемого сообщения
        @type last: C{int}
        @return: массив событий
        @rtype: C{list(Event)}
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">if</span> last <span style="color: #ff7700;font-weight:bold;">is</span> <span style="color: #008000;">None</span> <span style="color: #ff7700;font-weight:bold;">or</span> last <span style="color: #66cc66;">&gt;</span> <span style="color: #dc143c;">time</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
            last = <span style="color: #dc143c;">time</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>
&nbsp;
        <span style="color: #ff7700;font-weight:bold;">if</span> first <span style="color: #ff7700;font-weight:bold;">is</span> <span style="color: #008000;">None</span> <span style="color: #ff7700;font-weight:bold;">or</span> last <span style="color: #66cc66;">&lt;</span> first <span style="color: #ff7700;font-weight:bold;">or</span> 
           <span style="color: black;">&#40;</span>last-first<span style="color: black;">&#41;</span> <span style="color: #66cc66;">&gt;</span> <span style="color: #008000;">self</span>.<span style="color: black;">timeChunk</span> <span style="color: #66cc66;">*</span> <span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">numChunks</span>-<span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span>:
            first = <span style="color: #dc143c;">time</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span> - <span style="color: #008000;">self</span>.<span style="color: black;">timeChunk</span> <span style="color: #66cc66;">*</span> <span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">numChunks</span>-<span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span>
&nbsp;
        firstKey = first / <span style="color: #008000;">self</span>.<span style="color: black;">timeChunk</span> <span style="color: #66cc66;">%</span> <span style="color: #008000;">self</span>.<span style="color: black;">numChunks</span>
        lastKey = last / <span style="color: #008000;">self</span>.<span style="color: black;">timeChunk</span> <span style="color: #66cc66;">%</span> <span style="color: #008000;">self</span>.<span style="color: black;">numChunks</span>
&nbsp;
        <span style="color: #ff7700;font-weight:bold;">if</span> firstKey <span style="color: #66cc66;">&lt;</span> lastKey:
            keyRange = <span style="color: #008000;">range</span><span style="color: black;">&#40;</span>firstKey, lastKey+<span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">else</span>:
            keyRange = <span style="color: #008000;">range</span><span style="color: black;">&#40;</span>firstKey, <span style="color: #008000;">self</span>.<span style="color: black;">numChunks</span><span style="color: black;">&#41;</span> + <span style="color: #008000;">range</span><span style="color: black;">&#40;</span><span style="color: #ff4500;">0</span>, lastKey+<span style="color: #ff4500;">1</span><span style="color: black;">&#41;</span>
&nbsp;
        keys = <span style="color: black;">&#91;</span><span style="color: #008000;">self</span>.<span style="color: black;">keyTemplate</span> <span style="color: #66cc66;">%</span> n <span style="color: #ff7700;font-weight:bold;">for</span> n <span style="color: #ff7700;font-weight:bold;">in</span> keyRange<span style="color: black;">&#93;</span>
        result = <span style="color: black;">&#91;</span><span style="color: black;">&#93;</span>
        <span style="color: #ff7700;font-weight:bold;">for</span> key <span style="color: #ff7700;font-weight:bold;">in</span> keys:
            <span style="color: #ff7700;font-weight:bold;">try</span>:
                events = Event.<span style="color: black;">deserialize</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">get</span><span style="color: black;">&#40;</span>key<span style="color: black;">&#41;</span><span style="color: black;">&#41;</span>
            <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
                <span style="color: #ff7700;font-weight:bold;">continue</span>
&nbsp;
            result.<span style="color: black;">extend</span><span style="color: black;">&#40;</span><span style="color: #008000;">filter</span><span style="color: black;">&#40;</span><span style="color: #ff7700;font-weight:bold;">lambda</span> e: e.<span style="color: black;">when</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span> <span style="color: #66cc66;">&gt;</span>= first <span style="color: #ff7700;font-weight:bold;">and</span> 
                                                e.<span style="color: black;">when</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span> <span style="color: #66cc66;">&lt;</span>= last, l<span style="color: black;">&#41;</span><span style="color: black;">&#41;</span>
&nbsp;
        <span style="color: #ff7700;font-weight:bold;">return</span> result</pre></td></tr></table></div>


<h3>Обсуждение</h3>

<p>Основная идея лога событий &#8211; кольцевой буфер, состоящий из <code>numChunks</code> ключей в memcached.
Каждый ключ активен (то есть дополняется значениями) в течение <code>timeChunk</code> секунд, после
чего активным становится следующий ключ (если активным был последний ключ, эта роль
переходит к первому ключу). Полный цикл буфера, т.е. период времени между двумя использованиями
одного ключа составляет <code>numChunks * timeChunk</code> секунд, а время жизни каждого ключа &#8211; 
<code>(numChunks - 1) * timeChunk</code> секунд, таким образом при любом сдвиге времени создания
ключа по модулю <code>timeChunk</code> к моменту времени следующего использования ключ гарантированно
будет уничтожен. Таким образом, ёмкость лога событий (или период времени, за который
сохраняются события) составляет <code>(numChunks - 1) * timeChunk</code> секунд. Такое разбиение
лога на ключи позволяет при получении событий из лога вынимать лишь
те ключи, которые соответствуют интересному нам временному отрезку.</p>

<p>Выбор параметров
<code>timeChunk</code> и <code>numChunks</code> зависит от применения лога событий: сначала определяется желаемый срок
хранения событий, затем по частоте событий выбирается такое значение <code>timeChunk</code>, чтобы размер
каждого ключа лога событий был относительно небольшим (например, 10-20Кб). Из этих соображений
можно найти значение и второго параметра, <code>numChunks</code>.</p>

<p>В примере используется некоторый класс <code>Event</code>, который обладает единственным интересным
для нас свойством &#8211; временем, когда произошло событие. В методе <code>put</code> лога событий предполагается,
что событие <code>event</code>, переданное в качестве параметра, произошло &laquo;недавно&raquo;, то есть с момента
<code>event.when()</code> прошло не более чем <code>(numChunks - 1) * timeChunk</code> секунд (емкость лога). При
работе <code>put</code> вычисляется ключ, в который должна быть помещена информация о событии, в соответствие
с его временной меткой. После этого с помощью уже знакомой по предыдущим примерам техники ключ либо создается, либо
к значению уже существующего ключа дописывается сериализованное представление события.</p>

<p>Метод <code>fetch</code> вычисляет потенциальный набор ключей лога, в которых могут находиться события,
произошедшие во временной интервал от <code>first</code> до <code>last</code>. Если временные рамки не заданы,
<code>last</code> считается равным текущему моменту времени, а <code>first</code> &#8211; моменту времени, отстоящему
от текущего на емкость лога. Набор ключей вычисляется с учетом кольцевой структуры метода,
после чего выбираются соответствующие ключи, десериализуются последовательно записанные
в них события и проводится дополнительная фильтрация на попадание в отрезок <code>[first, last]</code>.</p>

<p>Приведенная выше сигнатура метода позволяет последовательными обращениями выводить новые
события из лога:</p>

<ol>
<li>Первый раз вызывается <code>events = fetch()</code>. Вычисляется <code>lastSeen</code> как <code>max(events.when())</code>.</li>
<li>Все последующие обращения выглядят следующим образом: <code>events = fetch(first=lastSeen)</code>, при этом
<code>lastSeen</code> каждый раз перевычисляется.</li>
</ol>

<h2>Массив</h2>

<h3>Задача 1</h3>

<p>В массиве хранится список значений произвольного типа, относительно редко происходит
обновление списка, гораздо чаще происходит получение списка целиком.</p>

<p>Операции над массивом:</p>

<ul>
<li>изменить массив (редкая операция);</li>
<li>получить массив целиком (частая операция).</li>
</ul>

<h3>Решение 1</h3>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> serializeArray<span style="color: black;">&#40;</span><span style="color: #dc143c;">array</span><span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">&quot;&quot;&quot;
    Сериализовать массив в бинарное представление.
    &quot;&quot;&quot;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> deserializeArray<span style="color: black;">&#40;</span><span style="color: #008000;">str</span><span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">&quot;&quot;&quot;
    Десериализовать массив из бинарного представления.
    &quot;&quot;&quot;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">class</span> MCArray1<span style="color: black;">&#40;</span>MemcacheObject<span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, mc, name<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Конструктор.
&nbsp;
        @param name: имя массива
        @type name: C{str}
        &quot;&quot;&quot;</span>
        <span style="color: #008000;">super</span><span style="color: black;">&#40;</span>MCArray1, <span style="color: #008000;">self</span><span style="color: black;">&#41;</span>.<span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span>mc<span style="color: black;">&#41;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">lock</span> = MCLock<span style="color: black;">&#40;</span>name<span style="color: black;">&#41;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">key</span> = <span style="color: #483d8b;">'array'</span> + name
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> fetch<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Получить текущее значение массива.
&nbsp;
        @return: массив
        @rtype: C{list}
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">try</span>:
            <span style="color: #ff7700;font-weight:bold;">return</span> deserializeArray<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">get</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span><span style="color: black;">&#41;</span><span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
            <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: black;">&#91;</span><span style="color: black;">&#93;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> change<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, add_elems=<span style="color: black;">&#91;</span><span style="color: black;">&#93;</span>, delete_elems=<span style="color: black;">&#91;</span><span style="color: black;">&#93;</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Изменить значение массива, добавив или удалив из него
        элементы.
&nbsp;
        @param add_elems: элементы, которые надо добавить
        @type add_elems: C{list}
        @param delete_elems: элементы, которые надо удалить
        @type delete_elems: C{list}
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">while</span> <span style="color: #ff7700;font-weight:bold;">not</span> <span style="color: #008000;">self</span>.<span style="color: black;">lock</span>.<span style="color: black;">try_lock</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
            <span style="color: #ff7700;font-weight:bold;">pass</span>
&nbsp;
        <span style="color: #ff7700;font-weight:bold;">try</span>:
            <span style="color: #ff7700;font-weight:bold;">try</span>:
                <span style="color: #dc143c;">array</span> = deserializeArray<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">get</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span><span style="color: black;">&#41;</span><span style="color: black;">&#41;</span>
            <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
                <span style="color: #dc143c;">array</span> = <span style="color: black;">&#91;</span><span style="color: black;">&#93;</span>
            <span style="color: #dc143c;">array</span> = <span style="color: #008000;">filter</span><span style="color: black;">&#40;</span><span style="color: #ff7700;font-weight:bold;">lambda</span> e: e <span style="color: #ff7700;font-weight:bold;">not</span> <span style="color: #ff7700;font-weight:bold;">in</span> delete_elems, <span style="color: #dc143c;">array</span><span style="color: black;">&#41;</span> + add_elems
            <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: #008000;">set</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span>, serializeArray<span style="color: black;">&#40;</span><span style="color: #dc143c;">array</span><span style="color: black;">&#41;</span>, <span style="color: #ff4500;">0</span><span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">finally</span>:
            <span style="color: #008000;">self</span>.<span style="color: black;">lock</span>.<span style="color: black;">unlock</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span></pre></td></tr></table></div>


<h3>Обсуждение 1</h3>

<p>Приведенный выше способ решения на самом деле не имеет никакого отношения к массивам, а может
быть применен для любой структуры данных. Он основан на модели reader-writer, когда есть много
читателей и относительно мало писателей. Читатели в любой момент с помощью метода <code>fetch</code> получают
содержимое массива, при этом важно, что &laquo;писатель&raquo; <code>сhange</code> записывает содержимое одной командой
memcached, то есть в силу внутренней атомарности операций <code>get</code> и <code>set</code> в memcached и несмотря на
отсутствие синхронизации между методами <code>fetch</code> и <code>сhange</code>, результат <code>fetch</code> всегда будет консистентным:
это будет значение до или после очередного изменения. Писатели блокируются от одновременного 
изменения массива с помощью блокировки <code>MCLock</code>, описанной выше.</p>

<p>В данной ситуации можно было бы избежать использования блокировки и воспользоваться командами
<code>gets</code>, <code>cas</code> и <code>add</code> из протокола memcached для того, чтобы гарантировать атомарность изменений
с помощью функции <code>change</code>.</p>

<h3>Задача 2</h3>

<p>Массив хранит список значений некоторого типа, часто происходит операция вида &laquo;добавить значение
в массив&raquo;. Относительно редко массив запрашивается целиком. Для простоты реализации
в дальнейшем будет рассматриваться массив целых чисел, хотя для решения задачи тип данных не имеет
существенного значения.</p>

<p>Операции над массивом:</p>

<ul>
<li>добавить значение в массив (частая операция);</li>
<li>получить массив целиком.</li>
</ul>

<h3>Решение 2</h3>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> serializeInt<span style="color: black;">&#40;</span><span style="color: #008000;">int</span><span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">&quot;&quot;&quot;
    Сериализовать целое число в бинарное представление (str).
    &quot;&quot;&quot;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> deserializeIntArray<span style="color: black;">&#40;</span><span style="color: #008000;">str</span><span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">&quot;&quot;&quot;
    Десериализовать массив целых чисел из бинарного представления.
    &quot;&quot;&quot;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">class</span> MCArray2<span style="color: black;">&#40;</span>MemcacheObject<span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, mc, name<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Конструктор.
&nbsp;
        @param name: имя массива
        @type name: C{str}
        &quot;&quot;&quot;</span>
        <span style="color: #008000;">super</span><span style="color: black;">&#40;</span>MCArray2, <span style="color: #008000;">self</span><span style="color: black;">&#41;</span>.<span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span>mc<span style="color: black;">&#41;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">key</span> = <span style="color: #483d8b;">'array'</span> + name
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> fetch<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Получить текущее значение массива.
&nbsp;
        @return: массив
        @rtype: C{list}
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">try</span>:
            <span style="color: #ff7700;font-weight:bold;">return</span> deserializeIntArray<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">get</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span><span style="color: black;">&#41;</span><span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
            <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: black;">&#91;</span><span style="color: black;">&#93;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> add<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, element<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Добавить элемент в массив.
&nbsp;
        @param element: элемент, который необходимо добавить в массив
        @type element: C{int}
        &quot;&quot;&quot;</span>
        element = serializeInt<span style="color: black;">&#40;</span>element<span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">while</span> <span style="color: #008000;">True</span>:
            <span style="color: #ff7700;font-weight:bold;">try</span>:
                <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">append</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span>, element<span style="color: black;">&#41;</span>
            <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
                <span style="color: #ff7700;font-weight:bold;">return</span>
&nbsp;
            <span style="color: #ff7700;font-weight:bold;">try</span>:
                <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">add</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span>, element, <span style="color: #ff4500;">0</span><span style="color: black;">&#41;</span>
            <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
                <span style="color: #ff7700;font-weight:bold;">return</span></pre></td></tr></table></div>


<h3>Обсуждение 2</h3>

<p>Эта реализация практически повторяет аналогичный код для лога событий, только упрощенный в силу
наличия всего одного ключа. По сравнению с первым вариантом реализации типа данных &laquo;массив&raquo; уменьшилось число операций
memcached, все изменяющие массив процессы могут выполняться без задержек (отсутствие блокировок).
Как и в первом варианте, не проверяется наличие дубликатов при добавлении элемента в массив
(может быть и хорошо, и плохо, в зависимости от применения).</p>

<p>Возможны следующие улучшения (или расширения) описанного примера:</p>

<ul>
<li>использование нескольких ключей для хранения массива вместо одного, распределение элементов
по ключам с использованием хэширования; такой вариант позволит ограничить размер каждого ключа,
при условии что массив большой (содержит много элементов);</li>
<li>реализация в том же стиле операции удаления элемента из массива, тогда массив можно представить
как последовательность операций &laquo;удалить&raquo; и &laquo;добавить&raquo;, например сериализованное представление
<code>+1 +3 +4 -3 +5</code> будет после десериализации образовывать массив <code>[1, 4, 5]</code>; при этом как 
операция добавления элемента, так и удаления, будет приводить к дописыванию байт в конец
сериализованного представления (атомарная операция <code>append</code>).</li>
</ul>

<h2>Таблица</h2>

<h3>Задача</h3>

<p>Необходимо хранить множество строк. Операции над множеством:</p>

<ul>
<li>проверка принадлежности строки множеству (самая частая операция);</li>
<li>получение множества целиком, добавление элемента, удаление элемента &#8211; редкие операции.</li>
</ul>

<p>Можно рассматривать данную структуру данных как таблицу, в которой осуществляется
быстрый поиск нужной строки. Или как хэш, хранящийся в распределенной памяти.</p>

<h3>Решение</h3>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> serializeArray<span style="color: black;">&#40;</span><span style="color: #dc143c;">array</span><span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">&quot;&quot;&quot;
    Сериализовать массив в бинарное представление.
    &quot;&quot;&quot;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> deserializeArray<span style="color: black;">&#40;</span><span style="color: #008000;">str</span><span style="color: black;">&#41;</span>:
    <span style="color: #483d8b;">&quot;&quot;&quot;
    Десериализовать массив из бинарного представления.
    &quot;&quot;&quot;</span>
&nbsp;
<span style="color: #ff7700;font-weight:bold;">class</span> MCTable<span style="color: black;">&#40;</span>MemcacheObject<span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">def</span> <span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, mc, name<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Конструктор.
&nbsp;
        @param name: имя таблицы
        @type name: C{str}
        &quot;&quot;&quot;</span>
        <span style="color: #008000;">super</span><span style="color: black;">&#40;</span>MCTable, <span style="color: #008000;">self</span><span style="color: black;">&#41;</span>.<span style="color: #0000cd;">__init__</span><span style="color: black;">&#40;</span>mc<span style="color: black;">&#41;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">lock</span> = MCLock<span style="color: black;">&#40;</span>name<span style="color: black;">&#41;</span>
        <span style="color: #008000;">self</span>.<span style="color: black;">key</span> = <span style="color: #483d8b;">'table'</span> + name
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> has<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, key<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Проверка наличия ключа в таблице.
&nbsp;
        @param key: ключ
        @type key: C{str}
        @rtype: C{bool}
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">try</span>:
            <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">get</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span> + <span style="color: #483d8b;">'_v_'</span> + key<span style="color: black;">&#41;</span>
            <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #008000;">True</span>
        <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
            <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #008000;">False</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> fetch<span style="color: black;">&#40;</span><span style="color: #008000;">self</span><span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Получить целиком значение элементов таблицы.
&nbsp;
        @return: значение таблицы
        @rtype: C{list(str)}
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">try</span>:
            <span style="color: #ff7700;font-weight:bold;">return</span> deserializeArray<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">get</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span> + <span style="color: #483d8b;">'_keys'</span><span style="color: black;">&#41;</span><span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
            <span style="color: #ff7700;font-weight:bold;">pass</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> add<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, key<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Добавить ключ в таблицу.
&nbsp;
        @param key: ключ
        @type key: C{str}
        &quot;&quot;&quot;</span>
        <span style="color: #ff7700;font-weight:bold;">while</span> <span style="color: #ff7700;font-weight:bold;">not</span> <span style="color: #008000;">self</span>.<span style="color: black;">lock</span>.<span style="color: black;">try_lock</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
            <span style="color: #ff7700;font-weight:bold;">pass</span>
&nbsp;
        <span style="color: #ff7700;font-weight:bold;">try</span>:
            <span style="color: #ff7700;font-weight:bold;">try</span>:
                <span style="color: #dc143c;">array</span> = deserializeArray<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: black;">get</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span> + <span style="color: #483d8b;">'_keys'</span><span style="color: black;">&#41;</span><span style="color: black;">&#41;</span>
            <span style="color: #ff7700;font-weight:bold;">except</span> <span style="color: #008000;">KeyError</span>:
                <span style="color: #dc143c;">array</span> = <span style="color: black;">&#91;</span><span style="color: black;">&#93;</span>
            <span style="color: #ff7700;font-weight:bold;">if</span> key <span style="color: #ff7700;font-weight:bold;">not</span> <span style="color: #ff7700;font-weight:bold;">in</span> <span style="color: #dc143c;">array</span>:
                <span style="color: #dc143c;">array</span>.<span style="color: black;">append</span><span style="color: black;">&#40;</span>key<span style="color: black;">&#41;</span>
            <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: #008000;">set</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span> + <span style="color: #483d8b;">'_v_'</span> + key, <span style="color: #ff4500;">1</span>, <span style="color: #ff4500;">0</span><span style="color: black;">&#41;</span>
            <span style="color: #008000;">self</span>.<span style="color: black;">mc</span>.<span style="color: #008000;">set</span><span style="color: black;">&#40;</span><span style="color: #008000;">self</span>.<span style="color: black;">key</span> + <span style="color: #483d8b;">'_keys'</span>, serializeArray<span style="color: black;">&#40;</span><span style="color: #dc143c;">array</span><span style="color: black;">&#41;</span>, <span style="color: #ff4500;">0</span><span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">finally</span>:
            <span style="color: #008000;">self</span>.<span style="color: black;">lock</span>.<span style="color: black;">unlock</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #ff7700;font-weight:bold;">def</span> delete<span style="color: black;">&#40;</span><span style="color: #008000;">self</span>, key<span style="color: black;">&#41;</span>:
        <span style="color: #483d8b;">&quot;&quot;&quot;
        Удалить ключ из таблицы.
&nbsp;
        Реализация аналогична методу add().
        &quot;&quot;&quot;</span></pre></td></tr></table></div>


<h3>Обсуждение</h3>

<p>Вообще говоря memcached представляет собой огромную хэш-таблицу, правда в ней отсутствует
одна операция, которая необходима для нашей структуры данных: получение списка ключей.
Поэтому реализация таблицы использует отдельные ключи для хранения каждого элемента таблицы,
и отдельно еще один ключ для хранения списка всех её элементов. Реализация хранения списка
всех элементов фактически совпадает с реализацией &laquo;массива 1&#8243;. Для сериализации доступа
к списку всех элементов используется блокировка, при этом методы <code>fetch</code> и <code>add</code> 
не синхронизированы друг с другом, т.к. список всех элементов меняется атомарно и при чтении
ключа мы всегда получим некоторое консистентное состояние.</p>

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

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

<h2>Заключение</h2>

<p>Итак, приведем список &laquo;приемов&raquo; или &laquo;трюков&raquo;, описанных в данной статье:</p>

<ul>
<li>атомарность операций с помощью memcached (пара <code>add</code>/<code>set</code> и т.п.);</li>
<li>блокировки;</li>
<li>теневые ключи;</li>
<li>кольцевой буфер с автоматическим &laquo;отмиранием&raquo; ключей;</li>
<li>блокировки и модель reader-writer.</li>
</ul>

<p>В статье не рассматривались вопросы оптимизации, специфичной для memcached, например,
использование multi-get запросов. Это делалось сознательно, чтобы не перегружать исходный код и рассказ.
Во многих ситуациях приведенные выше примеры следует рассматривать скорее как псевдокод, чем как пример
идеальной реализации на Python.</p>

<p>Если Вы нашли ошибку, хотите предложить более ясное, более оптимальное решение поставленным задачам,
хотите предложить реализацию для какой-то еще структуры данных &#8211; буду рад комментариям и критике.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/01/21/data-structures-in-memcached-memcachedb/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>PostgreSQL vs. MySQL</title>
		<link>http://www.smira.ru/2009/01/06/postgresql-vs-mysql/</link>
		<comments>http://www.smira.ru/2009/01/06/postgresql-vs-mysql/#comments</comments>
		<pubDate>Tue, 06 Jan 2009 20:48:06 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Smotri.Com]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[postgresql]]></category>
		<category><![CDATA[smotri.com]]></category>
		<category><![CDATA[тормозит]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=255</guid>
		<description><![CDATA[Тема этого поста была навеяна небольшим спором, разразившимся в ЖЖ. Эта битва будет вечной, как и война добра со злом, светлого и темного, FreeBSD и Linux, и т.п. Можно ломать копья, кричать &#171;кто круче&#187;, но спор ни к чему не приведет. Можно делать синтетические тесты, пытаясь определить, кто же быстрее, но один тест скажет, что [...]]]></description>
			<content:encoded><![CDATA[<p>Тема этого поста была навеяна небольшим спором, разразившимся в <a href="http://gornal.livejournal.com/82448.html?thread=947472#t947472">ЖЖ</a>. Эта битва будет вечной, как и война добра со злом, светлого и темного, FreeBSD и Linux, и т.п. Можно ломать копья, кричать &laquo;кто круче&raquo;, но спор ни к чему не приведет. Можно делать синтетические тесты, пытаясь определить, кто же быстрее, но один тест скажет, что MySQL быстрее на вставках, а другой &#8211; что PostgreSQL лучше масштабируется на многоядерных архитектурах. Найдутся рассказы о том, как всё стало классно, когда мы с MySQL перешли на PostgreSQL, найдутся и обратные истории.</p>

<p>Возвращаясь к теме &laquo;спора&raquo; с Горным (спора в кавычках, т.к. как такого спора не хотелось, да и не получилось): я не хочу никому сказать, что PostgreSQL &#8211; круче или что MySQL &#8211; полный отстой. Согласно опыту gornal, проект на PostgreSQL сделать нельзя и поэтому он ужасен, а аргументов вобщем-то других нет, я привел некоторые аргументы в чем PostgreSQL может быть лучше. Но не в этом дело. Совсем не в этом.</p>

<p>Господа, на PostgreSQL можно делать успешные проекты. Это классная, современная СУБД, она не &laquo;тормозит&raquo;, не бойтесь её. Она действительно работает, легко администрируется, устанавливается и т.п. К ней есть куча интереснейших расширений. Если Вы изучали SQL по мануалу MySQL, она Вам ничего не даст, но если Вы читали, например, <a href="http://www.ozon.ru/context/detail/id/2309312/">Дейта</a> или слушали курс по реляционной алгебре и о реляционных СУБД &#8211; в PostgreSQL Вы сможете воплотить БД своей мечты и оптимизировать её столько, сколько Вам захочется. Не бойтесь! Попробуйте, может, Вам понравится. Не понравится &#8211; есть MySQL, Informix, Oracle, DB2, Firebird и т.п. Но бояться не надо, она не тормозит, честное слово <img src='http://www.smira.ru/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>

<p>Всегда будут люди, которым нравятся какие-то продукты, например, ораклоид с пеной у рта будет доказывать, что кроме Oracle нет нормальных СУБД. Gornal скажет, что только на MySQL можно сделать успешный проект. Я люблю PostgreSQL и буду пользоваться им, покуда он будет подходить для тех задач, которые я решаю.</p>

<p>Не верьте тем, кто говорит, что PostgreSQL &#8211; отстой, попробуйте сами. Найдите для себя идеальную СУБД, попробуйте её возможности. Я использую PostgreSQL уже около четырех лет, но до сих пор открываю для себя что-то новое, есть специалисты по PostgreSQL, они знают огромное количество еще более интересного.</p>

<p>Напоследок: есть успешные проекты на PostgreSQL в мире веба и около него. Это Skype, Мой Круг и другие. И в сердце Smotri.Com как пламенный мотор трудится PostgreSQL, вся БД обслуживается по сути пятью серверами, один мастер и четыре слейва (Slony). Это самая простая на свете конструкция, её практически не оптимизировали с точки зрения специфики PostgreSQL. Я оцениваю возможности оптимизации в 2-3 раза без изменения логики приложения на сегодня. Дальнейшее возможно с изменением логики (например, шардинг или отгрузка части данных в MemcacheDB или что-то подобное).</p>

<p>Напоследок, российская специфика PostgreSQL:</p>

<ul>
<li><a href="http://postgresmen.ru/">PostgresMen</a></li>
<li><a href="http://www.sigaev.ru/">Фёдор Сигаев</a> и <a href="http://www.sai.msu.su/~megera/">Олег Бартунов</a> &#8211; активные разработчики PostgreSQL</li>
<li><a href="http://nikolay.samokhvalov.com/">Николай Самохвалов</a> &#8211; один из тех, кто сделал Мой Круг, а сегодня активно продвигает PostgreSQL в России</li>
<li>список можно было бы продолжить&#8230;</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2009/01/06/postgresql-vs-mysql/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>МТС и &#171;долг&#187; в тысячу рублей</title>
		<link>http://www.smira.ru/2008/12/20/mts-debt-1000-ruble/</link>
		<comments>http://www.smira.ru/2008/12/20/mts-debt-1000-ruble/#comments</comments>
		<pubDate>Sat, 20 Dec 2008 19:13:01 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Личное]]></category>
		<category><![CDATA[mts]]></category>
		<category><![CDATA[мтс]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=246</guid>
		<description><![CDATA[Эта история грустная, но со счастливым концом. И для начала хочется сказать спасибо компании МТС, что ситуация разрешилась в лучшую сторону. Итак, всё по порядку&#8230;

Обычным письмом приходит извещение от компании МТС, что на лицевом счету XXXXYYYY имеется непогашенная задолженность, что компания МТС скоро подаст в суд а данные мои передаст в бюро кредитных историй в [...]]]></description>
			<content:encoded><![CDATA[<p>Эта история грустная, но со счастливым концом. И для начала хочется сказать спасибо компании МТС, что ситуация разрешилась в лучшую сторону. Итак, всё по порядку&#8230;</p>

<p>Обычным письмом приходит извещение от компании МТС, что на лицевом счету XXXXYYYY имеется непогашенная задолженность, что компания МТС скоро подаст в суд а данные мои передаст в бюро кредитных историй в качестве злостного неплательщика. Я удивился, пошел в Интернет-помощника, выяснил какой телефонный номер привязан к этому лицевому счету, на нем нет задолженности (судя по балансу), да и не было &#8211; регулярные платежи каждый месяц или иногда даже чаще.</p>

<p>Звонок в службу поддержки, переводят меня на &laquo;финансовый департамент&raquo;, там милая девушка объясняет, что у каждого абонента имеется два баланса: один, который видит он, и, второй, &laquo;бухгалтерский&raquo;, который он не видит. Так вот, данные балансы разошлись, и, как легко догадаться, на внутреннем балансе числится долг в 1000 рублей, а на внешнем что-то типа +100 рублей (т.е. разница больше чем 1000 рублей). Она сообщила, что такое &laquo;бывает&raquo;, они будут разбираться и через неделю сообщат результаты.</p>

<p>Прошла неделя&#8230; Баланс счета (тот, который вижу я) стал -1000 рублей &#8212; надо думать, разобрались! Звоню, девушка думает и сообщает, что три года назад в 2005 году что-то не было засчитано правильно и с тех пор (sic!) за мной висит долг в 1000 рублей. Я спросил девушку, а как я могу это проверить или увидеть, потому что мне вобщем-то тяжело понять, как такое могло быть? Мне кажется, что она хотела сделать так, чтобы я больше не приставал с вопросами, и сказала, что я могу это увидеть в счетах, которые мне выставлялись за тот период (2005 год). Самым замечательным оказалось то, что как раз в то время я стал пользоваться Gmail, и одно из первых писем, сохраненных в моем ящике &#8211; это ежемесячные счета от МТС. Как раз в тот период на этом лицевом счете случился какой-то глюк биллинга МТС, который они исправляли около недели, разделяли счета, и т.п., и я даже был готов легко поверить, что именно тогда и могла образоваться такая задолженность. Я взглянул на счета за три месяца того периода, в них всё сходилось &#8211; баланс счета, платежи, расходы. Я снова позвонил в финансовую службу и спросил, как мне передать им эти счета, чтобы они мне показали, где же именно я оказался должен? Девушка мне предложила отправить письмо на адрес info@mts.ru, что я и сделал:</p>

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

<pre><code>Две недели назад мне пришло письмо о имеющейся задолженности по
лицевому счету 2XXXXXXXXXX.
При этом баланс счета в интернет-помощнике, например, был положительным.
Я обратился в контактный центр компании МТС, в ситуации пообещали разобраться.

Сегодня я обратился вновь, т.к. не так и не получил ответа. Мне
сообщили, что с моим счетом разобрались и выявлена задолженность в
сумме около 40 у.е., которая возникла за период октября-ноября 2005
года (даты не помню точно, но у вас есть эта информация). И что
начиная с декабря 2005 года между балансом в автоинформаторе и
балансом в счетах существовал разрыв в 40 у.е., что я могу увидеть на
счетах, присланных мне за этот период. Я нашел счета за указанный
период, которые были мне присланы в то время.

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

Также в письме, которое пришло 2 недели назад, компания МТС сообщала,
что мои данные могут быть переданы в бюро кредитных историй как
злостного неплательщика. Я бы хотел получить информацию о том,
произошло ли это действие, и гарантии того, что не произойдет в данной
ситуации, т.к. и по счетам, которые я получал, и по информации
автоинформатора мой счет не имел задолженности.

Если информация о задолженности не подтвердится, прошу вернуть сумму
на мой лицевой счет.
</code></pre>

<p>Никакого ответа я не получил (даже ответа вида &laquo;ваше сообщение в службу поддержки принято, его номер &laquo;2222&#8243;, к которому я привык даже в службах поддержки небольших компаний). Здесь же письмо ухнуло как в черную дыру. Через день я снова позвонил в контактный центр МТС и спросил девушку, когда мне стоит ждать ответа на обращение, на что она мне сообщила, что если это просто вопрос, то две недели, а если претензия &#8211; 45 дней. Я спросил девушку, а как определить, у меня претензия или вопрос? Ответа не получил. Я спросил, а 45 дней &#8211; этот срок чем установлен? Она засомневалась, пошла советоваться со старшей. Вернулась, и сообщила, что &laquo;установлен законом&raquo;. Я спросил: &laquo;Каким?&raquo; На что я получил ответ, потрясший меня до глубины души: &laquo;Конституцией&raquo;. Я на всякий случай уточнил: &laquo;Конституцией РФ?&raquo; На что получил утвердительный ответ. Вот такие люди работают в службе поддержки <img src='http://www.smira.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />  В конце концов я получил совет отправить еще одно письмо на info@mts.ru, чтобы узнать срок рассмотрения, ок, я отправил.</p>

<p>Через два дня я получил ответ, что не позднее недели, а еще через два и ответ на моё первое письмо:</p>

<pre><code>Благодарим Вас за пользование услугами компании МТС и в ответ на
Ваше обращение сообщаем, что на Вашем абонентском номере 916XXXXXXX
наблюдалось несовпадение реального баланса (ежемесячные счета на
услуги связи) и баланса, запрашиваемого  Вами по данному номеру.
Данная ситуация находилась под контролем у сотрудников технической
службы МТС и была устранена  05.11.2008г., после чего баланс Вашего
лицевого счета стал отрицательным и составил 1001,72 руб.
Мы сожалеем о возникшем недоразумении. Вследствие доставленных Вам
неудобств Компанией МТС принято решение о зачислении на Ваш лицевой
счет денежных средств в сумме 1200 руб., что соответствует размеру
уменьшения баланса. Указанная сумма зачислена на Ваш лицевой счет 10.11.2008г.
Заказав ежемесячные счета за период с 01.09.2008г. по 31.10.2008г.,
а в последующем и за ноябрь месяц, Вы сможете убедиться в том, что
на Вашем лицевом счете не совпадали реальный и запрашиваемый балансы.
В счетах, полученных Вами за 2005г., Вам представлена информация о
реальном балансе, но Вы, к сожалению, за давностью времени, не
можете ее сравнить с запрашиваемым Вами балансом на тот период
времени. Исходя из этого, Вы не можете увидеть несовпадение
реального баланса и баланса, запрашиваемого Вами.
Надеемся на Ваше понимание и дальнейшее сотрудничество. 
</code></pre>

<p>Результат &#8211; я ничего не понял, где баланс не сходился &#8211; в счетах он сходился, и в последних тоже, но не суть, может я правда чего-то не увидел, я не специалист. Деньги вернулись на счет, всё хорошо.</p>

<p>Дело не в деньгах, 1000 рублей &#8211; это еще не такая большая сумма, я готов заплатить, даже если я ничего не был должен. Обидно попасть в &laquo;бюро кредитных историй&raquo;, на этот вопрос, кстати, я ответа не получил до сих пор. Я понимаю, что письма неплательщикам рассылаются шаблонные, но вряд ли меня можно в полной мере считать таковым. Что меня несколько пугает &#8211; это то, что по сути любой сотовый оператор (и не только) может в какой-то момент сообщить, что у &laquo;вас имеется задолженность в сумме YYYYY рублей&raquo;. При этом количество нулей в этой сумме может быть практически произвольным. Расчеты с компанией у меня авансовые, то есть если образовалась задолженнность в 2005 году, то она не могла на самом деле тянуться два года (как не могла и в случае кредитных расчетов). А, самое главное, что если не эти копии счетов, сохранившиеся у меня почти случайно &#8211; ничего ведь доказать я не могу. Сумму поступлений на счет можно доказать чеками и т.п., но я никогда их не хранил, сумма расходов &#8211; только в биллинге оператора, и, по-хорошему, оператор в любой момент может увеличить сумму моих расходов за произвольный период или уменьшить сумму платежей &#8211; и доказать что-либо мне как клиенту будет очень сложно, а для суда они смогут сделать сколько угодно счетов с печатями, подтверждающими мой долг. А мне предоставить нечего, кроме электронных писем со счетами.</p>

<p>Вывод &#8211; хранить счета от оператора вечно. Иначе потом ничего никому не докажешь. </p>

<p>Еще раз спасибо МТСу, что деньги вернули, я так и не понял, был я должен их или нет, но если и был, то я этого не знал по вине их же биллинговой системы.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/12/20/mts-debt-1000-ruble/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>D. J. Bernstein</title>
		<link>http://www.smira.ru/2008/12/19/djb/</link>
		<comments>http://www.smira.ru/2008/12/19/djb/#comments</comments>
		<pubDate>Thu, 18 Dec 2008 22:25:14 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Разработка]]></category>
		<category><![CDATA[djb]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=244</guid>
		<description><![CDATA[Кто такой D. J. Bernstein или просто djb? С моей точки зрения, в первую очередь, &#8211; это интереснейший человек. Людям из мира web он больше всего известен как автор интересных, безопасных и быстрых программ: qmail и djbdns. Эти программы обладают архитектурой, непохожей на все привычные серверные решения: Apache, BIND, postfix и т.п. Они пропитаны духом [...]]]></description>
			<content:encoded><![CDATA[<p>Кто такой D. J. Bernstein или просто djb? С моей точки зрения, в первую очередь, &#8211; это интереснейший человек. Людям из мира web он больше всего известен как автор интересных, безопасных и быстрых программ: <a href="http://cr.yp.to/qmail.html">qmail</a> и <a href="http://cr.yp.to/djbdns.html">djbdns</a>. Эти программы обладают архитектурой, непохожей на все привычные серверные решения: Apache, BIND, postfix и т.п. Они пропитаны духом и философией UNIX, не демоны, пожирающие мегабайты памяти и решающие тысячи задач, а простые, маленькие программы, простые форматы файлов, четко определенные цели и взаимодействия, всё написано одновременно просто и в тоже время надежно.</p>

<p>А кто в курсе, что D. J. Bernstein еще и математик? Что он является профессором и преподает в <a href="http://cr.yp.to/positions.html">University of Illinois at Chicago</a>, опубликовал большое количество <a href="http://cr.yp.to/papers.html">статей</a>, каждая из которых производит впечатление выверенной, педантичной, качественной работы, какой и должна быть математическая статья. В основном статьи так или иначе посвящены теме криптографии, и, особенно, вопросам &laquo;быстрой&raquo; криптографии, когда криптографический алгоритм может быть использован на маломощных устройствах или для обработки больших потоков данных. </p>

<p>DJB не является профессиональным юристом, однако он <a href="http://cr.yp.to/export.html">&laquo;засудил&raquo; правительство США</a> (дело Bernstein v. United States) по поводу экспортных ограничений на системы безопасности (и, в частности, криптографии). Bernstein выступает против патентов на алгоритмы и идеи, пытаясь доказать <a href="http://cr.yp.to/patents.html">несостоятельность</a> существующих патентов (например, по причине того, что до подачи патентной заявки идеи были опубликованы). DJB и <a href="http://cr.yp.to/writing/ieee.html">против</a> попыток издательств получить экслюзивные права на научные публикации, чтобы потом по сути органичить доступ к ним, сделав его платным.</p>

<p>DJB дотошен, я бы сказал, что он &laquo;зануда&raquo;. Он может потратить не один день на рассказ о том, как <a href="http://cr.yp.to/uic.html">распределяются и крадутся</a> деньги грантов в его университете UIC (ничего не напоминает?). При этом он пытается бороться с этой системой. Вряд ли его &laquo;любят&raquo;, потому что он говорит многие вещи прямо. Он может свободно <a href="http://cr.yp.to/djbdns/blurb.html">осуждать</a> Paul Vixie, ISC, Nominum и других за реализацию BIND, по сути говоря о том, что они не могут написать хорошее программное обеспечение, &laquo;трясут&raquo; деньги за поддержку. При этом в BIND постоянно обнаруживаются проблемы безопасности, а его авторы предлагают заплатить деньги, чтобы узнать о них и получть патч, сознательно задерживая выход исправленной версии. DJB расскажет <a href="http://cr.yp.to/conferences/russia.html">историю</a> своей поездки на конференцию в Санкт-Петербург в стиле &laquo;и по улице у них бродят медведи&raquo;. Он <a href="http://cr.yp.to/djbdns/ipv6mess.html">раскритикует</a> IPv6 и планы его внедрения (с моей точки зрения, совершенно справедливо). DJB расскажет и о том, как надо и как не надо <a href="http://cr.yp.to/writing.html">писать</a> статьи.</p>

<p>Иногда хочется спросить, как он всё это успевает? Как удается человеку оставаться таким принципиальным? Как в нём одновременно уживается талант математика, профессора, программиста. Как одновременно он зануден в проведении очередного математического доказательства, в оптимизации на уровне еще одного такта ассемблерного кода для разных арихитектур, в выработке концепции безопасности программного обеспечения. Не знаю как, но, с моей точки зрения, он &#8211; выдающийся человек, у которого можно и нужно учиться.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/12/19/djb/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CDN своими руками или раздача видеоконтента</title>
		<link>http://www.smira.ru/2008/12/03/cdn-content-delivery/</link>
		<comments>http://www.smira.ru/2008/12/03/cdn-content-delivery/#comments</comments>
		<pubDate>Wed, 03 Dec 2008 06:23:48 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Smotri.Com]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[cdn]]></category>
		<category><![CDATA[content]]></category>
		<category><![CDATA[видео]]></category>
		<category><![CDATA[доставка]]></category>
		<category><![CDATA[контент]]></category>
		<category><![CDATA[разработка]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=232</guid>
		<description><![CDATA[В продолжение темы про доставку видеоконтента: мы обеспечили хранение и обработку контента, как теперь отдать контент таким образом, чтобы он оказался как можно &#171;ближе&#187; к потребителю. Большая часть статьи будет посвящена обобщенному подходу географически распределенной раздачи контента, а в конце в качестве примера он будет применен к доставке видеофайлов и вещаний конечным пользователям.

Необходимость географической распределенности

Кроме [...]]]></description>
			<content:encoded><![CDATA[<p>В продолжение темы про <a href="http://www.smira.ru/2008/12/02/video-broadcast-delivery/">доставку видеоконтента</a>: мы обеспечили хранение и обработку контента, как теперь отдать контент таким образом, чтобы он оказался как можно &laquo;ближе&raquo; к потребителю. Большая часть статьи будет посвящена обобщенному подходу географически распределенной раздачи контента, а в конце в качестве примера он будет применен к доставке видеофайлов и вещаний конечным пользователям.</p>

<h2>Необходимость географической распределенности</h2>

<p>Кроме самого факта, что контент был доставлен пользователю, мы должны обеспечить качество доставки контента. Для FLV-файла видео это означает, что скорость, с которой он доставляется пользователю, должна быть выше либо равна битрейта потока, иначе видео у пользователя при просмотре будет «затыкаться».</p>

<p>Кроме того, имеет смысл «приблизить» контент к пользователю географически. Это связано с пропускной способностью каналов (отсутствием иногда хороших магистральных каналов), а также с разницей в стоимости локального и внешнего трафика для конечного пользователя (например, в регионах РФ).</p>

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

<p>Таким образом, осознав необходимость географической распределенности для контента, мы покупаем/арендуем сервера в непосредственной близости от потребителя: в Европе, США, Украине, Екатеринбурге и т.д. Что же делать дальше?</p>

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

<h2>Механизм работы</h2>

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

<p><img src="http://www.smira.ru/wp-content/uploads/2008/12/visitor.png" alt="Посетитель IP" title="Посетитель - GeoIP" width="358" height="176" class="aligncenter size-medium wp-image-233" /></p>

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

<p><img src="http://www.smira.ru/wp-content/uploads/2008/12/resources.png" alt="Ресурс и расположение на серверах" title="Ресурс и расположение на серверах" width="427" height="215" class="aligncenter size-full wp-image-236" /></p>

<h2>Выбор ближайшего к посетителю ресурса</h2>

<p>Теперь у нас есть посетитель, его местоположение, ресурс, который он хочет получить вместе со всеми его копиями и местами их расположения. Теперь необходимо выбрать именно тот ресурс, который ближе всего к пользователю. Как это можно сделать?
Логично было бы посчитать расстояние от посетителя до ресурса и всех его копий и выбрать самый близкую к посетителю копию ресурса. Каким образом задать такое расстояние?</p>

<p><img src="http://www.smira.ru/wp-content/uploads/2008/12/graph.png" alt="Граф расстояний между городами" title="Граф расстояний между городами" width="439" height="259" class="aligncenter size-full wp-image-237" /></p>

<p>Это расстояние не всегда совпадает с расстоянием на карте между двумя точками, и является скорее мерой качества и пропускной способности каналов между регионами, странами или городами (или даже между отдельными провайдерами). В первом приближении достаточно задать расстояние между отдельными странами и городами. Пример такой расстановки расстояний приведен на рисунке выше.</p>

<p>Таким образом, мы получаем взвешенный ориентированный граф, на котором необходимо решить задачу поиска кратчайшего пути (минимизация суммы весов ребер графа, входящих в этот путь). Это классическая задача, которая может быть легко решена за полиномиальное время. Граф хоть и является слабосвязанным, но всё-таки его размеры достаточно велики, поэтому в реальном времени для каждого посетителя выполнять такой расчет не является самым эффективным решением. Но мы можем немного «схитрить»: мы знаем, что при всех поисках кратчайшего пути конечным пунктом пути будут места, где расположены сервера с ресурсами, таким образом, число различных конечный точек искомого пути фиксировано и относительно невелико. Теперь нам достаточно рассчитать и закэшировать, например, в memcached, длины кратчайших путей от всех вершин графа (где могут располагаться посетители нашего сайта) до мест расположения ресурсов.</p>

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

<h3>Копирование ресурса</h3>

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

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

<p><img src="http://www.smira.ru/wp-content/uploads/2008/12/formula.png" alt="Формула вычисления бонуса" title="Формула вычисления бонуса" width="144" height="45" class="aligncenter size-full wp-image-238" /></p>

<p>где <em>расстояние</em> – это расстояние от посетителя до данного географического места, а <em>k</em> – некоторый коэффициент, который задает то, насколько быстро ресурс будет перемещен на указанный сервер. Если нет пути от посетителя до данного места (т.е. посетитель далеко), то расстояние будет равно бесконечности, а бонус будет равен нулю. Если же путь существует, то ресурс будет скопирован на сервер в данной точке тем быстрее, чем ближе он к посетителю и чем больше таких посетителей попросят доступ к данному ресурсу.
Как только бонус превышает некоторый порог, осуществляется копирование ресурса на сервер, расположенный в данном географическом месте, и в дело уже вступает алгоритм выбора копии ресурса, описанный выше.</p>

<h2>Географически распределенные видеофайлы и вещания</h2>

<p>Теперь мы можем применить описанный выше подход к контенту видеохостинга: вещаниям и видео.</p>

<p><strong>Видеофайлы</strong>. В этом случае ресурс – это сам файл видео (причём любого типа, как FLV, так, например, и оригинал видео). Копии ресурса – копии файла, находящиеся на зеркалирующих файловых серверов. Обращение к ресурсу – скачивание файла, проигрывание FLV-видео посетителем в Flash Player’е. Копирование ресурса – это просто копирование файла с основного файлового сервера на один из зеркалирующих файловых серверах, расположенных в различных точках.</p>

<p><strong>Вещания</strong>. Для вещаний ситуация очень похожа, здесь ресурсом является, конечно же само вещание, расположенное на основном для него сервере вещания (том сервере, куда подключен автор вещания). Копии ресурса – это все ретрансляции данного вещания на других серверах вещания. Обращение к ресурсу – это «вход» в вещание нового посетителя. Копирование ресурса – открытие ретрансляции на сервере вещаний, расположенном в той или иной точке мира.</p>

<p>В случае вещаний интересным является то, что ретрансляция является одновременно способом борьбы с высокой нагрузкой и средством приближения контента к потребителю, то есть ретрансляция осуществляется одновременно в двух плоскостях: с одной стороны, удовлетворить запросы всех пользователей наиболее близко расположенным контентом. А с другой стороны необходимо обеспечить в каждой географической точке такое количество ретрансляций, чтобы нагрузка на сервера вещаний оставалась в норме.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/12/03/cdn-content-delivery/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Доставка видеоконтента пользователям</title>
		<link>http://www.smira.ru/2008/12/02/video-broadcast-delivery/</link>
		<comments>http://www.smira.ru/2008/12/02/video-broadcast-delivery/#comments</comments>
		<pubDate>Tue, 02 Dec 2008 07:27:10 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Smotri.Com]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[nms]]></category>
		<category><![CDATA[pyfms]]></category>
		<category><![CDATA[вещание]]></category>
		<category><![CDATA[видео]]></category>
		<category><![CDATA[разработка]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=223</guid>
		<description><![CDATA[Что такое «контент» для видеохостинга? Во-первых, контент видеохостинга – это просто видео, которое представляет собой набор файлов в различных форматах, в частности, в формате FLV для просмотра пользователем через Flash Player. Эти файлы статичны, видеохостинг при загрузке пользователем видеоролика осуществляет конвертацию во все требуемые форматы с необходимым битрейтом. Хранение такого контента — это хранение обычных [...]]]></description>
			<content:encoded><![CDATA[<p>Что такое «контент» для видеохостинга? Во-первых, контент видеохостинга – это просто видео, которое представляет собой набор файлов в различных форматах, в частности, в формате FLV для просмотра пользователем через Flash Player. Эти файлы статичны, видеохостинг при загрузке пользователем видеоролика осуществляет конвертацию во все требуемые форматы с необходимым битрейтом. Хранение такого контента — это хранение обычных файлов, только довольно большого размера. Отдача контента — это, по сути, организация скачивания файлов.
Во-вторых, контент видеохостинга — это «живые» потоки или вещания. Вещания не записываются на диск, не происходит их конвертация, потоки раздаются клиентам с учетом пропускной способности каналов (происходит пропуск пакетов, если канал клиента недостаточен для получения потока вещания в полном качестве). Отдача контента в данной ситуации — это раздача потока на большое количество подключенных пользователей (тысячи смотрящих).</p>

<p>Кроме как таковой задачи отдачи контента (корректность, работа под нагрузкой и т.п.) актуальной является проблема «приближения» контента к пользователю. Необходимо организовать доставку контента так, чтобы пользователь получал видео или вещание с сервера, который расположен близко к нему с точки зрения пропускной способности сетевых каналов, а также с точки зрения стоимости трафика для пользователя.</p>

<p>Данная статья, написанная по материалам <a href="http://www.smira.ru/2008/09/28/rit-highload-2008/">доклада</a> на конференции «РИТ: Высокие нагрузки»-2008, расскажет об одном из возможных подходов к решению поставленных задач. Рассказ будет основан на нашем опыте проектирования, разработки и поддержки видеохостинга <a href="http://smotri.com/">Smotri.Сom</a>, который является сегодня самым крупным видеохостингом Рунета. Описываемые подходы могут быть применимы в областях с похожими характеристиками контента: достаточно большой объем файлов, много файлов, различные виды вещаний и т.п.</p>

<p>Статья будет состоять из двух частей: в первой части речь пойдет об организации файлового хранилища для видеофайлов и об общих аспектах организации вещания. Во второй части будет представлен способ организации CDN (<a href="http://en.wikipedia.org/wiki/Content_Delivery_Network">Content Delivery Network</a>), то есть способ «приближения» контента к конечному потребителю, а также применение этого подхода к доставке статических файлов (файлов видео) и к потоковой трансляции (вещания).</p>

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

<h2>Видео: организация файлового хранилища</h2>

<h3>Видеофайлы</h3>

<p>Видеохостинг в процессе своего функционирования получает видео в различных форматах, которое загружается пользователями. Кроме хранения оригинального видеофайла, видеохостинг осуществляет конвертацию файла в ряд форматов: <a href="http://ru.wikipedia.org/wiki/Flash_Video">FLV</a>, <a href="http://ru.wikipedia.org/wiki/3gp">3GP</a>, <a href="http://en.wikipedia.org/wiki/MPEG-4">MP4</a> и т.п. Все эти файлы необходимо хранить на файловых серверах, а также отдавать пользователям. Рассмотрим сначала вопрос хранения таких файлов.</p>

<p>Что представляют из себя эти файлы? Согласно нашим расчетам, в среднем для хранения одной секунды видео требуется 250 Кб дискового пространства (имеется в виду 250Кб/с на все форматы видео вместе взятые), при этом средняя длительность видео составляет примерно 4 минуты. Легко посчитать, что для хранения 1 млн. видеофайлов нам потребуется 60 Тб дискового пространства, что является уже достаточно внушительной цифрой. Этим объемом хранения надо эффективно управлять, а также обеспечивать раздачу такого объема контента пользователям. Кроме непосредственно видеофайлов нам придётся хранить и отдавать вырезанные из видео кадры, которых в нашем случае будет 15 штук: 5 кадров, вырезанные из разных участков видео, каждый из которых представлен в трех различных размерах (для показа информации о видео в блоках разного формата).</p>

<h3>Доступ к файловому серверу через WebDAV</h3>

<p>Непосредственно за хранение файла отвечает файловый сервер, в котором есть достаточно хорошо построенная дисковая подсистема в плане надежности и скорости доступа (RAID). Запросы на изменение файлов (создание новых файлов, удаление старых и т.п.) поступают с другой группы серверов: с «морд» (frontend) и с перекодировщиков. Морды – это обычные сервера, которые обслуживают подавляющее большинство запросов по HTTP как непосредственно к самому сайту, так и к его API. Перекодировщики принимают запросы от пользователей на загрузку исходных файлов видео и осуществляют их конвертацию. Итак, на морде или перекодировщике формируется заказ на операцию с файлами на файловом сервере, т.к. файловая система не является локальной, необходимо то или иное сетевое средство доступа к файловому серверу.</p>

<p><img src="http://www.smira.ru/wp-content/uploads/2008/12/fileserver.png" alt="Схема файлового сервера" title="Файловый сервер" width="525" height="254" class="aligncenter size-full wp-image-225" /></p>

<p>Самое простое решение – это <a href="http://ru.wikipedia.org/wiki/Network_File_System">NFS</a>, которое может сохранить для кода сайта «ощущение», что файлы находятся локально, забирая при этом на себя все сложности по выполнению удаленных операций через сеть. Однако NFS может вести себя крайне ненадежно при падении сетевого соединения между мордой и файловым сервером, а также в силу того, что и морд, и файловых серверов десятки, количество кросс-маунтов NFS становится слишком большим. </p>

<p>Самым простым решением в такой ситуации может быть WebDAV. <a href="http://ru.wikipedia.org/wiki/WebDAV">WebDAV</a> является расширением HTTP, который, кстати, изначально предполагал, что содержимое World Wide Web является не статичным, а изменяемым. WebDAV делает еще один шаг вперед, добавляя в HTTP полноценные возможности по удаленному изменению, созданию, удалению файлов, каталогов, получению информации о файлах и т.п.</p>

<p>Внутри кластера серверов видеохостинга обращение к файловым серверам идёт по протоколу WebDAV. Скачивание файлов (в том числе проигрывание видео в Flash Player) идёт напрямую с файлового сервера, что позволяет более равномерно загрузить сетевые каналы, исключая «лишние» промежуточные звенья.</p>

<h3>Выбор файлового сервера из кластера</h3>

<p>Предположим, у нас есть кластер файловых серверов, все настроены, готовы отдавать файлы, а также принимать новые файлы через WebDAV. Было загружено видео. Какой файловый сервер выбрать для хранения очередного файла? Выбор можно осуществить, например, на основе следующих характеристик:</p>

<ul>
<li>объем свободного места/объем сервера;</li>
<li>нагрузка на сервер (как измерять?);</li>
<li>случайный выбор.</li>
</ul>

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

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

<h3>Необходимое ПО</h3>

<p>Описанный  выше подход требует простого и распространенного программного обеспечения: для скачивания файлов подойдет любой «быстрый» HTTP-сервер, например, <a href="http://sysoev.ru/nginx/">nginx</a>, <a href="http://www.lighttpd.net/">lighttpd</a> и т.п. При этом для обеспечения подгрузки FLV в плеер с любой временнóй отметки (так называемого «стриминга»), нам необходимо очень простое расширение, которое сегодня присутствует как в <a href="http://sysoev.ru/nginx/docs/http/ngx_http_flv_module.html">nginx</a>, так и в <a href="http://blog.lighttpd.net/articles/2006/03/09/flv-streaming-with-lighttpd">lighttpd</a> (flv-streaming). В качестве WebDAV-сервера подойдет Apache <a href="http://httpd.apache.org/">httpd</a> и т.п. Для доступа к файлам по WebDAV можно использовать любой WebDAV-клиент, который есть в том или ином виде практически во всех языках программирования. Так, например, в PHP он реализован в виде PEARовского класса (который приходится «допиливать», чтобы обеспечить разумную производительность). WebDAV не обеспечивает функциональности по получению объема свободного места на диске, но это легко обойти с помощью простейшего скрипта в cron, который вывод команды df перенаправляет в файл в корне файлового сервера, а этот файл уже можно скачать по HTTP с любой морды.</p>

<h3>Кросс-бэкап</h3>

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

<p><img src="http://www.smira.ru/wp-content/uploads/2008/12/crossbackup.png" alt="Кросс-бэкап" title="Кросс-бэкап" width="357" height="173" class="aligncenter size-full wp-image-228" /></p>

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

<h2>Вещания: ретрансляция</h2>

<p>Подробный рассказ про организацию вещаний и нашем сервере вещаний был уже представлен на конференции <a href="http://www.smira.ru/2008/04/09/rit-2008/">РИТ-2008</a>, поэтому здесь я не буду повторяться и вдаваться в подробности, остановлюсь лишь на основных аспектах.</p>

<p>Вещание представляет из себя видео и аудио потоки, которые кодируются на стороне клиента с помощью Flash Player, затем с помощью протокола RTMP отправляются на сервер вещаний, который раздает эти RTMP-потоки всем подключенным к вещанию зрителям. Здесь основной проблемой является то, что поток раздается клиентам хоть и без перекодирования (в качестве и с битрейтом, заданным автором трансляции), но количество клиентов, подключенных к вещанию, может составлять тысячи человек. При этом каждому клиенту может доставляться измененный поток в соответствие с пропускной способностью каждого клиента (пропуск пакетов в потоке). Кроме того, в RTMP-потоке кроме собственно потока вещания происходят удаленные вызовы процедур (сервер-клиент или клиент-сервер), а также транслируется состояние разделяемых объектов между всеми подключенными к вещанию клиентами.</p>

<p><img src="http://www.smira.ru/wp-content/uploads/2008/12/pyfms.png" alt="pyFMS NMS схема ретрансляции" title="pyFMS NMS ретрансляция" width="522" height="213" class="aligncenter size-full wp-image-229" /></p>

<p>Один сервер не всегда может справиться с задачей раздачи потока вещания, поэтому мы прибегаем к ретрансляции: автор вещания подключается к первичному серверу вещаний, к которому в качестве клиентов присоединяются другие сервера вещаний, которые уже в свою очередь раздают поток конечным зрителям вещания. При такой схеме оказывается возможным равномерно распределить нагрузку между серверами вещаний (количество клиентов на каждом сервере оказывается примерно одинаковым).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/12/02/video-broadcast-delivery/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Проверка орфографии в Vim</title>
		<link>http://www.smira.ru/2008/11/17/russian-vim-spell-checking/</link>
		<comments>http://www.smira.ru/2008/11/17/russian-vim-spell-checking/#comments</comments>
		<pubDate>Mon, 17 Nov 2008 05:27:40 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Разработка]]></category>
		<category><![CDATA[checker]]></category>
		<category><![CDATA[russian]]></category>
		<category><![CDATA[spell]]></category>
		<category><![CDATA[vim]]></category>
		<category><![CDATA[орфография]]></category>
		<category><![CDATA[проверка]]></category>
		<category><![CDATA[русский]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=210</guid>
		<description><![CDATA[Чудесная возможность Vim&#8217;а! Да поможет она повышению грамотности наших разработчиков. Проверка орфографии умная, понимает, что именно в исходном файле стоит проверять (например, комментарии), а что не стоит (например, ключевые слова).

Итак:


Качаем отсюда
словарь: http://ftp.services.openoffice.org/pub/OpenOffice.org/contrib/dictionaries/ru_RU.zip
Раскрываем архив в папку /tmp/dict.
mkdir -p ~/.vim/spell/


Запускаем Vim, делаем:

:mkspell! ~/.vim/spell/ru /tmp/dict/ru_RU


Вуаля! Словарь готов! (Всё описанное выше &#8211; подготовительный этап, это надо сделать всего один [...]]]></description>
			<content:encoded><![CDATA[<p>Чудесная возможность Vim&#8217;а! Да поможет она повышению грамотности наших разработчиков. Проверка орфографии умная, понимает, что именно в исходном файле стоит проверять (например, комментарии), а что не стоит (например, ключевые слова).</p>

<p>Итак:</p>

<ol>
<li>Качаем <a href="http://wiki.services.openoffice.org/wiki/Dictionaries#Russian_.28Russia.29">отсюда</a>
словарь: <a href="http://ftp.services.openoffice.org/pub/OpenOffice.org/contrib/dictionaries/ru_RU.zip">http://ftp.services.openoffice.org/pub/OpenOffice.org/contrib/dictionaries/ru_RU.zip</a></li>
<li>Раскрываем архив в папку <code>/tmp/dict</code>.</li>
<li><code>mkdir -p ~/.vim/spell/</code></li>
</ol>

<p>Запускаем Vim, делаем:</p>

<pre><code>:mkspell! ~/.vim/spell/ru /tmp/dict/ru_RU
</code></pre>

<p>Вуаля! Словарь готов! (Всё описанное выше &#8211; подготовительный этап, это надо сделать всего один раз).</p>

<p>Включение проверки на русском и английском (для текущего буфера):</p>

<pre><code>:setlocal spell spelllang=ru_ru,en_us
</code></pre>

<p>Дополнительно о проверке орфографии:</p>

<pre><code>:help spell
</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/11/17/russian-vim-spell-checking/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Open Source: Deferred для qooxdoo</title>
		<link>http://www.smira.ru/2008/11/01/open-source-deferred-qooxdoo/</link>
		<comments>http://www.smira.ru/2008/11/01/open-source-deferred-qooxdoo/#comments</comments>
		<pubDate>Sat, 01 Nov 2008 09:02:35 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Разработка]]></category>
		<category><![CDATA[deferred]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[qooxdoo]]></category>
		<category><![CDATA[twisted]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=205</guid>
		<description><![CDATA[Мы представляем начало нашего маленького проекта по выкладыванию в open-source исходного кода наших проектов (полностью или частично). Первой ласточкой становится маленькая библиотека, предназначенная для работы с Deferred из Twisted Framework в qooxdoo на JavaScript.

Практически полностью код был взят из MochiKit.Async и адаптирован под qooxdoo. Полезные нововедения: если если в течение 1 секунды не будет обработана [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://netstream.ru">Мы</a> представляем начало нашего маленького <a href="http://code.netstream.ru">проекта</a> по выкладыванию в open-source исходного кода наших проектов (полностью или частично). Первой ласточкой становится маленькая библиотека, предназначенная для работы с <a href="http://twistedmatrix.com/projects/core/documentation/howto/defer.html">Deferred</a> из <a href="http://twistedmatrix.com/">Twisted Framework</a> в <a href="http://qooxdoo.org/">qooxdoo</a> на JavaScript.</p>

<p>Практически полностью код был взят из <a href="http://mochikit.com/doc/html/MochiKit/Async.html">MochiKit.Async</a> и адаптирован под qooxdoo. Полезные нововедения: если если в течение 1 секунды не будет обработана ошибка в Deferred (выполнение дойдет до конца цепочки callback, и останется ошибка), о ней будет сообщено на консоль, как о возможно необработанном исключении. </p>

<p>Если Вы еще не знаете, что такое Deferred, я бы рекомендовал обратиться к <a href="http://twistedmatrix.com/projects/core/documentation/howto/index.html">Twisted Handbook</a>. Со своей стороны обещаю как можно скорее написать о Deferred по-русски.</p>

<p>Итак, страничка <a href="http://code.netstream.ru/wiki/QooxdooTwistedDeferred">qx.Deferred</a>.</p>

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

<p>Пример кода:</p>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
</pre></td><td class="code"><pre class="javascript" style="font-family:monospace;"><span style="color: #003366; font-weight: bold;">function</span> callRPC<span style="color: #009900;">&#40;</span>method<span style="color: #339933;">,</span> params<span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
    <span style="color: #000066; font-weight: bold;">this</span>.<span style="color: #660066;">debug</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">&quot;API: call &quot;</span> <span style="color: #339933;">+</span> method <span style="color: #339933;">+</span> <span style="color: #3366CC;">&quot; with params: &quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000066; font-weight: bold;">this</span>.<span style="color: #660066;">debug</span><span style="color: #009900;">&#40;</span>params<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #003366; font-weight: bold;">var</span> d <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">new</span> netstream.<span style="color: #660066;">lib</span>.<span style="color: #660066;">Deferred</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #003366; font-weight: bold;">var</span> rpc <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">new</span> qx.<span style="color: #660066;">io</span>.<span style="color: #660066;">remote</span>.<span style="color: #660066;">Rpc</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    rpc.<span style="color: #660066;">setTimeout</span><span style="color: #009900;">&#40;</span><span style="color: #CC0000;">10000</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    rpc.<span style="color: #660066;">setUrl</span><span style="color: #009900;">&#40;</span><span style="color: #000066; font-weight: bold;">this</span>.__api_url<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    rpc.<span style="color: #660066;">callAsync</span><span style="color: #009900;">&#40;</span>
        <span style="color: #003366; font-weight: bold;">function</span> <span style="color: #009900;">&#40;</span>result<span style="color: #339933;">,</span> ex<span style="color: #339933;">,</span> id<span style="color: #009900;">&#41;</span>
        <span style="color: #009900;">&#123;</span>
            <span style="color: #000066; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span>ex <span style="color: #339933;">==</span> <span style="color: #003366; font-weight: bold;">null</span><span style="color: #009900;">&#41;</span>
                d.<span style="color: #660066;">callback</span><span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
            <span style="color: #000066; font-weight: bold;">else</span>
                d.<span style="color: #660066;">errback</span><span style="color: #009900;">&#40;</span>ex<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
        <span style="color: #009900;">&#125;</span><span style="color: #339933;">,</span>
        method<span style="color: #339933;">,</span> params<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #003366; font-weight: bold;">var</span> that <span style="color: #339933;">=</span> <span style="color: #000066; font-weight: bold;">this</span><span style="color: #339933;">;</span>
    d.<span style="color: #660066;">addCallback</span><span style="color: #009900;">&#40;</span><span style="color: #003366; font-weight: bold;">function</span> <span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> that.<span style="color: #660066;">debug</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">&quot;API: result:&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> that.<span style="color: #660066;">debug</span><span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #000066; font-weight: bold;">return</span> result<span style="color: #339933;">;</span> <span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    d.<span style="color: #660066;">addErrback</span><span style="color: #009900;">&#40;</span><span style="color: #003366; font-weight: bold;">function</span> <span style="color: #009900;">&#40;</span>ex<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> that.<span style="color: #660066;">error</span><span style="color: #009900;">&#40;</span>ex<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #000066; font-weight: bold;">return</span> ex<span style="color: #339933;">;</span> <span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000066; font-weight: bold;">return</span> d<span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
&nbsp;
d <span style="color: #339933;">=</span> callRPC<span style="color: #009900;">&#40;</span><span style="color: #3366CC;">&quot;somemethod&quot;</span><span style="color: #339933;">,</span> <span style="color: #009900;">&#123;</span> <span style="color: #3366CC;">'someParams'</span> <span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
d.<span style="color: #660066;">addCallback</span><span style="color: #009900;">&#40;</span><span style="color: #003366; font-weight: bold;">function</span> <span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span> <span style="color: #000066;">alert</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">&quot;Got result: &quot;</span><span style="color: #339933;">+</span>result<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span> <span style="color: #009900;">&#125;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></td></tr></table></div>

]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/11/01/open-source-deferred-qooxdoo/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Memcached: статистика, отладка и RPC</title>
		<link>http://www.smira.ru/2008/10/31/web-caching-memcached-6/</link>
		<comments>http://www.smira.ru/2008/10/31/web-caching-memcached-6/#comments</comments>
		<pubDate>Fri, 31 Oct 2008 11:18:41 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Разработка]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[rpc]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[кэширование]]></category>
		<category><![CDATA[отладка]]></category>
		<category><![CDATA[статистика]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=187</guid>
		<description><![CDATA[Серия постов про &#171;Web, кэширование и memcached&#187; продолжается. Начало здесь: 1, 2, 3, 4 и 5.
В этих постах мы поговорили о 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>, <a href="http://www.smira.ru/2008/10/28/web-caching-memcached-4/">4</a> и <a href="http://www.smira.ru/2008/10/29/web-caching-memcached-5/">5</a>.
В этих постах мы поговорили о memcached, его архитектуре, возможном применении, выборе ключа кэширования, кластеризации, атомарных операциях и реализации счетчиков в <a href="http://danga.com/memcached/">memcached</a>, а также о проблеме одновременного перестроения кэшей и тэгировании кэшей.</p>

<p>Сегодняшний пост завершает эту серию, в нём обзорно мы поговорим о технических &laquo;мелочах&raquo;:</p>

<ul>
<li>анализ статистики memcached;</li>
<li>отладка memcached;</li>
<li>&laquo;RPC&raquo; с помощью memcached.</li>
</ul>

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

<p>Полный текст всех разделов в виде одной большой PDF-ки можно скачать и посмотреть <a href="http://www.smira.ru/2008/10/08/highload-plus-plus-2008/">здесь</a> (в разделе &laquo;Материалы&raquo;).</p>

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

<h2>Статистика работы memcached</h2>

<p>Кроме необходимости реализовать механизмы работы с memcached, необходимо постоянно заниматься мониторингом кластера memcached-серверов, чтобы быть уверенным, что мы достигли оптимальной производительности. Memcached предоставляет набор команд для получения информации о его работе.</p>

<p>Самая простая команда, <code>stats</code>, позволяет получить элементарную статистику: время работы сервера (uptime), объем используемой памяти, количество get запросов и  количество хитов (hits), т.е. попаданий в кэш. Их соотношение позволяет нам судить об эффективности кэширования в целом, хотя необходимо учитывать, что в memcached ключами являются не только закэшированные выборки, но и счетчики, блокировки, тэги и т.п., так что для вычисления чистой эффективности кэширования это значение требует корректировки. Из общей статистики мы также можем узнать, сколько ключей было удалено раньше истечения срока жизни (evictions), данный параметр может сигнализировать о недостаточности объема памяти memcached.</p>

<h2>Slab-аллокатор</h2>

<p>Для распределения памяти под значения ключей memcached использует вариант <a href="http://en.wikipedia.org/wiki/Slab_allocation">slab-аллокатора</a>. Данный тип аллокатора стремится сократить внутреннюю фрагментацию при выделении памяти, а также обеспечивают хорошую эффективность операций выделения памяти.</p>

<p>Механизм его работы заключается в том, что вся доступная memcached память делится на slab’ы (блоки), каждый из которых будет хранить элементы определенного размера. Например, slab для хранения объектов размером 256 байт, при этом сам slab имеет размер 1 Мб, таким образом он может сохранить 4096 таких объектов. Память внутри такого slab’а выделяется только по 256 байт. Если у нас есть slab’ы для объектов размером 64, 128, 256, 1024 и 2048 байт, то максимальный размер объекта, который мы можем сохранить – 2048 байт (в последнем slabе). Если мы хотим сохранить объект размером 65 байт, под него будет выделена память в slab’е-128, 1 байт – в slab’е 64.</p>

<p>Чтобы добиться эффективного использования памяти memcached для хранения наших ключей и значений, мы должны быть уверены в правильном выборе размеров slab’ов, который выделил memcached, а также в их разумном наполнении. Для этого мы можем попросить memcached предоставить статистику по slab’ам, которую можно, например, визуализировать в виде такого графика:</p>

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

<p>Здесь на горизонтальной оси отложены размеры slab’ов, а на вертикальной – объем памяти, используемый slab’ами данного размера. В данный момент вся память memcached занята ключами и их значениями, поэтому данный график представляет собой текущее распределение значений в памяти сервера. Легко видеть, что больше всего slab’ов выделено под ключи с относительно небольшими значениями – до 20 Кб, для больших по размеру ключей slab’ов гораздо меньше. Такое распределение адекватно нашей задаче: у нас больше всего именно маленьких ключей (счетчики, блокировки, небольшие кэши). При этом эти же ключи занимают и бóльшую часть памяти, с локальными пиками выделения под ключи размером 300 байт, 8 Кб. Если график отличается от того, который ожидается по логике задачи, это повод для беспокойства.</p>

<h2>Отладка проектов, использующих memcached</h2>

<p>Мы написали большую подсистему для работы с memcached, реализовали различные механизмы решения проблем, связанных с высокой нагрузкой. Как проверить, что всё действительно работает так, как нам бы этого хотелось? Высокую нагрузку, сетевые задержки и т.п. практически невозможно воспроизвести в локальном окружении, непросто это сделать и в тестовом окружении. На серверах в production нам доступны лишь те механизмы отладки, которые не затрагивают нормальное функционирование самого приложения. Способ отладки не должен вносить ощутимых временных задержек, иначе он изменит поведение приложения, и отладка станет бессмысленной.</p>

<p>Можно предложить следующий «трюк», который может помочь в данной ситуации: для каждого кэша (ключа в memcached) или для группы кэшей (ключей) мы заводим отдельный файл в локальной файловой системе. В этот файл в режиме append мы дописываем по одному символу в ответ на каждое логическое действие, которое произошло с кэшом. Для просмотра в реальном времени поведения кэширующей подсистемы достаточно сделать tail –f на этот файл:</p>

<pre><code>MLWUHHHHHHHHHHHHHHHMLLHHHHHHHHHH
</code></pre>

<p>Пусть буквы имеют следующий смысл:</p>

<ul>
<li><code>M</code> – кэш устарел (или не найден);</li>
<li><code>L</code> – попытка заблокироваться;</li>
<li><code>W</code> – запись (и построение) нового кэша;</li>
<li><code>U</code> – удаление блокировки;</li>
<li><code>H</code> – успешный запрос кэша.</li>
</ul>

<p>Тогда по приведенной последовательности можно рассказать то, что происходило с данным кэшом: вначале он отсутствовал, мы кэш не обнаружили (<code>M</code>), попытались заблокироваться (<code>L</code>) для его построения, заблокировались, построили кэш (<code>W</code>), сняли блокировку (<code>U</code>), затем какое-то время кэш успешно работал, отдавая закэшированные данные (<code>H</code>). Потом в какой-то момент кэш устарел или был сброшен (<code>M</code>), мы попытались заблокироваться, не получилось (<code>L</code>), попытались еще раз (<code>L</code>), блокировка оказалась снята, кто-то другой построил новый кэш, мы его прочитали (<code>H</code>) и дальше им пользовались.</p>

<h2>Межпроцессное взаимодействие с помощью memcached</h2>

<p>Сложный проект состоит из отдельных компонент, сервисов, которые должны взаимодействовать друг с другом, используя механизмы RPC, вызовы API, обмениваясь информацией через БД или каким-то еще способом. Иногда для такого обмена информацией можно использовать и memcached.</p>

<p>В качестве примера рассмотрим сервис пользовательских вещаний: существует какое-то количество вещаний, в каждом из которых в данный момент времени находится некоторое количество зрителей. Популярность вещания определяется количеством зрителей. Актуальной информацией о количестве зрителей обладает только сервер вещаний, а список вещаний на странице вещаний формирует frontend. Конечно, можно было бы сделать так, чтобы сервер вещаний периодически сбрасывал в БД или через API в frontend информацию о количестве зрителей, или frontend мог бы через API сервера вещаний получать актуальную информацию. Однако количество зрителей – очень быстро меняющаяся характеристика, и в данной ситуации можно просто из сервера вещаний периодически (раз в несколько секунд) сохранять в memcached информацию о количестве зрителей в каждом из вещаний, а frontend, обращаясь к memcached, может получить информацию в любой удобный момент. Таким может быть межпроцессное взаимодействие, реализованное с помощью memcached.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/10/31/web-caching-memcached-6/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Сброс группы кэшей и тэгирование в memcached</title>
		<link>http://www.smira.ru/2008/10/29/web-caching-memcached-5/</link>
		<comments>http://www.smira.ru/2008/10/29/web-caching-memcached-5/#comments</comments>
		<pubDate>Wed, 29 Oct 2008 06:59:04 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Разработка]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[tag]]></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=173</guid>
		<description><![CDATA[Серия постов про &#171;Web, кэширование и memcached&#187; продолжается. Начало здесь: 1, 2, 3 и 4.
В этих постах мы поговорили о 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> и <a href="http://www.smira.ru/2008/10/28/web-caching-memcached-4/">4</a>.
В этих постах мы поговорили о memcached, его архитектуре, возможном применении, выборе ключа кэширования, кластеризации, атомарных операциях и реализации счетчиков в <a href="http://danga.com/memcached/">memcached</a>, а также о проблеме одновременного перестроения кэшей.</p>

<p>Сегодня мы поговорим о тэгировании кэшей и о возможности сброса сразу группы кэшей в memcached.</p>

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

<p>Последний, шестой пост, будет посвящен различным техническим вопросам работы с memcached: анализу статистике, отладке и т.п.</p>

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

<h2>Сброс группы кэшей</h2>

<p>Если мы закэшировали какие-то данные от backend’а, например, выборку из БД, рано или поздно исходные данные изменяются, и кэш перестает быть валидным. Причем очень желательно, чтобы кэш сбрасывался сразу же за изменением, иначе пользователь после редактирования может увидеть старую версию объекта, что его, несомненно, смутит. Есть простой вариант ситуации: мы меняем информацию об объекте с ID 35, и сбрасываем кэш выборки этого объекта по параметру ID=35. На практике же чаще всего один и тот же объект явно или неявно входит в большое количество выборок, а значит и кэшей.</p>

<p>Рассмотрим такой пример: мы написали блогохостинг, в нем большое количество блогов. Когда один из авторов создает новый пост, меняется большое количество выборок: посты на главной странице и всех вторых страницах списка постов (т.к. все посты «сдвинулись» на один), изменилось количество записей в календаре постов, изменилась RSS-ка, и т.п. Конечно, мы могли бы поставить кэшам этих выборок небольшое время жизни, тогда через какое-то время они сбросятся и будут отображать правильную информацию, но слишком короткое время кэширования (5 секунд, например), будет давать низкое соотношение хитов в кэш, увеличивая нагрузку на БД, а более длительное будет создавать у пользователя ощущение, что информация после создания поста не обновилась, а, значит, пост не добавился. В то же время можно заметить, что в рамках блогохостинга если даже мы сбросим все кэши, связанные с данным блогом, это совсем небольшой процент от общей массы кэширования (т.к. блогов очень много). Остался вопрос: как найти и проидентифицировать все кэши данного блога? Какие-то из них мы можем легко построить, для некоторых это становится уже неудобно: например, количество кэшей постраничного списка постов зависит от количества страниц, которое еще необходимо вычислить. Что же делать?</p>

<p>Одно из возможных решений – тэгирование кэшей. Описанный ниже способ тэгирования по своей сути совпадает с описанным Дмитрием Котеровым в его <a href="http://dklab.ru/chicken/nablas/47.html">наблах</a>, но был нами разработан независимо. Существуют и другие варианты тэгирования, например, патч <a href="http://code.google.com/p/memcached-tag/">memcached-tag</a> на memcached. </p>

<h3>Тэг кэша</h3>

<p>Итак, мы вводим новое понятие – тэг кэша. Один кэш может нести с собой список тэгов, с которыми он связан. Сам по себе тэг – это некоторое имя и связанная с ним версия (число). Версия тэга может только монотонно увеличиваться. Группой кэшей мы будем называть кэши, имеющие один общий тэг. Для того чтобы сбросить группу кэшей, достаточно увеличить версию соответствующего тэга.</p>

<p>На программном уровне мы знаем, что данная выборка должна быть закэширована и что её кэш будет связан с тэгами <code>tag1</code> и <code>tag2</code> (данный факт определяется логикой работы нашего приложения). При создании кэша мы записываем в него кроме данных закэшированной выборки еще текущие (на момент создания кэша) версии тэгов <code>tag1</code> и <code>tag2</code>. При получении кэша мы считаем его валидным если не истекло время его жизни, и при этом текущии версии тэгов <code>tag1</code> и <code>tag2</code> равны версиям, записанным в кэше. Таким образом, если мы изменяем (увеличиваем) версию тэга <code>tag1</code>, все кэши, связанные с этим тэгом, которые были построены ранее, перестанут быть валидными (т.к. в них записана меньшая версия тэга <code>tag1</code>).</p>

<p>Рассмотрим пример с нашей выборкой, пусть было так:</p>

<pre><code>Версии тэгов:
  tag1 -&gt; 25
  tag2 -&gt; 63
Кэш выборки:
  [
    срок годности: 2008-11-07 21:00
    данные кэша: [
                     …
                  ]
    тэги: [
         tag1: 25
         tag2: 63
          ]
   ]
</code></pre>

<p>Затем произошло некоторое событие, и мы решили сбросить все кэши, ассоциированные с тэгом <code>tag2</code>, т.е. мы увеличили версию тэга: <code>tag2++</code>. Изменились версии тэгов:</p>

<pre><code>Версии тэгов:
  tag1 -&gt; 25
  tag2 -&gt; 64
</code></pre>

<p>Теперь наш кэш перестал быть валидным, не смотря на то, что его «срок годности» еще не истёк: версия тэга tag2, сохраненная в нем (63) не совпадает с текущей версией (64).</p>

<h3>Версии тэгов</h3>

<p>Тэги (то есть их версии) имеет смысл хранить там же, где мы и храним наши кэши, то есть в memcached. Для каждого тэга мы создадим ключ с именем, совпадающим с именем тэга, его значением будет версия тэга. Осталось решить, что использовать в качестве версии тэга? Можно было бы использовать просто числа, инкрементируя их при изменении версии тэга, но это может привести к некорректному поведению при условии возможной потери ключей. Пусть версия тэга равнялась единице, мы закэшировали выборку с этим тэгом, записали в кэш значение тэга – единицу. Затем ключ с версией тэга был удален из memcached, а в следующий момент времени мы захотели сбросить выборки, связанные с тэгом, то есть необходимо увеличить версию тэга. Так как мы потеряли значение версии тэга, мы снова поставим единицу, и теперь наш кэш будет считаться валидным, хотя он сбросился (не важно, какое значение выбирать при увеличении версии тэга, если она была потеряна – всегда возможна ситуация, что это же значение использовалось и ранее). </p>

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

<p>Использование текущего времени в качестве версии тэга даёт еще одно преимущество в ситуации, когда БД проекта устроена по схеме мастер-слейв репликации. При изменении исходного объекта в БД мы изменяем версию тэга, связанного с ним (записываем туда текущее время, то есть время изменения). В другом процессе мы обнаруживаем, что кэш устарел, то есть его надо перестроить, перестроение – это читающий запрос (<code>SELECT</code>), который необходимо отправить на слейв-сервер БД, но в силу задержек репликации слейв-сервер еще мог не получить актуальную версию объекта в БД, в результате мы кэш сбросили, но при его перестроении снова закэшировали старый вариант объекта, что неприемлемо. Можно использовать версию тэга при решении вопроса, на какой сервер БД отправить запрос: если разница между текущим временем и версией какого-либо тэга кэша меньше некоторого интервала, определяемого максимальной задержкой репликации, мы отправляем запрос на мастер-сервер БД вместо слейва.</p>

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

<p><em>Продолжение следует&#8230;</em></p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/10/29/web-caching-memcached-5/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<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>twisted.python.log и trial &#8211; веселимся вместе</title>
		<link>http://www.smira.ru/2008/10/24/twisted-log-and-trial-fun/</link>
		<comments>http://www.smira.ru/2008/10/24/twisted-log-and-trial-fun/#comments</comments>
		<pubDate>Fri, 24 Oct 2008 05:32:27 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Python]]></category>
		<category><![CDATA[Twisted]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[log]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[trial]]></category>
		<category><![CDATA[twisted]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=153</guid>
		<description><![CDATA[Следующий забавный случай взаимодействия частей Twisted Framework заставил меня потерять час времени, спешу рассказать, в надежде,
что это спасет чьё-то время. Итак, в Twisted есть модуль логгинга, twisted.python.log, в котором есть удобный метод log.err(), который позволяет записать в лог информацию о текущем исключении. А trial &#8211; это framework для написания юнит-тестов из того же Twisted. Их [...]]]></description>
			<content:encoded><![CDATA[<p>Следующий забавный случай взаимодействия частей <a href="http://twistedmatrix.com/">Twisted Framework</a> заставил меня потерять час времени, спешу рассказать, в надежде,
что это спасет чьё-то время. Итак, в Twisted есть модуль логгинга, <code>twisted.python.log</code>, в котором есть удобный метод <code>log.err()</code>, который позволяет записать в лог информацию о текущем исключении. А <code>trial</code> &#8211; это framework для написания юнит-тестов из того же Twisted. Их сочетание иногда приводит к проблеме <img src='http://www.smira.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>

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

<p>Итак, у меня был блок кода, похожий на пример ниже:</p>


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">from</span> twisted.<span style="color: black;">python</span> <span style="color: #ff7700;font-weight:bold;">import</span> log
&nbsp;
<span style="color: #ff7700;font-weight:bold;">def</span> catcher<span style="color: black;">&#40;</span>f<span style="color: black;">&#41;</span>:
  <span style="color: #ff7700;font-weight:bold;">try</span>:
    <span style="color: #ff7700;font-weight:bold;">return</span> f<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>
  <span style="color: #ff7700;font-weight:bold;">except</span> GoodException:
    <span style="color: #ff7700;font-weight:bold;">return</span> <span style="color: #483d8b;">'ERROR'</span>
  <span style="color: #ff7700;font-weight:bold;">except</span>:
    log.<span style="color: black;">err</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>
    <span style="color: #ff7700;font-weight:bold;">raise</span> UnknownException</pre></td></tr></table></div>


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


<div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
</pre></td><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #ff7700;font-weight:bold;">def</span> testCatcher<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
  <span style="color: #ff7700;font-weight:bold;">def</span> raiseBadException<span style="color: black;">&#40;</span><span style="color: black;">&#41;</span>:
    <span style="color: #ff7700;font-weight:bold;">assert</span> <span style="color: #008000;">False</span> 
  <span style="color: #008000;">self</span>.<span style="color: black;">assertEquals</span><span style="color: black;">&#40;</span><span style="color: #483d8b;">'ERROR'</span>, catcher, raiseBadException<span style="color: black;">&#41;</span></pre></td></tr></table></div>


<p>При запуске юнит-тест говорил, что он завершился с ошибкой, т.к. было выброшено исключение 
<code>exceptions.AssertionError</code>, т.е. то исключение, которое генерирует строка 3 в тесте. Но ведь оно было поймано <code>catcher</code>? Я долго бился с этой ситуацией (на самом деле код был сложнее, но суть его от этого не менялась), пока не понял, что если из-под <code>trial</code> (запускальщика юнит-тестов) сделать <code>twisted.python.log.err</code>, то вместо того, чтобы вывести в лог исключение, он помечает тест как ошибочный, при этом в качестве причины ошибки выводит текущее исключение! То есть тест отрабатывал штатно, но <code>log.err()</code> мой помечал его как ошибочный.</p>

<p>Вот такое вот не совсем очевидное взаимодействие&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/10/24/twisted-log-and-trial-fun/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Атомарность операций и счетчики в memcached</title>
		<link>http://www.smira.ru/2008/10/24/web-caching-memcached-3/</link>
		<comments>http://www.smira.ru/2008/10/24/web-caching-memcached-3/#comments</comments>
		<pubDate>Fri, 24 Oct 2008 05:00:53 +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>

		<guid isPermaLink="false">http://www.smira.ru/?p=142</guid>
		<description><![CDATA[Серия постов про &#171;Web, кэширование и memcached&#187; продолжается. В первом и втором постах мы поговорили о memcached, его архитектуре, возможном применении, выборе ключа кэширования и кластеризации memcached.

Сегодня речь пойдет о:


атомарных операциях в memcached;
реализации счетчиков просмотров и онлайнеров.




Следующий пост будет посвящен проблеме одновременного перестроения кэшей.



Атомарность операций в memcached

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

<p>Сегодня речь пойдет о:</p>

<ul>
<li>атомарных операциях в memcached;</li>
<li>реализации счетчиков просмотров и онлайнеров.</li>
</ul>

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

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

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

<h2>Атомарность операций в memcached</h2>

<p>Как таковые, все одиночные запросы к memcached атомарны (в силу его однопоточности и корректных внутренних блокировок в многопоточном случае). Это означает, что если мы выполняем запрос get, мы получим значения ключа таким, как кто-то его записал в кэш, но точно не смесь двух записей. Однако каждая операция независима, и мы не можем гарантировать, например, корректность такой процедуры в ситуации конкурентного доступа из нескольких параллельных процессов:</p>

<ol>
<li>Получить значение ключа «x» (<code>$x = get x</code>).</li>
<li>Увеличение значения переменной на единицу (<code>$x = $x + 1</code>).</li>
<li>Запись нового значения переменной в memcached (<code>set x = $x</code>).</li>
</ol>

<p>Если данный код выполняют несколько frontend’ов одновременно, может получиться так, что значение ключа x увеличится не n раз, как мы задумывали, а на меньшее значение (классическое <a href="http://ru.wikipedia.org/wiki/Состояние_гонки">состояние гонки</a>, <a href="http://en.wikipedia.org/wiki/Race_condition">race condition</a>). Конечно, такой подход неприемлем для нас. Классический ответ на сложившуюся ситуацию: применение синхронизационных примитивов (семафоров, мутексов и т.п.),  однако в memcached они отсутствуют. Другим вариантом решения задачи является реализация более сложных операций, которые заменяют неатомарную последовательность get/set.</p>

<p>В memcached для решения этой проблемы есть пара операций: <code>incr</code>/<code>decr</code> (инкремент и декремент). Они обеспечивают атомарное увеличение (или, соответственно, уменьшение) целочисленного значения существующего в memcached ключа. Атомарными являются также дополнительные операции: <code>append</code>/<code>prepend</code>, которые позволяют добавить к значению ключа данные в начало или в конец, также в каком-то плане атомарными можно считать операции <code>add</code> и <code>replace</code>, которые позволяют задать значение ключа, только если он ранее не существовал, или, наоборот, заменить значение уже существующего ключа. Об еще одном варианте атомарных операций речь пойдет в разделе про реализацию блокировок средствами memcached.
Необходимо дополнительно отметить, что любая блокировка в memcached должна быть мелкозернистой (fine-grained), то есть должна затрагивать как можно меньшее число объектов, так как основная задача сервера в любом случае – обеспечивать эффективный доступ к кэшу как можно большего числа параллельных процессов.</p>

<h2>Счетчики в memcached</h2>

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

<h3>Счетчик просмотров</h3>

<p>Пусть в нашем проекте есть некоторые объекты (например, фото, видео, статьи и т.п.), для которых мы должны в реальном времени показывать число просмотров. Счетчик должен увеличиваться с каждым просмотром. Самый простой вариант – при каждом просмотре обновлять поле в БД, не будет работать, т.к. просмотров много и БД не выдержит такую нагрузку. Мы можем реализовать точный и аккуратный сбор статистики просмотров, их аккумулирование, и периодический анализ, который заканчивается обновлением счетчика в базе данных (например, раз в час). Однако остается задача показа текущего количества просмотров.
Рассмотрим следующее возможное решение. Frontend в момент просмотра объекта формирует имя ключа счетчика в memcached, и пытается выполнить операцию <code>incr</code> (инкремент) над этим ключом. Если выполнение было успешным, это означает, что соответствующий ключ находится в memcached, мы просмотр засчитали, также мы получили новое значение счетчика (как результат операции <code>incr</code>), которое мы можем показать пользователю. Если же операция <code>incr</code> вернула ошибку, то ключ счетчика в данный момент отсутствует в memcached, мы можем выбрать в качестве начального значения число просмотров из базы данных, увеличить его на единицу, и выполнить операцию set, устанавливая новое значение счетчика. При последующих просмотрах ключ уже будет находиться в memcached, и мы будем просто увеличивать его значение с помощью incr.</p>

<p>Необходимо отметить, что приведенная схема не является вполне корректной: в ней присутствует состояние гонки (race condition). Если два frontend одновременно обращаются к счетчику, одновременно обнаруживают его отсутствие, и сделают две операции set, мы потеряем один просмотр. Это можно считать не очень критичным, так как процесс аккумулирования статистики восстановит правильное значение. В случае необходимости можно воспользоваться блокировками в memcached, речь о которых пойдет ниже. Или же реализовать инициализацию счетчика через операцию <code>add</code>, обрабатывая её результат.</p>

<h2>Счетчик онлайнеров</h2>

<p>Существует еще один вид счетчиков, который без memcached или подобного ему решения вряд ли может быть реализован: это счетчик «онлайнеров». Такие счетчики мы видим на большом количестве сайтов, однако в первую очередь необходимо определить, что же именно мы имеем в виду под «онлайнером». Пусть мы хотим рассчитать, сколько уникальных сессий (пользователей) обратилось к нашему сайту за последние 5 минут. Уникальность обращения пользователя с данной сессией в течение 5 минут можно отследить, сохраняя в сессии время последнего засчитанного обращения, если прошло более 5 минут – значит это новое (уникальное) обращение.</p>

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

<p>Итак, выделим в memcached шесть ключей с именами, например, <code>c_0</code>, <code>c_1</code>, <code>c_2</code>, …, <code>c_5</code>. Текущим изменяемым ключом мы будем считать счетчик с номером, равным остатку от деления текущей минуты на 6 (на рисунке это ключ <code>c_4</code>). Именно его мы будем увеличивать с помощью операции incr для обращения каждой уникальной в течение 5 минут сессии. Если <code>incr</code> вернет ошибку (счетчика еще нет), установим его значение в 1 с помощью <code>set</code>, обязательно указав время жизни 6 минут. Значением счетчика онлайнеров будем считать сумму всех ключей, кроме текущего (на рисунке это ключи <code>c_0</code>, <code>c_1</code>, <code>c_2</code>, <code>c_3</code> и <code>c_5</code>).</p>

<p>Когда наступит следующая минута, текущим изменяемым ключом станет ключ <code>c_5</code>, при этом его предыдущее значение исчезнет (т.к. он был создан 6 минут назад с временем жизни те же 6 минут). Значением счетчика станет сумма ключей c <code>с_0</code> по <code>c_4</code>, т.е. только что рассчитанное значение ключа <code>с_4</code> уже начнет учитываться в отображаемом значении счетчика.</p>

<p>Такой счетчик может быть построен и на меньшем числе ключей. Минимально возможными для данной схемы являются два ключа: один обновляется, значение другого показывается, затем по прошествии 5 минут счетчики меняются местами, при этом тот, который только что обновлялся, сбрасывается. В приведенной схеме с многими ключами обеспечивается некоторое «сглаживание», которое обеспечивает более плавное изменение счетчика в случае резкого притока или оттока посетителей.</p>

<p><em>Продолжение следует&#8230;</em></p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/10/24/web-caching-memcached-3/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Кластеризация memcached и выбор ключа кэширования</title>
		<link>http://www.smira.ru/2008/10/21/web-caching-memcached-2/</link>
		<comments>http://www.smira.ru/2008/10/21/web-caching-memcached-2/#comments</comments>
		<pubDate>Tue, 21 Oct 2008 06:36:46 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Разработка]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[ключ]]></category>
		<category><![CDATA[масштабирование]]></category>
		<category><![CDATA[разработка]]></category>
		<category><![CDATA[хэш]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=114</guid>
		<description><![CDATA[Серия постов про &#171;Web, кэширование и memcached&#187; продолжается. В первом посте мы поговорили о memcached, его архитектуре и возможном применении.

Сегодня речь пойдет о:


выборе ключа кэширования;
кластеризации memcached и алгоритмах распределения ключей.




Следующий пост будет посвящен атомарности операций и счетчикам в memcached.



Ключ кэширования

Пусть мы уже убедились, что использовать memcached для кэширования – это правильное решение. Первая задача, которая [...]]]></description>
			<content:encoded><![CDATA[<p>Серия <a href="http://www.smira.ru/2008/10/16/web-caching-memcached-1/">постов</a> про &laquo;Web, кэширование и memcached&raquo; продолжается. В первом <a href="http://www.smira.ru/2008/10/16/web-caching-memcached-1/">посте</a> мы поговорили о memcached, его архитектуре и возможном применении.</p>

<p>Сегодня речь пойдет о:</p>

<ul>
<li>выборе ключа кэширования;</li>
<li>кластеризации memcached и алгоритмах распределения ключей.</li>
</ul>

<p><img src="http://www.smira.ru/wp-content/uploads/2008/10/keys-259x300.jpg" alt="Ключи" title="keys" width="259" height="300" class="size-medium wp-image-123" /></p>

<p>Следующий пост будет посвящен атомарности операций и счетчикам в <a href="http://danga.com/memcached/">memcached</a>.</p>

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

<h2>Ключ кэширования</h2>

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

<ul>
<li>При изменении параметров выборки, которую мы кэшируем, ключ кэширования должен изменяться (чтобы с новыми параметрами мы не «попали» в старый кэш).</li>
<li>По параметрам выборки ключ должен определяться однозначно, т.е. для одной и той же выборки ключ кэширования должен быть только один, иначе мы рискуем понизить эффективность процесса кэширования.</li>
</ul>

<p>Конечно, мы могли бы для каждой выборки строить ключ самостоятельно, например, <code>‘user_158’</code> для выборки информации о пользователе с ID 158 или <code>‘friends_192_public_sorted_online’</code> для друзей пользователя с ID 192, которые доступно публично и притом отсортированы в порядке последнего появления на сайте. Такой подход чреват ошибками и несоблюдением условий, сформулированных выше.</p>

<p>Можно использовать следующий вариант (пример для PHP): если существует некоторая точка в коде, через которую проходят все обращения к БД, а любое обращение полностью описывается (содержит все параметры запроса) в некоторой структуре <code>$options</code>, можно использовать следующий ключ: </p>

<pre><code>$key = md5(serialize($options)) 
</code></pre>

<p>Такой ключ несомненно удовлетворяет первому условию (при изменении <code>$options</code> будет обязательно изменен <code>$key</code>), но и второе условие будет соблюдаться, если мы будем все типы данных в $options использовать «канонически», т.е. не допускать строки <code>"1"</code> вместо числа <code>1</code> (хотя в PHP два таких значения равны, но их сериализованное представление различается). Функция <code>md5</code> используется для «сжатия» данных: ключ memcached имеет ограничение по длине, а сериализованное представление может быть слишком длинным.</p>

<h2>Кластеризация memcached</h2>

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

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

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

<p>Исторически первой функцией распределения была функция модуля:</p>

<pre><code>f(ключ) = crc32(ключ) % количество_серверов
</code></pre>

<p>Такая функция обеспечивает равномерное распределение ключей по серверам, однако проблемы возникают при переконфигурировании кластера memcached: изменение количества серверов приводит к перемещению значительной части ключей по серверам, что эквивалентно потере значительной части ключей.</p>

<p>Альтернативой для данной функции является механизм консистентного хэширования (consistent hashing), который при переконфигурации кластера сохраняет положение ключей по серверам. Этот подход был реализован в клиентах memcached впервые разработчиками сервиса Last.fm в апреле 2007 года.</p>

<p><img src="http://www.smira.ru/wp-content/uploads/2008/10/consistent_hashing_1.png" alt="" title="consistent_hashing_1" width="237" height="239" class="alignnone size-full wp-image-126" /><img src="http://www.smira.ru/wp-content/uploads/2008/10/consistent_hashing_2.png" alt="" title="consistent_hashing_2" width="251" height="232" class="alignnone size-full wp-image-127" /></p>

<p>Суть алгоритма заключается в следующем: мы рассматриваем набор целых чисел от 0 до 2­<sup>32</sup>, «закручивая» числовую ось в кольцо (склеиваем 0 и 2<sup>32</sup>). Каждому сервера из пула memcached-серверов мы сопоставляем число на кольце (рисунок слева, сервера A, B и C). Ключ хэшируется в число в том же диапазоне (на рисунке – синие точки 1-4), в качестве сервера для хранения ключа мы выбираем сервер в точке, ближайшей к точке ключа в направлении по часовой стрелке. Если сервер удаляется из пула или добавляется в пул, на оси появляется или исчезает точка сервера, в результате чего лишь часть ключей перемещается на другой сервер. На рисунке 2 справа показана ситуация, когда сервер C был удалён из пула серверов и добавлен новый сервер D. Легко заметить, что ключи 1 и 2 не поменяли привязки к серверам, а ключи 3 и 4 переместились на другие сервера. На самом деле одному серверу ставится в соответствие 100-200 точек на оси (пропорционально его весу в пуле), что улучшает равномерность распределения ключей по серверам в случае изменения их конфигурации.</p>

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

<p><em>Продолжение следует&#8230;</em></p>

<h2>Материалы</h2>

<ol>
<li><a href="http://weblogs.java.net/blog/tomwhite/archive/2007/11/consistent&#95;hash.html">http://weblogs.java.net/blog/tomwhite/archive/2007/11/consistent&#95;hash.html</a></li>
<li><a href="http://www.lastfm.ru/user/RJ/journal/2007/04/10/rz&#95;libketama&#95;-&#95;a&#95;consistent&#95;hashing&#95;algo&#95;for&#95;memcache&#95;clients">http://www.lastfm.ru/user/RJ/journal/2007/04/10/rz&#95;libketama&#95;-&#95;a&#95;consistent&#95;hashing&#95;algo&#95;for&#95;memcache&#95;clients</a></li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/10/21/web-caching-memcached-2/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Кэширование и memcached</title>
		<link>http://www.smira.ru/2008/10/16/web-caching-memcached-1/</link>
		<comments>http://www.smira.ru/2008/10/16/web-caching-memcached-1/#comments</comments>
		<pubDate>Thu, 16 Oct 2008 07:56:59 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Разработка]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[кэш]]></category>
		<category><![CDATA[кэширование]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=98</guid>
		<description><![CDATA[Этим постом хочу открыть небольшую серию постов по материалам доклада на HighLoad++-2008. Впоследствии весь текст будет опубликован в виде одной большой PDF-ки. 

Введение

Для начала, о названии серии постов: посты будут и о кэшировании в Web’е (в высоконагруженных Web-проектах), и о применении memcached для кэширования, и о других применениях memcached в Web-проектах. То есть все три [...]]]></description>
			<content:encoded><![CDATA[<p>Этим постом хочу открыть небольшую серию постов по материалам <a href="http://www.smira.ru/2008/10/08/highload-plus-plus-2008/">доклада</a> на <a href="http://highload.ru/">HighLoad++-2008</a>. Впоследствии весь текст будет опубликован в виде одной большой PDF-ки. </p>

<h2>Введение</h2>

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

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

<p>Кэширование сегодня является неотъемлемой частью любого Web-проекта, не обязательно высоконагруженного. Для каждого ресурса критичной для пользователя является такая характеристика, как время отклика сервера. Увеличение времени отклика сервера приводит к оттоку посетителей. Следовательно, необходимо минимизировать время отклика: для этого необходимо уменьшать время, требуемое на формирование ответа пользователю, а ответ пользователю требует получить данные из каких-то внешних ресурсов (backend). Этими ресурсами могут быть как базы данных, так и любые другие относительно медленные источники данных (например, удаленный файловый сервер, на котором мы уточняем количество свободного места). Для генерации одной страницы достаточно сложного ресурса нам может потребоваться совершить десятки подобных обращений. Многие из них будут быстрыми: 20 мс и меньше, однако всегда существует некоторое небольшое количество запросов, время вычисления которых может исчисляться секундами или минутами (даже в самой оптимизированной системе один могут быть, хотя их количество должно быть минимально). Если сложить всё то время, которое мы затратим на ожидание результатов запросов (если же мы будем выполнять запросы параллельно, то возьмем время вычисления самого долгого запроса), мы получим неудовлетворительное время отклика.</p>

<p>Решением этой задачи является кэширование: мы помещаем результат вычислений в некоторое хранилище (например, memcached), которое обладает отличными характеристиками по времени доступа к информации. Теперь вместо обращений к медленным, сложным и тяжелым backend’ам нам достаточно выполнить запрос к быстрому кэшу.</p>

<h2>Memcached и кэширование</h2>

<h3>Принцип локальности</h3>

<p>Кэш или подход кэширования мы встречаем повсюду в электронных устройствах, архитектуре программного обеспечения: кэш ЦП (первого и второго уровня), буферы жесткого диска, кэш операционной системы, буфер в автомагнитоле. Чем же определяется такой успех кэширования? Ответ лежит в принципе локальности: программе, устройству свойственно в определенный промежуток времени работать с некоторым подмножеством данных из общего набора. В случае оперативной памяти это означает, что если программа работает с данными, находящимися по адресу 100, то с большей степенью вероятности следующее обращение будет по адресу 101, 102 и т.п., а не по адресу 10000, например. То же самое с жестким диском: его буфер наполняется данными из областей, соседних по отношению к последним прочитанным секторам, если бы наши программы работали в один момент времени не с некоторым относительно небольшим набором файлов, а со всем содержимым жесткого диска, буферы были бы бессмысленны. Буфер автомагнитолы совершает упреждающее чтение с диска следующих минут музыки, потому что мы, скорее всего, будем слушать музыкальный файл последовательно, чем перескакивать по набору музыки и т.п.</p>

<p>В случае web-проектов успех кэширования определяется тем, что на сайте есть всегда наиболее популярные страницы, некоторые данные используются на всех или почти на всех страницах, то есть существуют некоторые выборки, которые оказываются затребованы гораздо чаще других. Мы заменяем несколько обращений к backend’у на одно обращения для построения кэша, а затем все последующие обращения будет делать через быстро работающий кэш.
Кэш всегда лучше, чем исходный источник данных: кэш ЦП на порядки быстрее оперативной памяти, однако мы не можем сделать оперативную память такой же быстрой, как кэш – это экономически неэффективно и технически сложно. Буфер жесткого диска удовлетворяет запросы за данными на порядки быстрее самого жесткого диска, однако буфер не обладает свойством запоминать данные при отключении питания – в этом смысле он хуже самого устройства. Аналогичная ситуация и с кэшированием в Web’е: кэш быстрее и эффективнее, чем backend, однако он обычно в случае перезапуска или падения сервера не может сохранить данные, а также не обладает логикой по вычислению каких-либо результатов: он умеет возвращать лишь то, что мы ранее в него положили.</p>

<h3>Memcached</h3>

<p><a href="http://danga.com/memcached/">Memcached</a> представляет собой огромную хэш-таблицу в оперативной памяти, доступную по сетевому протоколу. Он обеспечивает сервис по хранению значений, ассоциированных с ключами. Доступ к хэшу мы получаем через простой сетевой протокол, клиентом может выступать программа, написанная на произвольном языке программирования (существуют клиенты для C/C++, PHP, Perl, Java и т.п.)</p>

<p>Самые простые операции – получить значение указанного ключа (get), установить значение ключа (set) и удалить ключ (del). Для реализации цепочки атомарных операций (при условии конкурентного доступа к memcached со стороны параллельных процессов) используются дополнительные операции: инкремент/декремент значения ключа (incr/decr), дописать данные к значению ключа в начало или в конец (append/prepend), атомарная связка получения/установки значения (gets/cas) и другие.</p>

<p>Memcached был реализован Брэдом Фитцпатриком (Brad Fitzpatrick) в рамках работы над проектом ЖЖ (LiveJournal). Он использовался для разгрузки базы данных от запросов при отдаче контента страниц. Сегодня memcached нашел своё применение в ядре многих крупных проектов, например, Wikipedia, YouTube, Facebook и другие.</p>

<h3>Общая схема кэширования</h3>

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

<p>В общем случае схема кэширования выглядит следующим образом: frontend’у (той части проекта, которая формирует ответ пользователю) требуется получить данные какой-то выборки. Frontend обращается к быстрому как гепард серверу memcached за кэшом выборки (get-запрос). Если соответствующий ключ будет обнаружен, работа на этом заканчивается. В противном случае следует обращение к тяжелому, неповоротливому, но мощному (как слон) backend’у, в роли которого чаще всего выступает база данных. Полученный результат сразу же записывается в memcached в качестве кэша (set-запрос). При этом обычно для ключа задается максимальное время жизни (срок годности), который соответствует моменту сброса кэша.</p>

<p>Такая стандартная схема кэширования реализуется всегда. Вместо memcached в некоторых проектах могут использоваться локальные файлы, иные способы хранения (другая БД, кэш PHP-акселератора и т.п.) Однако, как будет показано далее, в высоконагруженном проекте данная схема может работать не самым эффективным образом. Тем не менее, в нашем дальнейшем рассказе мы будем опираться именно на эту схему.</p>

<h3>Архитектура memcached</h3>

<p>Каким же образом устроен memcached? Как ему удаётся работать настолько быстро, что даже десятки запросов к memcached, необходимых для обработки одной страницы сайта, не приводят к существенной задержке. При этом memcached крайне нетребователен к вычислительным ресурсам: на нагруженной инсталляции процессорное время, использованное им, редко превышает 10%.</p>

<p>Во-первых,  memcached спроектирован так, чтобы все его операции имели алгоритмическую сложность O(1), т.е. время выполнения любой операции не зависит от количества ключей, которые хранит memcached. Это означает, что некоторые операции (или возможности) будут отсутствовать в нём, если их реализация требует всего лишь линейного (O(n)) времени. Так, в memcached отсутствуют возможность объединения ключей «в папки», т.е. какой-либо группировки ключей, также мы не найдем групповых операций над ключами или их значениями.
Основными оптимизированными операциями является выделение/освобождение блоков памяти под хранение ключей, определение политики самых неиспользуемых ключей (LRU) для очистки кэша при нехватке памяти. Поиск ключей происходит через хэширование, поэтому имеет сложность O(1).</p>

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

<p>По сути, можно сказать, что время отклика сервера memcached определяется только сетевыми издержками и практически равно времени передачи пакета от frontend’а до сервера memcached (RTT). Такие характеристики позволяют использовать memcached в высоконагруженных web-проектов для решения различных задач, в том числе и для кэширования данных.
Потеря ключей</p>

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

<ol>
<li>Ключ был удален раньше окончания его срока годности в силу нехватки памяти под хранение значений других ключей. Memcached использует политику LRU, поэтому такая потеря означает, что данный ключ редко использовался и память кэша освобождается для хранения более популярных ключей.</li>
<li>Ключ был удален, так как истекло его время жизни. Такая ситуация строго говоря не является потерей, так как мы сами ограничили время жизни ключа, но для клиентского по отношению к memcached кода такая потеря неотличима от других случаев – при обращении к memcached мы получаем ответ «такого ключа нет».</li>
<li>Самой неприятной ситуацией является крах процесса memcached или сервера, на котором он расположен. В этой ситуации мы теряем все ключи, которые хранились в кэше. Несколько сгладить последствия позволяет кластерная организация: множество серверов memcached, по которым «размазаны» ключи проекта: так последствия краха одного кэша будут менее заметны.</li>
</ol>

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

<p><strong>«Можно потерять»</strong>. К этой категории относятся кэши выборок из базы данных. Потеря таких ключей не так страшна, потому что мы можем легко восстановить их значения, обратившись заново к backend’у. Однако частые потери кэшей приводят к излишним обращениям к БД.</p>

<p><strong>«Не хотелось бы потерять»</strong>. Здесь можно упомянуть счетчики посетителей сайта, просмотров ресурсов и т.п. Хоть и восстановить эти значения иногда напрямую невозможно, но значения этих ключей имеют ограниченный по времени смысл: через несколько минут их значение уже неактуально, и будет рассчитано новое значение.</p>

<p><strong>«Совсем не должны терять»</strong>. Memcached удобен для хранения сессий пользователей – все сессии равнодоступны со всех серверов, входящих в кластер frontend’ов. Так вот содержимое сессий не хотелось бы терять никогда – иначе пользователей на сайте будет «разлогинивать». Как попытаться избежать? Можно дублировать ключи сессий на нескольких серверах memcached из кластера, так вероятность потери снижается.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/10/16/web-caching-memcached-1/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>Web, кэширование и memcached (выступление на HighLoad++ 2008)</title>
		<link>http://www.smira.ru/2008/10/08/highload-plus-plus-2008/</link>
		<comments>http://www.smira.ru/2008/10/08/highload-plus-plus-2008/#comments</comments>
		<pubDate>Wed, 08 Oct 2008 05:21:11 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Smotri.Com]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[highload]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[кэширование]]></category>
		<category><![CDATA[разработка]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=90</guid>
		<description><![CDATA[Итак, HighLoad++ состоялся. Если говорить кратко, конференция мне понравилась. Ниже мои личные впечатления о конференции, краткие тезисы доклада и презентация. 

Текст доклада дописываю, есть мечта к концу недели это доделать (сейчас готова ровно половина). Тогда же текст опубликую, возможно в серии отдельных постов и в виде одной большой PDF-ки тут.

Мои впечатления о конференции

Итак, мне понравилось. [...]]]></description>
			<content:encoded><![CDATA[<p>Итак, HighLoad++ состоялся. Если говорить кратко, конференция мне понравилась. Ниже мои личные впечатления о конференции, краткие тезисы доклада и презентация. </p>

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

<h2>Мои впечатления о конференции</h2>

<p>Итак, мне понравилось. Интересные доклады &#8211; много интересных докладов. Жалко, что не было
Яндекса &#8211; они делают хорошие доклады. В первый день была проблема поесть и попить, но ко второму дню ситуация как-то улучшилась. Народу чуть-чуть больше, чем хотелось бы (иногда в аудиторию к докладчику не пролезть через тела тех, которые устроили &laquo;пробку&raquo; на входе в зал). Но интересные или очень интересные доклады, много обсуждений, новых идей. Встретил старых знакомых, это всегда приятно <img src='http://www.smira.ru/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />  </p>

<p>Огранизационно всё было четко, понравился дизайн мелочей &#8211; бейджиков, шаблона презентаций и прочего &#8211; просто и со вкусом. В общем и целом &#8211; так держать, Олег <img src='http://www.smira.ru/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>

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

<h2>Краткие тезисы</h2>

<p>Требуемое время: 50 минут 
Докладчик: Андрей Смирнов</p>

<p>Цель доклада – рассказать о проблемах кеширования в распределенных высоконагруженных проектах и о возможных путях решения этой проблемы. Предполагаемый уровень подготовки аудитории – начинающий++.</p>

<p>Современный высоконагруженный проект может использовать десятки гигабайт распределенной памяти, используемой под кеш, организованной в виде кластера memcached-серверов. Зачем нужен memcached? Как работать с таким хранилищем, как распределить ключи по элементам кластера? Как назвать ключ, соответствующий кешу? Как обеспечить атомарность операций, “блокировки”?</p>

<p>Как эффективно использовать такое хранилище? Как исключить возможность одновременного построения “тяжелых” кешей разными мордами? Как сбросить одновременно группу кешей? Как отлаживать (собирать статистику) о кешировании? Как работает slab-аллокатор? Для чего еще может быть полезен memcached в веб-проекте? </p>

<h2>Видео с конференции</h2>

<p><object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="400" height="330"><param name="movie" value="http://pics.smotri.com/scrubber_custom8.swf?file=v649374d232&#038;bufferTime=3&#038;autoStart=false&#038;str_lang=rus&#038;xmlsource=http%3A%2F%2Fpics.smotri.com%2Fcskins%2Fblue%2Fskin_color_black.xml&#038;xmldatasource=http%3A%2F%2Fpics.smotri.com%2Fskin_ng.xml" /><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="true" /><param name="bgcolor" value="#ffffff" /><embed src="http://pics.smotri.com/scrubber_custom8.swf?file=v649374d232&#038;bufferTime=3&#038;autoStart=false&#038;str_lang=rus&#038;xmlsource=http%3A%2F%2Fpics.smotri.com%2Fcskins%2Fblue%2Fskin_color_black.xml&#038;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"></embed></object></p>

<h2>Презентация</h2>

<p><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%">      <param name="movie" value="http://documents.scribd.com/ScribdViewer.swf?document_id=6442228&#038;access_key=key-2gjlb75syn57xsue0rnj&#038;page=&#038;version=1&#038;auto_size=true&#038;viewMode=">      <param name="quality" value="high">         <param name="play" value="true">        <param name="loop" value="true">        <param name="scale" value="showall">        <param name="wmode" value="opaque">         <param name="devicefont" value="false">     <param name="bgcolor" value="#ffffff">      <param name="menu" value="true">        <param name="allowFullScreen" value="true">         <param name="allowScriptAccess" value="always">         <param name="salign" value="">          <embed src="http://documents.scribd.com/ScribdViewer.swf?document_id=6442228&#038;access_key=key-2gjlb75syn57xsue0rnj&#038;page=&#038;version=1&#038;auto_size=true&#038;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%"></embed>   </object><div style="font-size:10px;text-align:center;width:100%"><a href="http://www.scribd.com/doc/6442228/Web-memcached">Web, кэширование и memcached</a> &#8211; <a href="http://www.scribd.com/upload">Upload a Document to Scribd</a></div></p>

<h2>Полный текст доклада</h2>

<p><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"> <param name="movie" value="http://documents.scribd.com/ScribdViewer.swf?document_id=7667009&#038;access_key=key-1ikhiaj4iruc95lho5as&#038;page=1&#038;version=1&#038;viewMode=list"> <param name="quality" value="high"> <param name="play" value="true"> <param name="loop" value="true"> <param name="scale" value="showall"> <param name="wmode" value="opaque"> <param name="devicefont" value="false"> <param name="bgcolor" value="#ffffff"> <param name="menu" value="true"> <param name="allowFullScreen" value="true"> <param name="allowScriptAccess" value="always"> <param name="salign" value=""> <param name="mode" value="list"> <embed src="http://documents.scribd.com/ScribdViewer.swf?document_id=7667009&#038;access_key=key-1ikhiaj4iruc95lho5as&#038;page=1&#038;version=1&#038;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"></embed> </object><div style="font-size:10px;text-align:center;width:700"><a href="http://www.scribd.com/doc/7667009/Web-Caching-and-Memcached">Web Caching and Memcached</a> &#8211; <a href="http://www.scribd.com/upload">Upload a Document to Scribd</a></div> </p>

<h2>Материалы</h2>

<ul>
<li>Тезисы (<a href="http://www.smira.ru/wp-content/uploads/2008/10/abstract.rtf">RTF</a>)</li>
<li>Презентация (<a href="http://www.smira.ru/wp-content/uploads/2008/10/dhndhudhdhudhndhndhny-dhdh-highload-2008.ppt">PowerPoint</a>)</li>
<li>Полный текст доклада (<a href="http://www.smira.ru/wp-content/uploads/2008/10/web-caching-and-memcached.pdf">PDF</a>)</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/10/08/highload-plus-plus-2008/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Анонс: выступление на HighLoad++ (2008)</title>
		<link>http://www.smira.ru/2008/09/30/highload-2008/</link>
		<comments>http://www.smira.ru/2008/09/30/highload-2008/#comments</comments>
		<pubDate>Tue, 30 Sep 2008 01:42:03 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Smotri.Com]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[highload]]></category>
		<category><![CDATA[memcached]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=80</guid>
		<description><![CDATA[6-7 октября в Москве пройдет конференция HighLoad++. На этой конференции я представлю доклад на тему &#171;Web, кеширование и memcached&#187; (текущая программа конференции).

Краткие тезисы доклада привожу ниже:

Цель доклада – рассказать о проблемах кеширования в распределенных высоконагруженных проектах и о возможных путях решения этой проблемы. Предполагаемый уровень подготовки аудитории – начинающий++.

UPD: доклад будет во второй день, 7 [...]]]></description>
			<content:encoded><![CDATA[<p>6-7 октября в Москве пройдет конференция <a href="http://highload.ru/">HighLoad++</a>. На этой конференции я представлю доклад на тему &laquo;Web, кеширование и memcached&raquo; (<a href="http://highload.ru/delegates/7529.html">текущая программа конференции</a>).</p>

<p>Краткие тезисы доклада привожу ниже:</p>

<p>Цель доклада – рассказать о проблемах кеширования в распределенных высоконагруженных проектах и о возможных путях решения этой проблемы. Предполагаемый уровень подготовки аудитории – начинающий++.</p>

<p><strong>UPD:</strong> доклад будет во второй день, 7 октября, 17:20-18:10, второй зал.</p>

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

<p>Современный высоконагруженный проект может использовать десятки гигабайт распределенной памяти, используемой под кеш, организованной в виде кластера memcached-серверов. Зачем нужен memcached? Как работать с таким хранилищем, как распределить ключи по элементам кластера? Как назвать ключ, соответствующий кешу? Как обеспечить атомарность операций, “блокировки”?</p>

<p>Как эффективно использовать такое хранилище? Как исключить возможность одновременного построения “тяжелых” кешей разными мордами? Как сбросить одновременно группу кешей? Как отлаживать (собирать статистику) о кешировании? Как работает slab-аллокатор? Для чего еще может быть полезен memcached в веб-проекте? </p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/09/30/highload-2008/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Выступление на РИТ: Высокие нагрузки (2008)</title>
		<link>http://www.smira.ru/2008/09/28/rit-highload-2008/</link>
		<comments>http://www.smira.ru/2008/09/28/rit-highload-2008/#comments</comments>
		<pubDate>Sun, 28 Sep 2008 20:39:49 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Smotri.Com]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[highload]]></category>
		<category><![CDATA[рит]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=71</guid>
		<description><![CDATA[22-23 сентября в Москве состоялась конференция РИТ: Высокие нагрузки. На ней я представил доклад &#171;Доставка видеоконтента пользователям&#187; (доклад строился на нашем опыте в NetStream по проектированию Smotri.Com).

Ниже вы найдете краткие тезисы доклады, полные тезисы, презентацию, а также видео с конференции.



Краткие тезисы

Цель доклада: познакомить аудиторию с возможными способами организации отдачи статического контента (видеофайлы) и «живых» потоков [...]]]></description>
			<content:encoded><![CDATA[<p>22-23 сентября в Москве состоялась конференция <a href="http://highload.info/">РИТ: Высокие нагрузки</a>. На ней я представил доклад &laquo;Доставка видеоконтента пользователям&raquo; (доклад строился на нашем опыте в <a href="http://netstream.ru/">NetStream</a> по проектированию <a href="http://smotri.com">Smotri.Com</a>).</p>

<p>Ниже вы найдете краткие тезисы доклады, полные тезисы, презентацию, а также видео с конференции.</p>

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

<h2>Краткие тезисы</h2>

<p><strong>Цель доклада</strong>: познакомить аудиторию с возможными способами организации отдачи статического контента (видеофайлы) и «живых» потоков вещания в условиях высокой нагрузки и географической распределенности.</p>

<p><strong>Аудитория доклада</strong>: уровень — средний, может быть интересно широкому классу специалистов.</p>

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

<p><strong>Основные вопросы:</strong></p>

<ol>
<li>Организация файлового хранилища: доступ к серверам, мониторинг, бэкап данных.</li>
<li>Настройка сервера для FLV-стриминга.</li>
<li>Географическая распределенность (зеркала) «своими руками»: концепция и её реализация.</li>
<li>Применение географической распределенности к файлам видео.</li>
<li>Организация вещаний (трансляций), вещания с большим числом зрителей, ретрансляция. Оптимизация сервера вещаний.</li>
<li>Применение географической распределенности к вещаниям.</li>
</ol>

<p><strong>Оценка значимости и области применимости:</strong>
Данные вопросы интересны как для видеохостинга, так и для произвольных вариантов вещаний через Internet (например, групповые видеочаты). Схожие проблемы могут возникать у любых ресурсов, имеющих контент довольно большого размера и требующих «быстрой» отдачи их пользователям (например, файлохранилища).
Описываемое решение не является полностью уникальным, существуют другие более общие решения для части проблем, например, для файлов это GFS, концепция CDN и т.п. Для вещаний, насколько я знаю, подобные вопросы обсуждаются впервые.</p>

<h2>Видео с конференции</h2>

<p><object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="400" height="330"><param name="movie" value="http://pics.smotri.com/scrubber_custom8.swf?file=v634534991d&#038;bufferTime=3&#038;autoStart=false&#038;str_lang=rus&#038;xmlsource=http%3A%2F%2Fpics.smotri.com%2Fcskins%2Fblue%2Fskin_color_black.xml&#038;xmldatasource=http%3A%2F%2Fpics.smotri.com%2Fskin_ng.xml" /><param name="allowScriptAccess" value="always" /><param name="allowFullScreen" value="true" /><param name="bgcolor" value="#ffffff" /><embed src="http://pics.smotri.com/scrubber_custom8.swf?file=v634534991d&#038;bufferTime=3&#038;autoStart=false&#038;str_lang=rus&#038;xmlsource=http%3A%2F%2Fpics.smotri.com%2Fcskins%2Fblue%2Fskin_color_black.xml&#038;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"></embed></object></p>

<h2>Презентация</h2>

<p><object codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" id="doc_200464541682834" name="doc_200464541682834" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" align="middle"  height="500" width="100%">      <param name="movie" value="http://documents.scribd.com/ScribdViewer.swf?document_id=6285268&#038;access_key=key-6btipn6gd90fp8tazii&#038;page=&#038;version=1&#038;auto_size=true&#038;viewMode=">       <param name="quality" value="high">         <param name="play" value="true">        <param name="loop" value="true">        <param name="scale" value="showall">        <param name="wmode" value="opaque">         <param name="devicefont" value="false">     <param name="bgcolor" value="#ffffff">      <param name="menu" value="true">        <param name="allowFullScreen" value="true">         <param name="allowScriptAccess" value="always">         <param name="salign" value="">          <embed src="http://documents.scribd.com/ScribdViewer.swf?document_id=6285268&#038;access_key=key-6btipn6gd90fp8tazii&#038;page=&#038;version=1&#038;auto_size=true&#038;viewMode=" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer" play="true" loop="true" scale="showall" wmode="opaque" devicefont="false" bgcolor="#ffffff" name="doc_200464541682834_object" menu="true" allowfullscreen="true" allowscriptaccess="always" salign="" type="application/x-shockwave-flash" align="middle"  height="500" width="100%"></embed>    </object><div style="font-size:10px;text-align:center;width:100%"><a href="http://www.scribd.com/doc/6285268/-2008">презентация на РИТ Высокие нагрузки &#8211; 2008</a> &#8211; <a href="http://www.scribd.com/upload">Upload a Document to Scribd</a></div></p>

<h2>Полный текст доклада</h2>

<p><a title="View Доставка видеоконтента пользователям document on Scribd" href="http://www.scribd.com/doc/8587493/-" style="margin: 12px auto 6px auto; font-family: Helvetica,Arial,Sans-serif; font-style: normal; font-variant: normal; font-weight: normal; font-size: 14px; line-height: normal; font-size-adjust: none; font-stretch: normal; -x-system-font: none; display: block; text-decoration: underline;">Доставка видеоконтента пользователям</a> <object codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" id="doc_492526224648429" name="doc_492526224648429" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" align="middle" height="500" width="650"> <param name="movie" value="http://documents.scribd.com/ScribdViewer.swf?document_id=8587493&#038;access_key=key-29vnkm8ulowt3jo2vb2c&#038;page=1&#038;version=1&#038;viewMode=list"> <param name="quality" value="high"> <param name="play" value="true"> <param name="loop" value="true"> <param name="scale" value="showall"> <param name="wmode" value="opaque"> <param name="devicefont" value="false"> <param name="bgcolor" value="#ffffff"> <param name="menu" value="true"> <param name="allowFullScreen" value="true"> <param name="allowScriptAccess" value="always"> <param name="salign" value=""> <param name="mode" value="list"> <embed src="http://documents.scribd.com/ScribdViewer.swf?document_id=8587493&#038;access_key=key-29vnkm8ulowt3jo2vb2c&#038;page=1&#038;version=1&#038;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_492526224648429_object" menu="true" allowfullscreen="true" allowscriptaccess="always" salign="" type="application/x-shockwave-flash" align="middle" mode="list" height="500" width="650"></embed> </object> <div style="margin: 6px auto 3px auto; font-family: Helvetica,Arial,Sans-serif; font-style: normal; font-variant: normal; font-weight: normal; font-size: 12px; line-height: normal; font-size-adjust: none; font-stretch: normal; -x-system-font: none; display: block;"> <a href="http://www.scribd.com/upload" style="text-decoration: underline;">Publish at Scribd</a> or <a href="http://www.scribd.com/browse" style="text-decoration: underline;">explore</a> others: <a href="http://www.scribd.com/browse?c=119-internet" style="text-decoration: underline;">Internet</a> <a href="http://www.scribd.com/browse?c=114-technology" style="text-decoration: underline;">Technology</a> </div></p>

<h2>Материалы</h2>

<ul>
<li><a href='http://www.smira.ru/wp-content/uploads/2008/09/abstract.odt'>Эти же тезисы</a> (OpenDocument)</li>
<li><a href='http://www.smira.ru/wp-content/uploads/2008/09/dhndhudhdhudhndhndhny-dhdh-dhdhdhc-dhnnfdhdhdhdhu-dhdhdhnnfdhdhdh-2008.ppt'>Презентация на РИТ: Высокие нагрузки (2008)</a> (PowerPoint)</li>
<li><a href='http://www.smira.ru/wp-content/uploads/2008/12/video.pdf'>Полный текст доклада</a> (PDF)</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/09/28/rit-highload-2008/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>MDC: скоро запуск</title>
		<link>http://www.smira.ru/2008/08/27/mdc-to-be-launched-soon/</link>
		<comments>http://www.smira.ru/2008/08/27/mdc-to-be-launched-soon/#comments</comments>
		<pubDate>Wed, 27 Aug 2008 15:41:14 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[MDC]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[mdc]]></category>
		<category><![CDATA[разработка]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=70</guid>
		<description><![CDATA[Близко-близко уже дата запуска нашего нового суперкрутого проекта. Да, мы гордимся им! На сайте уже тикает будильник: http://mdc.ru/
]]></description>
			<content:encoded><![CDATA[<p>Близко-близко уже дата запуска нашего нового суперкрутого проекта. Да, мы гордимся им! На сайте уже тикает будильник: <a href="http://mdc.ru/">http://mdc.ru/</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/08/27/mdc-to-be-launched-soon/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>Лог комита: зачем он нужен на самом деле?</title>
		<link>http://www.smira.ru/2008/08/13/commit-log/</link>
		<comments>http://www.smira.ru/2008/08/13/commit-log/#comments</comments>
		<pubDate>Wed, 13 Aug 2008 04:52:42 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Разработка]]></category>
		<category><![CDATA[svn]]></category>
		<category><![CDATA[разработка]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=69</guid>
		<description><![CDATA[Разработчики уже давно привыкли пользоваться системами контроля версий. Для кого-то это является естественным переходом, кто-то воспринимает сначала систему контроля версий как некоторое дополнительное усложнение своей работы, но работа над проектом в команде невозможна без этого инструмента.

Очень часто переход от мышления &#171;я сохранил файл &#8211; код зафиксирован&#187; к мышлению &#171;я сделал комит &#8211; код зафиксирован&#187; натыкается [...]]]></description>
			<content:encoded><![CDATA[<p>Разработчики уже давно привыкли пользоваться системами контроля версий. Для кого-то это является естественным переходом, кто-то воспринимает сначала систему контроля версий как некоторое дополнительное усложнение своей работы, но работа над проектом в команде невозможна без этого инструмента.</p>

<p>Очень часто переход от мышления &laquo;я сохранил файл &#8211; код зафиксирован&raquo; к мышлению &laquo;я сделал комит &#8211; код зафиксирован&raquo; натыкается на то, что процесс комита требует написания <em>лога комита</em>. Первое решение &#8211; оставить лог пустым или написать что-то из:</p>

<ul>
<li>&laquo;фикс бага&raquo;;</li>
<li>&laquo;закомитил всё, что сделал&raquo;;</li>
<li>&laquo;тестовый комит&raquo;;</li>
<li>&laquo;исправил опечатку&raquo;;</li>
<li>и т.п.</li>
</ul>

<p>Почему это плохо?</p>

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

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

<p>Пытаясь удовлетворить приведенным выше требованиям разработчик часто возьмет заголовок тикета и впишет его в лог комита. Однако, чаще всего тикет формулируется в терминах не разработки, а исходной задачи, например: &laquo;Сделать отступ 10 пикселей между блоками А и Б&raquo;. Такая фраза в качестве лога комита в CSS-файл сообщает информацию о изменении, но какую информацию она дает <em>читающему</em> такой лог? Ведь на самом деле, например, был создан новый CSS-класс с именем таким-то, из другого CSS-класса был удален какой-то стиль и т.п. То есть само по себе изменение описывается на другом языке, не на языке менеджера проекта. Было бы полезно иметь и то, и другое представление комита, например:</p>

<pre><code>Сделать отступ 10 пикселей между блоками А и Б: был 
создан новый класс blockC, из класса blockA удалено 
padding-bottom: 0.5em, т.к. все paddingи перенесены в 
класс blockC.

Тикет: #1734
</code></pre>

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

<p>Что нового дает такой лог комита по сравнению с логом: &laquo;закомитил CSS-фикс&raquo;?</p>

<p>Мне кажется, основное преимущество состоит в <em>вербализации</em> изменения. И оно даже не для читающего этот лог когда-то, а для пишущего. Процесс программирования сегодня особенно в web-разработке очень быстрый. Мы не пишем псевдо-код, редко рисуем диаграммы, часто между постановкой задачи и выкладкой нового кода на рабочие сервера может пройти несколько часов. За эти часы код не может пройти естественные этапы: отлежаться &laquo;пару дней&raquo;, чтобы автор мог вернуться к нему с &laquo;незамутненным взглядом&raquo;, не успеет пройти code-review, другие разработчики не успеют получить его из репозитория, и т.п. В то время как основная масса кода оказывается лучше изучена и протестирована за время от одного релиза до другого. Но и очень часто мы допускаем ошибки просто в силу того, что упускаем из вида важные детали из-за каких-то еще причин: отсутствия опыта, усталости, частого переключения между задачами и т.п.  Как обеспечить качество разработки в таких условиях?</p>

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

<p>Код написан, и вот сейчас подошло время фиксировать изменение в репозитории, а значит, писать лог комита! И это самое время попробовать вербализировать своё изменение на естественном языке. Если ограничиться отпиской вида &laquo;фикс бага&raquo; или скопировать часть фразы из тикета, процесс вербализации не включается, т.к. мы ничего не формулируем. А давайте попробуем на хорошем русском языке сформулировать что же именно мы изменили, почему мы изменили, какие это имеет последствия (возможно) для другого кода? Для того, чтобы сформулировать точно, что же именно мы изменили, потребуется просмотреть дифф текущего изменения, что замечательно, это фиксирует внимание на каждом блоке изменений в попытке рассмотреть каждую строчку кода, резюмировать изменения общей идеей, не упуская при этом важных деталей. Этот дополнительный взгяд на дифф изменений позволяет найти как простые технические ошибки, как, например, отсутствие докблока, так и нетривиальные &#8211; забытые отладочные операторы, изменения, сделанные временно в процессе программирования, &laquo;меньше&raquo; вместо &laquo;больше&raquo; и т.п. </p>

<p>Процесс формулирования самого лога комита на естественном языке позволяет найти логические ошибки в изменении, когда мы понимаем, что наше исправление неполно, избыточно, противоречиво, не решает исходную задачу и т.п. Магия состоит именно в попытке сформулировать рассказ об изменении, т.е. рассказать о своем изменении другим разработчикам. Наверное, многие замечали, что иногда достаточно подойти за помощью к другому разработчику и <em>рассказать</em> о проблеме, чтобы тут же в голове появилось решение. Именно процесс вербализации нашей потусторонней, фантастической деятельности программистов, заключающейся в переводе мыслей заказчика в фразы языка программирования, позволяет осознать суть сделанных изменений и найти скрытую ошибку в коде. Поэтому&#8230; пишите хорошие логи комитов!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/08/13/commit-log/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>ffmpeg: &#171;утечка&#187; памяти (av_read_packet)</title>
		<link>http://www.smira.ru/2008/07/10/ffmpeg-memory-leak-av_read_packet/</link>
		<comments>http://www.smira.ru/2008/07/10/ffmpeg-memory-leak-av_read_packet/#comments</comments>
		<pubDate>Thu, 10 Jul 2008 16:25:30 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[Smotri.Com]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[ffmpeg]]></category>
		<category><![CDATA[утечка]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=68</guid>
		<description><![CDATA[Может, это кому-то поможет сохранить пару часов отладки. Когда читаем видеопоток из файла, делаем что-то наподобие следующего:

AVPacket *m_pPacket = (AVPacket *)malloc(sizeof(AVPacket))
while(1)
{
     int ret = av_read_frame(formatCtx, m_pPacket);

     if (ret &#60; 0)
         // error

     if (m_pPacket.stream_index == [...]]]></description>
			<content:encoded><![CDATA[<p>Может, это кому-то поможет сохранить пару часов отладки. Когда читаем видеопоток из файла, делаем что-то наподобие следующего:</p>

<pre><code>AVPacket *m_pPacket = (AVPacket *)malloc(sizeof(AVPacket))
while(1)
{
     int ret = av_read_frame(formatCtx, m_pPacket);

     if (ret &lt; 0)
         // error

     if (m_pPacket.stream_index == videoStreamIndex)
     {
         .... avcodec_decode_video ...
     }
 }
 av_free_packet(m_pPacket);
 free(m_pPacket);
</code></pre>

<p>Так вот, это <strong>неправильно</strong>, нужно вызывать av&#95;free&#95;packet после каждого av&#95;read&#95;packet:</p>

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

<pre><code>AVPacket *m_pPacket = (AVPacket *)malloc(sizeof(AVPacket))
while(1)
{
     int ret = av_read_frame(formatCtx, m_pPacket);

     if (ret &lt; 0)
         // error

     if (m_pPacket.stream_index == videoStreamIndex)
     {
         .... avcodec_decode_video ...
     }
     av_free_packet(m_pPacket);
 }
 free(m_pPacket);
</code></pre>

<p>P.S. Может, из текста не совсем понятно, это не бага в самом ffmpeg, это моя программа,  используя libavcodec, libavformat (библиотеки ffmpeg), как я написал в начале поста, &laquo;текла&raquo;. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/07/10/ffmpeg-memory-leak-av_read_packet/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>МТС, Евросеть и Билайн</title>
		<link>http://www.smira.ru/2008/07/10/mts-euroset-beeline/</link>
		<comments>http://www.smira.ru/2008/07/10/mts-euroset-beeline/#comments</comments>
		<pubDate>Thu, 10 Jul 2008 14:06:44 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Личное]]></category>
		<category><![CDATA[cell]]></category>
		<category><![CDATA[сотовый]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=67</guid>
		<description><![CDATA[04.07.08 (пятница)

Еду в машине, никого не трогаю. В телефоне симка МТС, которой уже много-много лет. Оператора и телефонный номер сто лет не менял, но последнее время напрягала сумма платежей МТСу (доходило до 2000 руб. каждый месяц при звонках внутри Москвы). Так вот, еду, в телефоне MIP живёт, аська зеленеет. Тут соединение рвётся и MIP делает [...]]]></description>
			<content:encoded><![CDATA[<h2>04.07.08 (пятница)</h2>

<p>Еду в машине, никого не трогаю. В телефоне симка МТС, которой уже много-много лет. Оператора и телефонный номер сто лет не менял, но последнее время напрягала сумма платежей МТСу (доходило до 2000 руб. каждый месяц при звонках внутри Москвы). Так вот, еду, в телефоне MIP живёт, аська зеленеет. Тут соединение рвётся и MIP делает тщетные попытки переподключиться. Ему это не удается ни за десять ни за двадцать минут. Телефон мой пишет &laquo;Нед доступа к сети&raquo;, что звучит вполне как приговор. Подумал, что сота накрылась в том месте, где я ехал, но сеть не появилась и через час. </p>

<p>Звонок в МТС. После стандартных советов &laquo;включите-выключите телефон&raquo; следует вердикт: &laquo;Скорее всего, сим-карта сломалась&raquo;. И предлагают &laquo;вставить её в другой телефон&raquo;. Я один был, нет у меня другого телефона. Ну ладно, а где у вас ближайший центр обслуживания, чтобы съездить в субботу?</p>

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

<h2>05.07.08 (суббота)</h2>

<p>Лень ехать чинить МТСовскую симку, еду в ближайшую Евросеть &#8211; пять минут на машине, предварительно выбрав понравившийся тарифный план Билайн на их сайте. Почему не МТС &#8211; потому что было интересно попробовать что-то еще, а вдруг качественнее и дешевле? Отстоял очередь в Евросети плативших за телефон (свободных сотрудников нет), и получил ответ &laquo;к этому тарифному плану Билайн больше не подключает&raquo;. Странно, ну ладно. Выбрал другой, предоплатный тариф. &laquo;У нас таких нет в продаже&raquo; &#8211; получил я ответ. Осталось только развернуться и уйти после такого желания осчастливить клиента контрактом <img src='http://www.smira.ru/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>

<p>В следующем торговом центре (еще 10 минут на машине) как назло оказалась опять Евросеть, в ней нашелся нужный тарифный план, и вот я счастливый обладатель Билайна. Приехав домой и разглядев коробку с симкой повнимательнее, осознал, что ей уже год (sic!). И хотя и называется тарифный план так, как я хотел &#8211; расценки совсем не те. Окей, звоню в Билайн. &laquo;Девушка, а сколько у меня стоит минута?&raquo; &#8230; &laquo;6 руб.&raquo; .. &laquo;А как мне перейти на новые расценки по этому тарифному плану?&raquo;. Девушка присылает мне СМС с номером, звоню по нему, на всё соглашаюсь, получаю ответ: &laquo;Вы и так находитесь на этом тарифном плане&raquo;. Хм. Звоню снова, жалуюсь, молодой человек через минуту отвечает: &laquo;А у вас контракт не зарегистирован, вот поэтому и нельзя сменить тарифный план. Через семь <em>рабочих</em> дней всё будет хорошо&raquo;. Отлично, из-за этого у меня еще и не работает управление контрактом через Интернет. </p>

<h2>06.07.08 (воскресенье)</h2>

<p>За это время обнаружил, что в отличие от МТС и Мегафон, Билайн отлично ловится у меня в квартире, а (о чудо!) в GPRS входит телефон с первой попытки. Прощай МТС (за качество связи). </p>

<p>Проснулся, приходит СМС на Билайн: &laquo;Ваш номер заблокирован&raquo;. Хм, интересно, а ведь вчера 500 рублей кинул и они дошли до счета. Звоню. Девушка выясняет всё, сама удивляется, разблокирует. Говорит что проблема в том, что &laquo;мой контракт не зарегистрирован&raquo;. Кажется, из-за этого все беды.</p>

<p>Продолжаю наслаждаться Билайном и тишиной (новый мой номер почти никто не знает, а старая симка так и лежит мертвая).</p>

<h2>07.07.08 (понедельник)</h2>

<p>Привычное уже &laquo;ваш телефон заблокирован&raquo;. Звоню в Билайн, шучу с девушкой-оператором, что буду ей каждое утро звонить <img src='http://www.smira.ru/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />  Разблокировали, и (о чудо!) он до сегодняшнего дня больше не блокировался (тьфу-тьфу).</p>

<p>Заказываю в МТС бесплатную доставку сим-карты рано утром. Вечером звонят из МТС, но не успеваю подобрать вызов, не перезванивают.</p>

<h2>08.07.08 (вторник)</h2>

<p>Звонили из МТС, обещали привезти симку, уточняли адрес, он из формы до них дошел в странном виде. Ждём&#8230;</p>

<h2>09.07.08 (среда)</h2>

<p>Привезли симку! На карточке, к которой прикреплена симка, фотография двух молодых парней, нежно смотрящих друг на друга, один у другого на плече. Я вроде бы не гей, но шут бы с ними.</p>

<p>Вставляю симку в телефон &#8211; новое чудо &laquo;сим-карта не активна&raquo;. Стоп-стоп-стоп. Звоню в МТС, получаю совет &laquo;включить и выключить телефон, подождать&raquo;. (Видимо, забывают сказать, что надо еще с бубном потанцевать). Матерюсь, меняю симку в телефоне Билайн на МТС и обратно &#8211; не помогает. Звоню еще раз &#8211; получаю гениальный ответ&#8230; Я такого ожидал: &laquo;Курьер привез симку <em>слишком быстро</em>, она не успела активироваться!&raquo;. Отлично, курьер видимо забыл &laquo;потупить&raquo;, как его учили в МТС. Обещают, что заработает через сутки.</p>

<h2>10.07.08 (четверг)</h2>

<p>МТСовская симка так и не заработала&#8230; Снова звоню, жалуюсь&#8230; Оператор задумался надолго. Догадывается у меня спросить номер симки&#8230; Диктую ему и получаю еще один фантастический ответ: &laquo;Вам не эту симку должны были привезти!&raquo; Хм, я отвечаю, что другой нет, и она вот такая в красивом красном конверте (про мужиков на фотографии умолчал). Оператор подумал и обещал до вечера всё исправить. </p>

<p>Вечером всё заработало, итак, почти неделя без МТСа&#8230; </p>

<p>Кто-нибудь еще хочет пользоваться услугами наших сотовых операторов? А покупать что-либо в Евросети?</p>

<p>P.S. МТС нужен только для того, чтобы установить переадресацию с автоинформатором на Билайн.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/07/10/mts-euroset-beeline/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Страуструп: наставления начинающему программисту</title>
		<link>http://www.smira.ru/2008/06/26/straustroup-beginner-programmer-advices/</link>
		<comments>http://www.smira.ru/2008/06/26/straustroup-beginner-programmer-advices/#comments</comments>
		<pubDate>Thu, 26 Jun 2008 04:29:48 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Разработка]]></category>
		<category><![CDATA[c++]]></category>
		<category><![CDATA[straustroup]]></category>
		<category><![CDATA[разработка]]></category>
		<category><![CDATA[с++]]></category>
		<category><![CDATA[страуструп]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=66</guid>
		<description><![CDATA[Прочитал интервью Бьерна Страуструпа для австралийского ComputerWorld. В этом интервью ему задают вопрос:


  Do you have any advice for up-and-coming programmers? 


Мне кажется, ответ на данный вопрос формулирует то самое, к чему должен стремиться любой программист. Итак, далее мой вольный перевод на русский:


  Можете что-то посоветовать начинающим программистам?
  
  Изучайте основы [...]]]></description>
			<content:encoded><![CDATA[<p>Прочитал <a href="http://www.computerworld.com.au/index.php/id;408408016;pp;6;fp;16;fpid;1">интервью</a> <a href="http://www.research.att.com/~bs/homepage.html">Бьерна Страуструпа</a> для австралийского ComputerWorld. В этом интервью ему задают вопрос:</p>

<blockquote>
  <p><strong>Do you have any advice for up-and-coming programmers?</strong> </p>
</blockquote>

<p>Мне кажется, ответ на данный вопрос формулирует то самое, к чему должен стремиться любой программист. Итак, далее мой вольный перевод на русский:</p>

<blockquote>
  <p><strong>Можете что-то посоветовать начинающим программистам?</strong></p>
  
  <p>Изучайте основы программирования: алгоритмы, архитектуру машин, структуры данных и т.д. Не копируйте слепо подходы из одного приложения в другое. Вы всегда должны знать, <em>что</em> вы делаете, быть уверенными, что ваша программа <em>работает</em>, и твёрдо знать, <em>почему</em> она работает. Не думайте, что вы можете предсказать, какой будет индустрия программирования через 5 лет и чем именно придётся заниматься вам, поэтому учитесь более общим и полезным приёмам и подходам. Старайтесь писать код, который <em>лучше</em>, код, который больше соответствует вашим принципам программирования. Работайте так, чтобы программирование в большей степени было профессиональной деятельностью, а не низкоуровневым &laquo;хакерством&raquo; (программирование &#8211; это и ремесло, но не только ремесло). Учитесь на классике в области разработки и с помощью лучших книг, не надо полагаться на &laquo;how to&raquo; и документацию в онлайне &#8211; она недостаточно глубоко затрагивает вопросы программирования.</p>
</blockquote>
]]></content:encoded>
			<wfw:commentRss>http://www.smira.ru/2008/06/26/straustroup-beginner-programmer-advices/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Web, кеширование и memcached: часть 2</title>
		<link>http://www.smira.ru/2008/06/23/web-cache-memcached-2/</link>
		<comments>http://www.smira.ru/2008/06/23/web-cache-memcached-2/#comments</comments>
		<pubDate>Sun, 22 Jun 2008 21:02:33 +0000</pubDate>
		<dc:creator>Андрей</dc:creator>
				<category><![CDATA[Smotri.Com]]></category>
		<category><![CDATA[Разработка]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[кеш]]></category>

		<guid isPermaLink="false">http://www.smira.ru/?p=64</guid>
		<description><![CDATA[Начало серии постов здесь. Продолжаем разбираться с этим вопросом.

Проблема №3. Одного мемкеша мало

Если сайт большой, данных много, кешей тоже много, в один memcached физически не умещаемся. Пожалуйста: создаем кластер мемкешовых серверов, хешируем ключи для определения номера сервера, на котором ключ должен храниться. Всё вроде бы хорошо до тех пор, пока эти сервера не начинают падать [...]]]></description>
			<content:encoded><![CDATA[<p>Начало серии постов <a href="http://www.smira.ru/2008/06/22/web-cache-memcached-1/">здесь</a>. Продолжаем разбираться с этим вопросом.</p>

<h2>Проблема №3. Одного мемкеша мало</h2>

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

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

<p>Самый простой алгоритм хеширования вида:</p>

<pre><code>$memcache_server_id = crc32($key) % count($memcache_servers);
</code></pre>

<p>начинает &laquo;веселиться&raquo;, когда обнаруживает, что сервер упал. Когда обнаруживается проблема сетевого доступа к серверу, <a href="http://pecl.php.net/package/memcache">драйвер memcachа</a> убирает его из списка $memcache<em>servers. При этом count($memcache</em>servers) изменяется, какая-то часть ключей &laquo;сдвигается&raquo; на другие сервера, при этом &laquo;пропадает&raquo; куча кешей (хорошо, если кешей, которые не так жалко потерять, а если речь идёт о сессиях пользователей?).</p>

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

<p>Когда куча ключей теряется, резко начинают перестраиваться кеши, вырастает нагрузка на БД, и т.д., и т.п., то есть падение одного сервера мемкеша заваливает весь &laquo;отказоустойчивый&raquo; кластер.</p>

<p>Что делать? Есть <a href="http://www.lastfm.ru/user/RJ/journal/2007/04/10/rz_libketama_-_a_consistent_hashing_algo_for_memcache_clients">другие алгоритмы хеширования</a> ключей, устойчивые к удалению/добавлению серверов в пул. Для <a href="http://pecl.php.net/package/memcache">PHP-шного модуля Memcache</a> это:</p>

<pre><code>ini_set('memcache.hash_strategy', 'consistent');
</code></pre>

<h2>Проблема №4. Одновременное перестроение кеша несколькими мордами</h2>

<p>Если мы выставляем ключу (кешу) некоторое время жизни, рано или поздно, memcache скажет, что такого ключа на сервере нет. А если это &laquo;популярный&raquo; кеш, например, использующийся на главной странице, то такую ситуацию могут &laquo;обнаружить&raquo; одновременно несколько морд. И они все попытаются построить этот кеш, то есть они все одновременно отправят запрос в БД, то есть они отправят <em>много</em> запросов в БД, причём за короткий промежуток времени. Конечно, как только первый запрос завершится, морда запишет новое значение ключа и больше новых запросов за этой выборкой не будет. Но мы уже подвергли БД высокой нагрузке, хотя хотели этого избежать при помощи кеширования.</p>

<p>Напрашивается решение: пока одна морда строит кеш, все остальные должны её &laquo;подождать&raquo;. Как это реализовать? Например, с помощью блокировок в том же мемкеше. Перед тем, как начать строить кеш для ключа mykey, прове