<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-241164918537745539</id><updated>2012-01-20T01:01:24.820+12:00</updated><category term='simplicity'/><category term='couchdb'/><category term='clustering'/><category term='teamcity'/><category term='continuous integration'/><category term='asynchronous'/><category term='cache'/><category term='distributed cache'/><category term='log analysis'/><category term='redis'/><category term='development'/><category term='apache cassandra'/><category term='messaging'/><category term='maven'/><category term='terracotta'/><category term='devoxxx'/><category term='quizz'/><category term='ooad'/><category term='chenbro'/><category term='asynchronous processing'/><category term='remote shell'/><category term='memcache'/><category term='locks'/><category term='scalaris'/><category term='lease'/><category term='spring'/><category term='ioc-container'/><category term='antlr'/><category term='findbugs'/><category term='load-testing'/><category term='windows'/><category term='task manager'/><category term='spring beans'/><category term='hardware'/><category term='message ordering'/><category term='synology'/><category term='amdahl&apos;s law'/><category term='deploy'/><category term='project voldemort'/><category term='linux'/><category term='sacling vectors'/><category term='key-value'/><category term='dependency injection'/><category term='theory'/><category term='math'/><category term='scalability'/><category term='java'/><category term='mysql'/><category term='php'/><category term='jdk'/><category term='interruption'/><category term='programming'/><category term='moore law'/><category term='non-blocking collections'/><category term='concurrency'/><category term='mongodb'/><category term='nas'/><category term='multi-core cpu'/><category term='scatter-gather'/><category term='activemq'/><category term='pipelining'/><category term='mysqlnd'/><category term='multi-threading'/><category term='groovy'/><category term='q4m'/><category term='fail-fast'/><category term='qnap'/><category term='atom'/><category term='dsl'/><category term='memcachedb'/><category term='out-of-memory'/><category term='perfomance'/><category term='supermicro'/><category term='null-handling'/><title type='text'>Severe reality</title><subtitle type='html'>The endless way...</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>37</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-8833293252619374250</id><published>2011-06-29T23:38:00.009+12:00</published><updated>2011-06-30T11:11:04.889+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='deploy'/><title type='text'>Deploy и прочие неприятности</title><content type='html'>&lt;p&gt;В продолжение &lt;a href="http://dotsid.blogspot.com/2011/04/blog-post.html"&gt;предыдущего поста&lt;/a&gt; хочу немного рассказать о том как у нас происходит deploy. В прошлый раз мы закончили на том что артефакт доставлен на production и готов к развертыванию. Начинается самое интересное, процесс деплоя.&lt;/p&gt;&lt;span class="fullpost"&gt; &lt;p&gt;Но для начала надо немного описать платформу на которую мы деплоим наши приложения. В качестве servlet-container'а мы используем &lt;a href="http://jetty.codehaus.org/jetty/"&gt;Jetty&lt;/a&gt;. Есть два основных типа приложений, которые мы пишем и сопровождаем: web-приложения, которые имеют некий web-интерфейс (это может быть как UI, так и REST интерфейс), а также background сервисы, которые как правило работают асинхронно (мы активно используем &lt;a href="http://activemq.apache.org/"&gt;ActiveMQ&lt;/a&gt;). И здесь нужно подметить первую особенность, мы используем Jetty для обоих типов приложений. Это может показатся странным, так как для background-сервисов мы получаем явный overhead связанный с запуском и работой servlet-container'а. Но этот overhead просто мизерный по сравнению с тем какие плюсы мы получаем. А именно:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;единые утилиты управления lifecycle'ом приложения вне зависимости от его типа (старт, останов, перезапуск);&lt;/li&gt; &lt;li&gt;стандартные способы задания конфигурации приложения вне зависимости от его типа;&lt;/li&gt; &lt;li&gt;стандартные форматы распространения приложения вне зависимости от его типа (в нашем случае &lt;code&gt;.war&lt;/code&gt;).&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Вобщем, стандартизация на лицо.&lt;/p&gt; &lt;p&gt;У нас своя сборка Jetty в которой кроме самого Jetty, есть еще несколько часто используемых нами библиотек (например: &lt;code&gt;slf4j&lt;/code&gt;, &lt;code&gt;logback&lt;/code&gt;, &lt;code&gt;mysql&lt;/code&gt; драйвер, библиотеки connection pooling'а и некоторые другие). Сборка представлена в виде rpm пакета, что позволяет очень быстро подготавливать новую машину: &lt;code&gt;yum install jdk jetty&lt;/code&gt;, готово.&lt;/p&gt; &lt;p&gt;Вас может удивило почему мы ставим на production машины JDK, а не JRE. Это связано с наличием в JDK очень полезных для диагностики утилит, таких как: &lt;code&gt;jmap&lt;/code&gt;, &lt;code&gt;jstack&lt;/code&gt;, &lt;code&gt;jvisualvm&lt;/code&gt; и других. Это выгодно отличает платформу Java от множества других, -- масса утилит для диагностики, которые в большинстве случаев позволяют довольно быстро понять что не так с приложением.&lt;/p&gt; &lt;p&gt;Деплой приложения состоит из следующих шагов:&lt;/p&gt; &lt;ul&gt; &lt;li&gt;распространение бинарника с приложением, а также конфигурации на целевые машины;&lt;/li&gt; &lt;li&gt;перезапуск servlet container'а;&lt;/li&gt; &lt;li&gt;deployment тестирование.&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;Естественно второй и третий шаг выполняются для всех машин по очереди.&lt;/p&gt; &lt;h2&gt;Распространение артефакта и конфигурации&lt;/h2&gt; &lt;p&gt;Тут все просто, &lt;code&gt;scp&lt;/code&gt; доставляет файлы на каждую машину по очереди и ложит каждый в нужное место. Доставляются артефакты, а также конфигурационные файлы.&lt;/p&gt; &lt;h2&gt;Перезапуск servlet container'а&lt;/h2&gt; &lt;p&gt;Мы не используем hot redeploy, так как он вызывает ряд проблем. В частности, небезизвестную &lt;a href="http://www.jroller.com/agileanswers/entry/preventing_java_s_java_lang"&gt;OutOfMemoryError: PermGen&lt;/a&gt;. Для перезапуска используются скрипты входящие в стандартную поставку Jetty с некоторыми несущественными дополнениями.&lt;/p&gt; &lt;h2&gt;Deployment тестирование&lt;/h2&gt; &lt;p&gt;Этот момент стоит описать поподробнее. После того как Jetty запустился было бы неплохо проверить что приложение способно выполнять основные функции. Тот кто деплоит приложение может опечататься в конфигурации, системные администраторы могут забыть "пропилить дырку" в firewall'е для необходимого приложению сервиса, мало ли что еще может произойти.&lt;/p&gt; &lt;p&gt;Задача deployment тестирования проверить что у приложения есть доступ ко всем необходимым сервисам для того чтобы оно выполняло свою работу. Это могут быть базы данных, очереди сообщений, сетевые файловые системы и другое middleware ПО. К счастью для нас, Spring Beans контейнер практически все делает за нас. Если во время инициализации контейнера (частью которого являются подключения к БД и т.д.) происходит ошибка, об этом легко узнать послав &lt;code&gt;GET&lt;/code&gt; запрос на любой url, который обрабатывается непосредственно spring'ом. У нас есть соглашение что url &lt;code&gt;/status&lt;/code&gt; не занимается приложением и служит для deployment тестирования. Этот url также сообщает какая версия приложения запушена в данный момент. Таким образом, deploy скрипт после того как перезапускает servlet container начинает опрашивать приложение. Если приложение возвращает 200-й статус код, то можно переходить к обновлению следующей машины. Если нет, то процесс деплоя считается неуспешным и прерывается. Тот кто делает deploy может инициировать процедуру отката. Откат мы делаем руками, так как процент "битых" релизов у нас не велик и смысла автоматизировать эту процедуру пока что смысла нет.&lt;/p&gt; &lt;h1&gt;Rollout распределенных приложений&lt;/h1&gt; &lt;p&gt;Когда приложение запущено в нескольких экземплярах (на нескольких машинах), то после запуска приложения на одном ноде имеет смысл немного подождать прежде чем переходить к следующей машине. Здесь играют свою роль как особенности приложения, так и особенности платформы. Java, как русские -- "долго запрягает, но быстро едет" (начальная загрузка байт кода, создание пула соединений, JIT компилятор etc). Нас такой tradeoff вполне устраивает, поэтому наш deploy скрипт может быть сконфигурирован таким образом, чтобы давать только что запущенному ноду "разогреться" перед тем как принять на себя нагрузку его еще не обновленных коллег. Как правило, 3-5 секунд достаточно.&lt;/p&gt; &lt;p&gt;Деплой на несколько машин (особенно учитывая их "разогрев") порождает интересную проблему. Во время обновления кластера, на нем работает &lt;em&gt;две версии приложения&lt;/em&gt;. Некоторых людей этот факт ставит в ступор. Они не могут ужиться с тем, что у приложения в любой момент времени нет строго определенной версии.&lt;/p&gt; &lt;p&gt;Суровая реальность заключается в том, что требование работать одновременно в "нескольких версиях" порождается природой web-приложений. Мы стремимся к тому чтобы наше система всегда была online. Это имеет одно очень важное последствие для процесса deploy'я:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;Мы всегда должны иметь возможность откатиться на предыдущую версию системы, так как пусть редко, но бывает что ошибка все же проходит сквозь все рубежи тестирования и попадает на production. Причем rollback должен осуществлятся не медленнее чем rollout.&lt;/p&gt; &lt;/blockquote&gt; &lt;p&gt;Это приводит нас к следующему заключению: &lt;em&gt;следующая версия системы всегда должна быть обратно-совместима с предыдущей&lt;/em&gt;. Это касается не только кода, но и схемы БД, данных в кеше и т.д.&lt;/p&gt; &lt;p&gt;Оказывается, что когда новая версия системы обратно совместима с текущей, нет ничего страшного в том чтобы некоторое время они поработали вместе.&lt;/p&gt; &lt;h1&gt;На практике&lt;/h1&gt; &lt;p&gt;Итак, как это все происходит на практике. Разработчик заходит через &lt;code&gt;ssh&lt;/code&gt; на машину-координатор, куда автоматически доставляются артефакты приложений. У каждого приложения есть отдельная директория содержимое которой выглядит примерно следующим образом:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;-rw-rw-r-- 1 tech tech      101 Jun 24 16:42 .config
-rw-rw-r-- 1 tech tech     1134 Jun  9 13:05 config.properties
-rw-r--r-- 1 tech tech      163 May 16 16:11 .jettyrc
-rw-r--r-- 1 tech tech 30298960 Jun  3 19:41 search-web-frontend-1.0.118.war
-rw-r--r-- 1 tech tech 30299011 Jun  3 21:25 search-web-frontend-1.0.119.war
-rw-r--r-- 1 tech tech 30298949 Jun 13 21:51 search-web-frontend-1.0.120.war
-rw-r--r-- 1 tech tech 30297647 Jun 14 12:38 search-web-frontend-1.0.121.war
-rw-r--r-- 1 tech tech 30297689 Jun 15 11:50 search-web-frontend-1.0.122.war
-rw-r--r-- 1 tech tech 30298356 Jun 15 12:13 search-web-frontend-1.0.123.war
-rw-r--r-- 1 tech tech 30298678 Jun 15 12:32 search-web-frontend-1.0.124.war
-rw-r--r-- 1 tech tech 30732203 Jun 23 11:45 search-web-frontend-1.0.125.war
-rw-r--r-- 1 tech tech 30732204 Jun 23 11:55 search-web-frontend-1.0.126.war
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Здесь есть несколько файлов о которых стоит рассказать поподробнее. Файл &lt;code&gt;.config&lt;/code&gt; это обыкновенный &lt;code&gt;ini&lt;/code&gt;-файл хранящий имена всех машин на которых установлено приложение, а также некоторые другие настройки развертывания. &lt;code&gt;config.properties&lt;/code&gt; &amp;mdash; это &lt;code&gt;properties&lt;/code&gt;-файл, который содержит настройки приложения. &lt;code&gt;.jettyrc&lt;/code&gt; содержит startup опции виртуальной машины на которой стартует Jetty.&lt;/p&gt; &lt;p&gt;За довольно длительное время мы перепробовали различные способы работы с конфигурационными файлами приложений. Мы хранили их в development системе контроля версий. Мы создавали для них отдельный репозиторий в зоне production. Текущая схема нам нравится больше всего. Она позволяет автоматически бекапить все конфигурационные файлы приложений, что безусловно необходимо, а также не связываться с излишей сложностью VCS систем для управления "двумя файлами из 10 строчек каждый".&lt;/p&gt; &lt;p&gt;Если запустить команду deploy без аргументов, то она выведет текущее состояние нодов:&lt;/p&gt; &lt;pre&gt;&lt;code&gt;$ deploy 
http://search-service1:8080 - Ok com.farpost.search:search-web-frontend 1.0.126
http://search-service2:8080 - Ok com.farpost.search:search-web-frontend 1.0.126
http://search-service3:8080 - Ok com.farpost.search:search-web-frontend 1.0.126
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Благодаря &lt;a href="http://maven.apache.org/"&gt;maven&lt;/a&gt; во все наши сборки автоматически попадает информация о версии, а также номере билда.&lt;/p&gt; &lt;p&gt;Если же передать команде deploy имя артефакта, то начнется его развертывание на production машинах.&lt;/p&gt; &lt;h1&gt;Дальнейшие соображения&lt;/h1&gt; &lt;p&gt;Текущий процесс нас вполне устраивает, тем не менее у нас есть идеи как сделать его еще лучше.&lt;/p&gt; &lt;h2&gt;Runtime обновление конфигурации логгирования&lt;/h2&gt; &lt;p&gt;Мы используем &lt;a href="http://logback.qos.ch/"&gt;logback&lt;/a&gt; в качестве библиотеки логгирования. Существенным ее плюсом является то что она позволяет &lt;a href="http://logback.qos.ch/manual/configuration.html#autoScan"&gt;менять конфигурацию логгирования на лету&lt;/a&gt;. Достаточно просто поменять XML файл с конфигурацией. Было бы неплохо иметь возможность распространять конфигурацию логгирования на машины без перезагрузки самого приложения.&lt;/p&gt; &lt;h2&gt;Дифференцирование конфигурации различных нодов&lt;/h2&gt; &lt;p&gt;В распределенной системе разные ноды могут иметь идентичную сборку, но разную конфигурацию, обуславливающую требуемую разность в их поведении. Сейчас у нас пока что нет возможности задать разную конфигурацию для разных нодов. Учитывая что конфигурация задается в виде &lt;code&gt;properties&lt;/code&gt; файлов, сделать такого рода дифференциорание не сложно.&lt;/p&gt; &lt;h2&gt;Partial deploy&lt;/h2&gt; &lt;p&gt;Иногда бывает необходимо обновить не весь кластер, а только один нод из кластера. В будущем, я думаю мы сделаем такую возможность.&lt;/p&gt; &lt;p&gt;Вот пожалуй и все. А как деплоите приложения вы? ;)&lt;/p&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-8833293252619374250?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/8833293252619374250/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2011/06/deploy.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/8833293252619374250'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/8833293252619374250'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2011/06/deploy.html' title='Deploy и прочие неприятности'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-8474758996390922577</id><published>2011-04-17T18:44:00.004+12:00</published><updated>2011-04-17T19:17:05.305+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='quizz'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>Fair lock</title><content type='html'>&lt;p&gt;Не так давно у нас на собеседовании был кандидат, который произвел довольно хорошее впечатление, поэтому было решено предложить ему более сложную задачу, которую обычно мы не спрашиваем. Вот ее немного видоизмененный вариант:&lt;/p&gt;

&lt;blockquote&gt;Переделайте следующий код оставив его многопоточным таким образом, чтобы лампочки зажигались и гасли строго по очереди и в любой момент времени должна быть включена только одна лампочка:&lt;/blockquote&gt;

&lt;blockquote&gt;&lt;pre&gt;package me.bazhenov.bulb;

public class Main {

 public static void main(String[] args) {
  new Thread(new Bulb("first")).start();
  new Thread(new Bulb("seconds")).start();
 }
}

public class Bulb implements Runnable {

 private final String name;

 public Bulb(String name) {
  this.name = name;
 }

 public void run() {
  Thread self = currentThread();
  while(!self.isInterrupted()) {
   System.out.println(name + " bulb is on");
   try {
    sleep(300);
   } catch (InterruptedException e) {
    self.interrupt();
   }
   System.out.println(name + " bulb is off");
  }
 }
}&lt;/pre&gt;&lt;/blockquote&gt;

&lt;p&gt;Кандидат предложил использовать &lt;a href="http://download.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantLock.html"&gt;ReentrantLock&lt;/a&gt; в FairSync режиме. В первом приближении эта идея может показаться рабочей. Передать общий лок в оба оъекта типа Bulb и синхронизироватся там на нем. Тем не менее, этот подход не работает. Если мы заглянем в документацию к классу, то увидим следующее описание:&lt;/p&gt;

&lt;blockquote&gt;&lt;span class="Apple-style-span"   style="  ;font-family:Times;font-size:medium;"&gt;&lt;/span&gt;&lt;span class="Apple-style-span"   style="  ;font-family:Times;font-size:medium;"&gt;The constructor for this class accepts an optional&lt;/span&gt;&lt;span class="Apple-style-span"   style="  ;font-family:Times;font-size:medium;"&gt; &lt;/span&gt;&lt;span class="Apple-style-span"   style="  ;font-family:Times;font-size:medium;"&gt;&lt;em&gt;fairness&lt;/em&gt;&lt;/span&gt;&lt;span class="Apple-style-span"   style="  ;font-family:Times;font-size:medium;"&gt; &lt;/span&gt;&lt;span class="Apple-style-span"   style="  ;font-family:Times;font-size:medium;"&gt;parameter. When set&lt;/span&gt;&lt;span class="Apple-style-span"   style="  ;font-family:Times;font-size:medium;"&gt; &lt;/span&gt;&lt;span class="Apple-style-span"   style="  ;font-family:Times;font-size:medium;"&gt;&lt;tt&gt;true&lt;/tt&gt;&lt;/span&gt;&lt;span class="Apple-style-span"   style="  ;font-family:Times;font-size:medium;"&gt;, under contention, locks favor granting access to the longest-waiting thread. Otherwise this lock does not guarantee any particular access order. [...] Note however, that fairness of locks does not guarantee fairness of thread scheduling.&lt;/span&gt;&lt;/blockquote&gt;

&lt;p&gt;FairSync не гарантирует отсутствие race condition'а между потоками. Единственное что он гарантирует это то, что лок возьмет поток который ждал на локе дольше всего. Отсутствие контроля за CPU шедулером не дает нам гарантии что в момент когда поток отпускает лок его оппонент уже попытался сделать acquire на этом же локе (что необходимо для того чтобы сработал FairSync в этой задаче).&lt;/p&gt;
&lt;p&gt;К сожалению у меня нет под рукой соответствующего железа, но я подозреваю что на однопроцессорной машине разницы между FairSync и NonfairSync вообще не будет, так как у параллельного потока не будет возможности поставить в очередь заявку на acquire, чтобы при следующем unlock'е его заявка была обслужена.&lt;/p&gt;

&lt;p&gt;Правильное же решение задачи остается на совести читателя :)&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-8474758996390922577?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/8474758996390922577/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2011/04/fair-lock.html#comment-form' title='Комментарии: 4'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/8474758996390922577'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/8474758996390922577'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2011/04/fair-lock.html' title='Fair lock'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-3622657711977585627</id><published>2011-04-09T14:08:00.024+12:00</published><updated>2011-04-12T23:46:48.971+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='findbugs'/><category scheme='http://www.blogger.com/atom/ns#' term='teamcity'/><category scheme='http://www.blogger.com/atom/ns#' term='maven'/><category scheme='http://www.blogger.com/atom/ns#' term='continuous integration'/><title type='text'>Маленький Билд и его друзья</title><content type='html'>&lt;p&gt;Помните фильм "Пятый элемент"? Там была сцена, когда Зорг опрокидывает стакан на пол и роботы тут же начинают уборку помещения. Всего лишь одно маленькое действие привело в жизнь десяток машин, которые сразу же начали подметать и мыть полы, а в конце еще и налили воды хозяину.&lt;/p&gt;&lt;p&gt;Что-то похожее происходит в коллективе с налаженным build процессом, когда разработчик коммитит изменения в систему контроля версий.&lt;/p&gt;&lt;span class="fullpost"&gt;&lt;div&gt;&lt;p&gt;О пользе налаженного процесса билда известно много. Но "путь на production" не прост. После того как код написан и перед тем как он будет запущен на production'е было бы неплохо сделать следующие вещи:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;откомпилировать код если вы используете компилируемый язык (удивил, да?);&lt;/li&gt;&lt;li&gt;прогнать модульные и интеграционные тесты если они у вас есть;&lt;/li&gt;&lt;li&gt;выполнить статический анализ кода и/или другие проверки которые позволяет выполнить ваша платформа и которые имеют смысл для вашей команды (code style check, code coverage, цикломатическая сложность, dependency matrix и т.д.);&lt;/li&gt;&lt;li&gt;собрать артефакт приложения (желательно в виде одного файла), содержащий в себе весь код и ресурсы необходимые для запуска приложения на целевой платформе;&lt;/li&gt;&lt;li&gt;выполнить приемочное тестирование, если оно у вас есть;&lt;/li&gt;&lt;li&gt;опубликовать артефакт в репозитории (предположительно локальном), чтобы другие члены комманды могли им воспользоватся в своих целях (особенно актуально, если вы разрабатываете билиотеку, а не приложение);&lt;/li&gt;&lt;li&gt;в случае если вы разрабатываете приложение а не библиотеку, произвести deploy в окружение staging тестирования чтобы команда и менеджеры могли оценить текущее состояние проекта.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Этот список действий не догма и варьируется от потребностей проекта. Я же расскажу что и как делаем мы. Сразу отмечу что у нас в проекте используется две платформы: PHP и Java. Я преимущественно буду описывать как мы собираем Java проекты, так как за счет поддержки инструментария билд процесс там получается более целостный и вразумительный. Я думаю, этот опыт будет полезен читателям.&lt;/p&gt;&lt;p&gt;Итак код закоммичен в систему контроля версий. С этого момента начинается его маленькое путешествие на production. Но для начала давайте познакомимся с главными героями. Для того чтобы успешно проводить автоматизированною сборку проекта необходим определенный инструментарий.&lt;/p&gt;&lt;p&gt;&lt;span class="Apple-style-span"  style="font-size:x-large;"&gt;В главных ролях&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;Система контроля версий (VCS)&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Какую VCS систему использовать решать вам. Это может быть старый проверенный временем &lt;a href="http://subversion.apache.org/"&gt;subversion&lt;/a&gt;, или модно-распределенный &lt;a href="http://git-scm.com/"&gt;git&lt;/a&gt;/&lt;a href="http://mercurial.selenic.com/"&gt;mercurial&lt;/a&gt;. Но она должна быть. Если на вашем календаре уже 2011 год и вы все еще не пользуетесь какой-либо системой контроля версий, то я настоятельно советую вам переоценить принципы согласно котрым вы принимаете решения.&lt;/p&gt;&lt;p&gt;В контексте обсуждения процесса сборки выбор системы контроля версий настолько неважен, что я даже не буду говорить какую используем мы :)&lt;/p&gt;
&lt;p&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;Система сборки проектов (build tool)&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Основная задача системы сборки состоит в автоматизации задач связанных с созданием релиза из исходных кодов. Среди программистов работающих с интепретируемыми языками распространено мнение что исходный код и есть релиз. Связано это судя по всему с тем, что в таких языках нет выделенной фазы компиляции. Я считаю, что даже в этом случае разделение между исходниками и релизами нужно, потому что вне зависимости от того на каком языке программирования написано приложение, релиз должен обладать следующими свойствами, которыми не обладают исходные коды:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;любой релиз в отличии от исходных кодов должен работать. Подтверждатся это должно каким-либо видом тестирования (хотя бы ручным). Исходные коды же могут находится некоторое время в нерабочем состоянии (например, во время рефакторинга);&lt;/li&gt;&lt;li&gt;релиз может содержать third party библиотеки и программное обеспечение, которое разрабатывается и поддерживается третьими лицами. Хранить все это в системе контроля версий может быть не самым удобным решением;&lt;/li&gt;&lt;li&gt;платформа на которой работает приложение может иметь ограничения на формат релиза. Например, может быть строго определен формат архива с приложением (например, &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;rpm&lt;/span&gt; или &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;deb&lt;/span&gt;). Хранить исходные коды в VCS в том же формате может быть очень неудобно c точки зрения разработки.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Выражаясь математически: &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;релиз = ƒ(исходники)&lt;/span&gt;.&lt;/p&gt;&lt;p&gt;Для сборки мы используем &lt;a href="http://maven.apache.org/"&gt;Apache Maven&lt;/a&gt;, -- это довольно зрелая и продвинутая система сборки, которая впрочем не так проста в изучении как ее более "легковесные" братья вроде &lt;a href="http://ant.apache.org/"&gt;ant&lt;/a&gt; и &lt;a href="http://gradle.org/"&gt;gradle&lt;/a&gt; (хотя ant — это скорее бабушка, а gradle внучатый племянник). Gradle в последнее время получил довольно много положительных отзывов и продолжает набирать популярность, поэтому вам определенно стоит посмотреть на него, если вы определяетесь с вопросом выбора системы сборки Java-проекта.&lt;/p&gt;&lt;p&gt;Для того чтобы использовать maven более эффективно, у нас есть определенная экосистема для его поддержки.&lt;/p&gt;&lt;p&gt;Во-первых, это репозиторий артефактов о котором я расскажу ниже.&lt;/p&gt;&lt;p&gt;Во-вторых, у нас есть общийэ POM-дескриптор для всех проектов компании, который определяет настройки компилятора, сразу делает доступными библиотеки повсеместно используемые в нашей компании (TestNG, hamcrest, logback и т.д.), а также настраивает плагины для статического анализа кода и логгирования и т.д.&lt;/p&gt;&lt;p&gt;В третьих, у нас есть несколько прототипов проектов (&lt;a href="http://maven.apache.org/guides/introduction/introduction-to-archetypes.html"&gt;archetype&lt;/a&gt; в терминологии maven), которые позволяют одной командой из консоли создать новый проект. Эдакий hello world с уже настроенным логированием, &lt;a href="http://docs.codehaus.org/display/JETTY/Maven+Jetty+Plugin"&gt;Jetty для тестирования&lt;/a&gt;, &lt;a href="http://activemq.apache.org/"&gt;ActiveMQ&lt;/a&gt; и &lt;a href="http://www.springsource.org/spring-integration"&gt;Spring Integration&lt;/a&gt; для обработки сообщений, &lt;a href="http://www.springsource.org/"&gt;Spring&lt;/a&gt; в качестве web-framework'а и много еще чем. Все это очень сильно упрощает старт, особенно людям не знакомым с премудростями настройки всего этого "зоопарка".&lt;/p&gt;&lt;p&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;Репозиторий артефактов&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Еще один вопрос связанный с системой билда — куда ложить его результат? После того как мы собрали приложение и протестировали его, нам надо опубликовать релиз. Необходимо общее централизованное место где бы хранились все артефакты, чтобы любой разработчик знал где искать последний релиз продукта. Позже вы можете достать его и использовать для deploy'я на production или в окружение staging тестирования. Если вы разрабатываете библиотеку то релиз нужен другим членам команды, для того чтобы использовать ее в своем приложении.&lt;/p&gt;&lt;p&gt;В простейшем случае роль репозитория может играть web-сервер. Его настройка для этих задач не займет у вас много времени. Либо это может быть FTP-сервер или сетевой диск доступный всем разработчикам. Так же эту роль может играть continious integration сервер, речь о котором пойдет ниже.&lt;/p&gt;&lt;p&gt;В более сложных случаях удачным решением может быть специализирванное ПО. Особенно это актуально если вы используете Maven, который специфицирует формат репозиториев и протокол работы с ними (поверх HTTP).&lt;/p&gt;&lt;p&gt;Мы используем &lt;a href="http://www.jfrog.com/products.php"&gt;Artifactory&lt;/a&gt; в качестве репозитория, который выполняет несколько ролей в нашей экосистеме:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;хранит артефакты делая их (и их исходники) доступными для использования любым программистом в любое время;&lt;/li&gt;&lt;li&gt;кеширует third party библиотеки используемые программистами. Если вы начинаете новый проект и хотите использовать какую-то библиотеку, с высокой долей вероятности она уже есть в локальном репозитории и вы получите ее моментально не дожидаясь загрузки из интернета.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;Continuous Integration сервер&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Не смотря на грозное название, CI-сервера по своей сути — это триггеры билд процесса которые предоставляют разработчикам удобный способ контроллировать процесс и результат сборок. Они делают несколько вещей:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;позволяют конфигурировать политику запуска сборки (при коммите в VCS, через заданные интевалы времени, после успешной сборки зависимого проекта, вручную, и т.д.);&lt;/li&gt;&lt;li&gt;предоставляют возможность следить за процессом сборки публикуя ее логи через web-интерфейс;&lt;/li&gt;&lt;li&gt;предоставляют отчеты по результатам билда (проваленные тесты, предупреждения статического анализатора и т.д.);&lt;/li&gt;&lt;li&gt;строят тренды на основании истории сборок (количество тестов, время затраченное на исправление билда, процент успешных сборок и т.д.);&lt;/li&gt;&lt;li&gt;могут успешно играть роль репозитория артефактов.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Строго говоря использование CI-сервера не обязательно. Более того, на начальных этапах я бы рекомендовал делать сборки на машинах разработчиков. Это позволит сэкономить вам время в процессе допиливания build-процесса, которое на начальных стадиях неизбежно.&lt;/p&gt;&lt;p&gt;Мы до сих пор не используем CI-сервер для сборки билиотек. Библиотеки собираются и релизятся самим программистами. Несмотря на то что этот подход имеет ряд недостатков, он существенно проще.&lt;/p&gt;&lt;p&gt;В качестве CI-сервера мы используем &lt;a href="http://www.jetbrains.com/teamcity/"&gt;TeamCity&lt;/a&gt;, бесплатной версией которой мы полностью довольны. Одна из привлекательных фич этого продукта состоит в том что он предоставляет неплохую статистику по билдам, включая success rate сборок, количество тестов и т.д.&lt;/p&gt;&lt;p&gt;&lt;a href="http://1.bp.blogspot.com/-1185LetB6v8/TaQ1dku0R4I/AAAAAAAAAVY/eURo8mBVjAA/s1600/Screen%2Bshot%2B2011-04-12%2Bat%2B22.11.35.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://1.bp.blogspot.com/-1185LetB6v8/TaQ1dku0R4I/AAAAAAAAAVY/eURo8mBVjAA/s400/Screen%2Bshot%2B2011-04-12%2Bat%2B22.11.35.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5594655419293517698" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 253px; " /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;span class="Apple-style-span"  style="font-size:x-large;"&gt;Поезд отправляется&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Итак  как это все происходит на практике.&lt;/p&gt;&lt;p&gt;За системой контроля версий следит continuous integration сервер. Как только он замечает какие либо изменения, он делает checkout свежей версии и инициирует процесс сборки проекта.&lt;/p&gt;&lt;p&gt;TeamCity позволяет очень детально настроить с какими параметрами будет запущен build. Вы можете предопределить значения системных переменных нужных вашему процессу билда. Эти переменные могут использоваться например для того чтобы специфицировать на какой БД будет выполнятся тестирование.&lt;/p&gt;&lt;p&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;img src="http://4.bp.blogspot.com/-g8o1AQoZLF8/TaQ1C9LKCfI/AAAAAAAAAVI/9jmJCyRkn90/s400/Screen%2Bshot%2B2011-04-12%2Bat%2B22.01.50.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5594654961998367218" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 248px; " /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Затем в работу вступает Maven. Он компилирует исходники, запускает модульные тесты. Если они провалились, то генерирует отчеты по проваленным тестам, которые будут затем показаны на персональной странице билда, а сам билд завершается и считается проваленным:&lt;/p&gt;&lt;p&gt;&lt;a href="http://3.bp.blogspot.com/-DqgvmGFTel0/TaQ1S-8ZJMI/AAAAAAAAAVQ/y0tvo2whMJ8/s1600/Screen%2Bshot%2B2011-04-12%2Bat%2B22.03.36.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://3.bp.blogspot.com/-DqgvmGFTel0/TaQ1S-8ZJMI/AAAAAAAAAVQ/y0tvo2whMJ8/s400/Screen%2Bshot%2B2011-04-12%2Bat%2B22.03.36.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5594655237351220418" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 253px; " /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Если тесты проходят, то "состав движется дальше". Следующим шагом является сборка артефактов. В терминах continious integration &lt;i&gt;артефакт&lt;/i&gt; это любой результат процесса сборки, главная черта которого — воспроизводимость. Для нас это означает что артефакт является процессом работы машины, а не ручных действий человека.&lt;/p&gt;&lt;p&gt;Обратите внимание, что в смежных областях знаний, например в configuration management, термин "артефакт" имеет немного отличные значения.&lt;/p&gt;&lt;p&gt;Воспроизводимость артефакта — очень важное его свойство. Оно позволяет в любой момент времени из любого среза VCS попытатся собрать приложение и посмотреть что из этого получится.&lt;/p&gt;&lt;p&gt;Воспроизводимость артефакта служит хорошей поддержкой для так популярных в наше время итеративных методов разработки, смысл которых состоит в идее "&lt;i&gt;давайте начнем стрелять, а потом будем корректировать огонь&lt;/i&gt;". Когда патроны дешевле чем время, это хороший подход. Налаженный процесс автоматического производства артефактов в этом контексте можно сравнить с автоматическим станком по производству патронов. Чем лучше он у вас отточен, тем дешевле ваши патроны, и тем быстрее вы можете стрелять и получать фидбек критически важный для следующей итерации.&lt;/p&gt;&lt;p&gt;После того как артефакт собран Maven принимается за интеграционное и приемочное тестирование. Здесь необходимо отметить зачем разделение между модульными, интеграционными и приемочными тестами.&lt;/p&gt;&lt;p&gt;Модульные тесты тестируют классы в изоляции от других частей системы, а также в изоляции от внешних систем, таких как базы данных и веб-сервисы. Их задача состоит в том чтобы давать программисту быстрый фидбек относительно того, работает система или нет безотносительно того верно ли она интегрируется с внешними источниками данных. Фактически, любой программист должен иметь возможность сделать checkout исходников проекта, запустить модульные тесты на абсолютно не подготовленной машине (например, без базы данных) и тесты должны выполнится. Единственная причина почему они могут не выполнится, это если в коде приложения допущена ошибка.&lt;/p&gt;&lt;p&gt;Таким образом, у модульных тестов не должно быть зависимостей, отличных от тех которые тест может удовлетворить сам. По личному опыту могу сказать: тех кто не соблюдаете это правило ждет не очень приятное будущее.&lt;/p&gt;&lt;p&gt;В сухом остатке. Модульные тесты должны:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;выполнятся быстро. Потолок 15-20 секунд, затем их просто перестают использовать. Здесь нужно обратить внимание на то, что модульные тесты должны выполнятся именно на стороне программиста и как можно чаще. И конечно же обязательно перед коммитом в систему контроля версий. На CI-сервере они выполнятся лишь для полноты тестирования;&lt;/li&gt;&lt;li&gt;быть изолированными от внешнего окружения и выполнятся даже на машине без сети.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;В отличии от модульных, интеграционные тесты проверяют как приложение дружит с внешними системами, и как следствие, обладают следующим рядом особенностей:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ol&gt;&lt;li&gt;могут требовать довольно сложное окружение для своего выполнения (конкретную БД, возможно даже конкретной версии);&lt;/li&gt;&lt;li&gt;могут быть довольно медленными, так как включают в себя взаимодействие по сети, зачастую с системами производительность которых находятся под контролем третьих лиц;&lt;/li&gt;&lt;li&gt;являются гораздо более хрупкими чем модульные, потому что опираются на заранее установленное окружение, а также на программное обеспечение которое скорее всего меняется без оглядки на ваше конкретное приложение.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Тем не менее тестировать все равно надо, поэтому такие тесты выделяются в отдульную группу и запускаются на CI-сервере. Настроить и поддерживать в актуальном состоянии сложное окружение проще один раз на сервере, чем десять раз на машинах разработчиков. Разработчик может запустить интеграционные тесты у себя, но для этого он должен настроить хотя бы часть окружения на своей машине, что может быть довольно непросто. Частично эти проблему можно решить при помощи виртуализации и таких инструментов как &lt;a href="http://www.puppetlabs.com/"&gt;puppet&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Обычно интеграционные тесты на стороне разработчика запускаются только в случае если менялся интеграционный код системы и как правило только тесты на изменившуюся часть системы. С точки зрения maven (а именно, &lt;a href="http://maven.apache.org/plugins/maven-surefire-plugin/"&gt;maven-surefire-plugin)&lt;/a&gt; интеграционные тесты отличаются от модульных только постфиксом (&lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;*Test&lt;/span&gt; у модульных, &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;*IT&lt;/span&gt; у интеграционных). После того как тесту задан соответствующий постфикс, он автоматически начинает запускатся на нужной фазе сборки проекта.&lt;/p&gt;&lt;p&gt;Если с интеграционными тестами все хорошо, то запускаются приемочные. Приемочные тесты проверяют систему по типу черного ящика. Если вы разрабатываете web-приложение, то оно запускается на web-сервере и тест под видом обыкновенного пользователя начинает ходить по нему и проверять его работоспособность. Пожалуй, это один из самых сложных видов тестирования. В нем довольно легко наделать ошибок, которые могут существенно увеличить стоимость поддержки тестовой инфраструктуры. Это в конце концов делает процесс тестирования менее эффективным. Впрочем, тема приемочного тестирования выходит далеко за рамки моего поста, поэтому я не буду заострять на этом сейчас внимание. Людям искушенным очень советую прочитать книгу Джеза Хамбла и Девида Ферли &lt;a href="http://continuousdelivery.com/"&gt;Continuous Delivery&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Ну а мы отправляемся дальше. Следуюшая остановка — статический анализ кода. Мы используем &lt;a href="http://mojo.codehaus.org/findbugs-maven-plugin/"&gt;findbugs&lt;/a&gt; для анализа кода. Статические анализаторы действительно находят ошибки и потенциальные уязвимости, так почему бы не делать это при каждом изменении наших исходных файлов.&lt;/p&gt;&lt;p&gt;&lt;a href="http://2.bp.blogspot.com/-R0MgUpBEAkM/TaQ0j4RMZzI/AAAAAAAAAVA/mL7RLulRC4U/s1600/Screen%2Bshot%2B2011-04-12%2Bat%2B22.15.38.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img src="http://2.bp.blogspot.com/-R0MgUpBEAkM/TaQ0j4RMZzI/AAAAAAAAAVA/mL7RLulRC4U/s400/Screen%2Bshot%2B2011-04-12%2Bat%2B22.15.38.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5594654428105566002" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 253px; " /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Можно указать TeamCity чтобы при превышении опредленного порога по количеству найденных проблемных ситуаций, билд считался проваленным.&lt;/p&gt;&lt;p&gt;Но, данный шаг можно порекомендовать не всем. Во-первых, для этого требуется существенная поддержка инструментов. Например, для php нет ничего подобного. Во-вторых, получаемая от статического анализатора информация должна оцениваться прагматически. Помните что анализатор показывает риски с формальной точки зрения. У каждого риска есть &lt;i&gt;вероятность материализации&lt;/i&gt; и &lt;i&gt;стоимость исправления&lt;/i&gt;. Вероятность материализации — это вероятность с которой риск превратится в проблему (то есть вы сможете наблюдать его воочию). Стоимость исправления — это сколько ресурсов (например, в виде человеко часов) нам надо будет потратить если риск все же материализуется в проблему. Перемножив эти два числа мы получим &lt;i&gt;математическое ожидание проблемы&lt;/i&gt;. Может оказаться так, что математическое ожидание проблемы гораздо меньше чем стоимость внесения изменений в код здесь и сейчас. Некоторые проблемы дешевле исправлять пост фактум.&lt;/p&gt;&lt;p&gt;После того как статический анализ кода законечен, закончен и процесс непосредственно сборки. Теперь нам осталось опубликовать артефакт в общедоступный репозиторий и можно рапортовать об успешности билда.&lt;/p&gt;&lt;p&gt;Деплой у нас происходит в два этапа. Первый этап это деплой в локальный репозиторий артефактов. Второй этап включает в себя автоматическую доставку приложения на production сервера. Обратите внимание, &lt;i&gt;доставку, но не deploy&lt;/i&gt;. Deploy осуществляется только по инициативе и под контролем разработчика. Таким образом, если разаботчик работает над проектом &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;search-service&lt;/span&gt; версии &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;1.0&lt;/span&gt; и на TeamCity успешно выполнился билд под номером &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;134&lt;/span&gt;, то на production кластере в папке проекта автоматически появляется файл &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;search-service-1.0.134.war&lt;/span&gt;, который содержит в себе все необходимое для работы приложения и готов к развертыванию по команде разработчика. Это позволяет свести к минимуму участие человека в процессе билда. Человек привлекается только там где его внимание и возможность принимать решения не может заменить машина.&lt;/p&gt;&lt;p&gt;Вот пожалуй и все. Некоторые моменты остались за кадром. Например, как происходит запуск (deploy) приложения на production серверах. Но это тема совсем другого разговора.&lt;/p&gt;&lt;p&gt;Буду рад услышать отзывы и success stories читателей.&lt;/p&gt;&lt;/div&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-3622657711977585627?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/3622657711977585627/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2011/04/blog-post.html#comment-form' title='Комментарии: 3'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/3622657711977585627'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/3622657711977585627'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2011/04/blog-post.html' title='Маленький Билд и его друзья'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-1185LetB6v8/TaQ1dku0R4I/AAAAAAAAAVY/eURo8mBVjAA/s72-c/Screen%2Bshot%2B2011-04-12%2Bat%2B22.11.35.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-4204231538886278297</id><published>2010-11-14T14:01:00.015+11:00</published><updated>2010-11-17T23:52:24.750+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='atom'/><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='synology'/><category scheme='http://www.blogger.com/atom/ns#' term='nas'/><category scheme='http://www.blogger.com/atom/ns#' term='supermicro'/><category scheme='http://www.blogger.com/atom/ns#' term='chenbro'/><category scheme='http://www.blogger.com/atom/ns#' term='qnap'/><title type='text'>Как я собирал NAS</title><content type='html'>&lt;div style="text-align: left;"&gt;Коллеги давно просили меня описать процесс сборки и настройки своего NAS-сервера. Этим постом я искупаю свою вину. К тому же тема действительно актуальная и, мне кажется, многим будет интересно с какими проблемами я столкнулся, какое железо и софт использовал.&lt;/div&gt;&lt;p&gt;NAS у меня исполняет несколько обязанностей:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;файловое хранилище "жирного" контента (фильмы, etc);&lt;/li&gt;&lt;li&gt;сервер для Apple TimeMachine (бекапы);&lt;/li&gt;&lt;li&gt;качает и раздает торренты;&lt;/li&gt;&lt;li&gt;"самопальный" мониторинг сетевой активности с использованием &lt;a href="http://www.mrtg.org/rrdtool/"&gt;rrdtool&lt;/a&gt;;&lt;/li&gt;&lt;li&gt;локальный git-репозиторий.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Мой опыт использования NAS начался с &lt;a href="http://www.qnap.com/pro_detail_feature.asp?p_id=92"&gt;QNAP TS-109 PRO II&lt;/a&gt;.&lt;/p&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 336px; height: 406px;" src="http://www.zdnet.de/i/news/200702/qnap%20ts-109.jpg" border="0" alt="" /&gt;&lt;p&gt;Довольно удачная однодисковая железка, которая прослужила мне верой и правдой более года. У нее несколько минусов:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;собственно всего один диск;&lt;/li&gt;&lt;li&gt;нестандартный встроенный linux;&lt;/li&gt;&lt;li&gt;ARM-based процессор.&lt;/li&gt;&lt;/ul&gt;Один диск меня не устраивал по причине отсутствия резервирования. Второй и третий пункт меня напрягали, так как сам я довольно уверенно пользуюсь linux и embedded версия ОС предлагаемая QNAP вкупе с ARM процессором не устраивала меня по ряду причин: сборки не для всех пакетов есть под ARM, а те которые есть, редко обновляются и иногда содержат странные баги (у меня появился довольно изрядный опыт "войны" с rtorrent'ом). Конечно, можно было бы собирать софт из исходников, но это осложнено следующими факторами:&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;установка компилятора и development библиотек тоже довольно нетривиальна (видимо проблема курицы и яйца);&lt;/li&gt;&lt;li&gt;будучи программистом с серьезным отношением к тестированию я отношусь к той категории людей которые считают, что если ты сам компилируешь пакет, ты должен его сам и тестировать. А мне этого совсем не хотелось.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Таким, довольно эволюционным путем, примерно через полтора года я пришел к мысли что пора менять storage. На этот момент на рынке было несколько device'ов, которые привлекли мое внимание:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.qnap.com/pro_detail_feature.asp?p_id=122"&gt;QNAP TS-219P&lt;/a&gt;;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.synology.com/enu/products/DS210+/index.php"&gt;Synology DiskStation DS210+&lt;/a&gt;;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.qnap.com/pro_detail_feature.asp?p_id=143"&gt;QNAP TS-259 Pro&lt;/a&gt;;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.dont.ru/market.id13184.html"&gt;Acer easyStore H340&lt;/a&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Моя вера в x86 на этом поприще росла с каждой новостью связанной с Intel Atom. Поэтому два первых девайса, опять таки построенные на базе ARM процессора, отсеялись сами собой. Два последних представляют уже больший интерес, но анализируя их спецификации я пришел к мысли что я бы мог сам собрать более подходящую мне железку.&lt;/p&gt;&lt;h3&gt;Hardware&lt;/h3&gt;&lt;p&gt;Я начал подискивать подходящий miniITX корпус. Мягко говоря это не просто, особенно во Владивостоке. Большинство корпусов в этом форм факторе нацелены на создание nettop'ов: имеют максимум 2 корзины под HDD, кучу лишних фентифлюшек вроде headphones output на фасаде и т.д. Мне же хотелось собрать железку не имеющую ничего лишнего и при этом "на вырост", чтобы через пару лет не пришлось менять снова.&lt;/p&gt;&lt;p&gt;В конце концов я нашел корпус, который меня полностью удовлетворил — &lt;a href="http://usa.chenbro.com/corporatesite/products_detail.php?sku=79"&gt;Chenbro ES3469&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;a href="http://usa.chenbro.com/assets/2007/10/04/product6213780907.jpg"&gt;&lt;img src="http://usa.chenbro.com/assets/2007/10/04/product6213780907.jpg" border="0" alt="" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 600px; height: 840px; " /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Этот корпус специально создавался под NAS, поэтому обладает довольно характерными особенностями:&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;четыре корзины для 3.5" HDD с поддержкой горячей замены и индивидуальной индикацией питания и data activity;&lt;/li&gt;&lt;li&gt;один внутренний слот для 2.5" HDD (очень удобно так как все четыре жестких можно отдать под хранение данных, а ОС поставить на отдельный пятый жесткий);&lt;/li&gt;&lt;li&gt;внешний блок питания, что позволяет сэкономить место внутри и избавится от одного лишнего вентилятора (внешний БП без активного охлаждения);&lt;/li&gt;&lt;li&gt;довольно "богатая" индикация на передней панели.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Сам корпус сделан довольно качественно, внутри разделен на два отсека. В одном отсеке находятся жесткие диски с двумя вентиляторами работающими на выдув, во втором материнская плата и вся коммутация.&lt;/p&gt;&lt;p&gt;Немного огорчило меня то, что одна из корзин сразу не заработала. Сначала я грешил на backplane, но слава богу дело было в неисправном SATA кабеле, который легко поддается замене. Замена правда осуществляется не самым простым образом, — для того чтобы добраться до отсека с корзинами и до backplane'ов, надо буквально говоря разобрать весь корпус, что не очень удобно. Что уж тут поделать, издержки форм фактора.&lt;/p&gt;&lt;p&gt;Следующим шагом стал выбор материнской платы.  С ней тоже все оказалось не просто. Надо было найти Atom-based miniITX материнскую плату с полностью &lt;i&gt;пассивным охлаждением&lt;/i&gt; и минимум &lt;i&gt;5 SATA портами&lt;/i&gt;! Немного странные требования для форм фактора miniTX, не находите? Такая все же нашлась — &lt;a href="http://www.supermicro.com/products/motherboard/ATOM/ICH9/X7SPA.cfm?typ=H"&gt;Supermicro X7SPA-H&lt;/a&gt;:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;двухядерник Intel Atom D510 1.6Ghz с поддержкой HT;&lt;/li&gt;&lt;li&gt;&lt;i&gt;6 SATA портов&lt;/i&gt;;&lt;/li&gt;&lt;li&gt;два SO-DIMM DDR2 667MHz модуля, максимум 4Gb.&lt;/li&gt;&lt;li&gt;две гигабитные сетевые карты;&lt;/li&gt;&lt;li&gt;полностью пассивное охлаждение;&lt;/li&gt;&lt;li&gt;довольно простая видеокарта Intel GMA3150, что в моем случае является плюсом, будет меньше кушать и греться;&lt;/li&gt;&lt;li&gt;поддержка power on after failure, что для девайсов такого класса очень полезно.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Вобщем, эта материнская плата подошла в моем случае просто идеально. Причем, материнская плата и корпус, что называется, соответствуют друг другу. Например, у обоих есть поддержка индикации power fail и chassis intrusion.&lt;/p&gt;&lt;p&gt;Правда, тут произошел казус. Сразу материнка не завелась: питание есть, вентилятор на корпусе вращается, а power LED не горит и стандартного писка о прохождении POST check'а нету. Отключил всю периферию, проверил что нигде ничего не замыкает, проверил память, — один черт, не заводится. Сбросил CMOS, — не заводится. Причем, если достать RAM, то матерится о ее отсутствии на всю квартиру. В конце концов, на третий день, когда вера в то что материнка жива окончательно иссякла, она при самых странных обстоятельствах (во время очередной операции "выткни-воткни оперативку") завелась. То ли чудо, то ли фокус... То ли я два или три десятка раз подряд неверно вставлял оперативную память.&lt;/p&gt;&lt;p&gt;Дальше стало проще. В качестве жестких я выбрал Western Digital линейки &lt;a href="http://www.wdc.com/ru/products/Products.asp?DriveID=866"&gt;Caviar Green&lt;/a&gt;, так как они хорошо себя зарекомендовали еще со времен моего первого NAS'а — тихие и холодные. Сейчас у меня стоят два: WD10EADS и WD10EARS. Насколько я понимаю, отличаются они только объемом кеша 32/64Mb.&lt;/p&gt;&lt;p&gt;&lt;a href="http://3.bp.blogspot.com/_olxzclEPtBk/TN9pTp8Xc1I/AAAAAAAAATI/4oF0scY8GSA/s1600/DSC02620.JPG"&gt;&lt;img src="http://3.bp.blogspot.com/_olxzclEPtBk/TN9pTp8Xc1I/AAAAAAAAATI/4oF0scY8GSA/s320/DSC02620.JPG" border="0" alt="" id="BLOGGER_PHOTO_ID_5539261853086479186" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 320px; height: 240px; " /&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Так как я старался собрать максимально тихий device, один из вентиляторов в отсеке с жесткими я отключил, а на второй поставил zalman'овский регулятор скорости вращения. Эмпирическим путем была подобрана скорость, которая дает тихую работу. Температура при этом держится в районе 40-42 градусов, что меня вполне устраивает.&lt;/p&gt;&lt;p&gt;&lt;a href="http://4.bp.blogspot.com/_olxzclEPtBk/TOPD28y3NOI/AAAAAAAAATQ/818CiTBupWA/s1600/hdd-temp.png"&gt;&lt;img src="http://4.bp.blogspot.com/_olxzclEPtBk/TOPD28y3NOI/AAAAAAAAATQ/818CiTBupWA/s320/hdd-temp.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5540487315395654882" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 320px; height: 130px; " /&gt;&lt;/a&gt;&lt;/p&gt;&lt;h3&gt;Software&lt;/h3&gt;&lt;p&gt;Теперь немного об используемом программном обеспечении. Я "на короткой ноге" с Ubuntu, поэтому я выбрал именно этот дистрибутив. Так же, наслушавшись страшных историй о том как восстанавливают данные с RAID-зеркал, я сразу принял решение использовать файловую систему с поддержкой избыточности вместо аппаратного RAID'а.&lt;/p&gt;&lt;p&gt;Собственно, сейчас таких файловых системы две: BTRFS и ZFS. Первая находится на стадии беты и не предназначена для production использования. Вторая довольно стабильная и широко используемая, но в linux реализована в рамках проекта FUSE из-за лицензионных ограничений (изначально ее разработала Sun для ОС Solaris). По совокупности факторов я решил использовать ZFS.&lt;/p&gt;&lt;p&gt;ZFS вещь довольно интересная. Она позволяет собрать из списка блочных девайсов виртуальный том с заданными настройка избыточности. У нее есть ряд преимуществ перед аппаратным RAID'ом:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;в зеркале могут участвовать абсолютно разные жесткие (разного размера);&lt;/li&gt;&lt;li&gt;выход из строя блочного девайса не запрещает клиентам читать и &lt;i&gt;писать&lt;/i&gt; на том, до тех пор пока есть хотя бы одна живая реплика;&lt;/li&gt;&lt;li&gt;rebuild тома (scrub в терминах ZFS) тоже не блокирует клиентов, они по прежнему могут читать и &lt;i&gt;писать&lt;/i&gt;;&lt;/li&gt;&lt;li&gt;настройки избыточности можно задавать отдельно для директорий. То есть вы можете хранить все в двух копиях, а особо важные для вас папки в трех (для этого том должен состоять как минимум из трех физических дисков).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Все фичи кроме последней я вынуждено "протестировал" в ходе эксплуатации. Так что, да, это действительно работает. Для проверки последней у меня просто не было мотивации, у меня нет &lt;i&gt;настолько&lt;/i&gt; важных данных.&lt;/p&gt;&lt;p&gt;Создание ZFS тома делается очень просто:&lt;/p&gt;&lt;p&gt;&lt;span class="Apple-style-span"   style=" line-height: 12px; color: rgb(51, 51, 51);  font-family:Ubuntu, 'Ubuntu Beta', 'Bitstream Vera Sans', 'DejaVu Sans', Tahoma, sans-serif;font-size:12px;"&gt;&lt;/span&gt;&lt;/p&gt;&lt;pre style="margin-top: 0px; margin-right: 0px; margin-bottom: 8px; margin-left: 0px; padding-top: 4pt; padding-right: 4pt; padding-bottom: 4pt; padding-left: 4pt; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-style: initial; border-color: initial; font-weight: inherit; font-style: inherit; font-size: 12px; line-height: 16px; font-family: UbuntuMono, courier, monospace; text-align: left; vertical-align: baseline; background-color: rgb(243, 243, 243); border-top-style: dashed; border-right-style: dashed; border-bottom-style: dashed; border-left-style: dashed; border-top-color: rgb(193, 180, 150); border-right-color: rgb(193, 180, 150); border-bottom-color: rgb(193, 180, 150); border-left-color: rgb(193, 180, 150); white-space: pre-wrap; word-wrap: break-word; "&gt;zpool create tank raidz1 /dev/sda /dev/sdb&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;После создания, том будет автоматически примонтирован в /tank. Теперь можно заливать на него информацию. Просмотр статуса по виртуальным томам делается следующими командами:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style="margin-top: 0px; margin-right: 0px; margin-bottom: 8px; margin-left: 0px; padding-top: 4pt; padding-right: 4pt; padding-bottom: 4pt; padding-left: 4pt; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-style: initial; border-color: initial; font-weight: inherit; font-style: inherit; font-size: 12px; line-height: 16px; font-family: UbuntuMono, courier, monospace; text-align: left; vertical-align: baseline; background-color: rgb(243, 243, 243); border-top-style: dashed; border-right-style: dashed; border-bottom-style: dashed; border-left-style: dashed; border-top-color: rgb(193, 180, 150); border-right-color: rgb(193, 180, 150); border-bottom-color: rgb(193, 180, 150); border-left-color: rgb(193, 180, 150); white-space: pre-wrap; word-wrap: break-word; "&gt;zpool status&lt;/pre&gt;&lt;pre style="margin-top: 0px; margin-right: 0px; margin-bottom: 8px; margin-left: 0px; padding-top: 4pt; padding-right: 4pt; padding-bottom: 4pt; padding-left: 4pt; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-style: initial; border-color: initial; font-weight: inherit; font-style: inherit; font-size: 12px; line-height: 16px; font-family: UbuntuMono, courier, monospace; text-align: left; vertical-align: baseline; background-color: rgb(243, 243, 243); border-top-style: dashed; border-right-style: dashed; border-bottom-style: dashed; border-left-style: dashed; border-top-color: rgb(193, 180, 150); border-right-color: rgb(193, 180, 150); border-bottom-color: rgb(193, 180, 150); border-left-color: rgb(193, 180, 150); white-space: pre-wrap; word-wrap: break-word; "&gt;zpool iostat -v&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Перебалансировка и починка тома осуществляется при помощи команды:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;pre style="margin-top: 0px; margin-right: 0px; margin-bottom: 8px; margin-left: 0px; padding-top: 4pt; padding-right: 4pt; padding-bottom: 4pt; padding-left: 4pt; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-style: initial; border-color: initial; font-weight: inherit; font-style: inherit; font-size: 12px; line-height: 16px; font-family: UbuntuMono, courier, monospace; text-align: left; vertical-align: baseline; background-color: rgb(243, 243, 243); border-top-style: dashed; border-right-style: dashed; border-bottom-style: dashed; border-left-style: dashed; border-top-color: rgb(193, 180, 150); border-right-color: rgb(193, 180, 150); border-bottom-color: rgb(193, 180, 150); border-left-color: rgb(193, 180, 150); white-space: pre-wrap; word-wrap: break-word; "&gt;zpool scrub tank&lt;/pre&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;В интернете есть множество информации о ZFS. Если хотите использовать ZFS, советую обратится обратится к &lt;a href="http://docs.sun.com/app/docs/doc/819-5461"&gt;официальному мануалу&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Надо понимать что такая гибкость не дается бесплатно. ZFS прожорлив по памяти, поэтому советую оснастить машину гигабайтом, а лучше двумя гигабайтами оперативной памяти. Я так и сделал, благо она дешевая.&lt;/p&gt;&lt;p&gt;С ZFS у меня произошел только один негатвиный инцедент. Во время начальной заливки данных на том, oomkiller'у не понравилось что драйвер ZFS ест много памяти, и он его убил :). Это издержки того что ZFS работает в user space. После reboot'а все стало нормально, ничего чинить не пришлось. Больше такого у меня не повторялось.&lt;/p&gt;&lt;p&gt;Так же, я использую NAS как сервер для Apple Time Machine, поэтому мне надо было настроить его так, чтобы мой mac mini воспринимал его как AFP сервер. В интернете есть полно мануалов, &lt;a href="http://www.kremalicious.com/2008/06/ubuntu-as-mac-file-server-and-time-machine-volume/"&gt;одним из которых я и пользовался&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Вот пожалуй и все что касается софта.&lt;/p&gt;&lt;h3&gt;В итоге&lt;/h3&gt;&lt;p&gt;Atom все же поменял мое представление о том, какой должен быть домашний файловый сервер. На базе атома вполне можно собрать тихий и холодный storage, и спихнуть на него "домашнюю работу". При этом это все это будет работать под управлением стандартного дистрибутива вашей любимой ОС (хоть  windows). А сама железка мне обошлась даже немого дешевле чем Acer easyStore H340.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-4204231538886278297?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/4204231538886278297/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2010/11/nas.html#comment-form' title='Комментарии: 4'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/4204231538886278297'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/4204231538886278297'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2010/11/nas.html' title='Как я собирал NAS'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_olxzclEPtBk/TN9pTp8Xc1I/AAAAAAAAATI/4oF0scY8GSA/s72-c/DSC02620.JPG' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-1328616466109984933</id><published>2010-10-31T15:23:00.007+11:00</published><updated>2010-11-10T00:13:37.518+11:00</updated><title type='text'>HighLoad++ 2010 - впечатления</title><content type='html'>&lt;p&gt;В этом году я побывал на &lt;a href="http://www.highload.ru/"&gt;HighLoad++&lt;/a&gt;. Event довольно интересный, поэтому я попытаюсь вкратце описать свои впечатления, а так же основные тенденции наблюдаемые на конференции.
&lt;/p&gt;&lt;p&gt;Во-первых, стоит отметить, что &lt;a href="http://ontico.ru/vita/p/2010/hl-abstracts-2010.pdf"&gt;тезисы докладов&lt;/a&gt; и &lt;a href="http://ontico.ru/vita/p/2010/hl-presentations-2010.rar"&gt;презентации&lt;/a&gt; доступны на официальном сайте конференции.&lt;/p&gt;&lt;p&gt;Я живу во Владивостоке, поэтому мне пришлось пролететь очень большое расстояние чтобы попасть на конференцию. Этим частично обусловлено мое отношение к конференции как к таковой.&lt;/p&gt;&lt;p&gt;Начнем с приятного. На конференции действительно были специалисты с которыми было интересно общаться. Чувствовалось что люди знают о чем говорят и эту информацию они получили не из мануалов в интернете, а из своей ежедневной практики.&lt;/p&gt;&lt;p&gt;Организация была вполне приемлемая: был WiFi (который впрочем почему-то не ловил мой телефон) и достаточно еды (на которую у меня впрочем не было времени). Расписание было составлено таким образом, что самые интересные доклады параллельно не шли, а это радует.&lt;/p&gt;&lt;h2&gt;Основные понравившееся доклады&lt;/h2&gt;&lt;h3&gt;Performance tweaks and tools for Linux / Joe Damato&lt;/h3&gt;&lt;p&gt;В этом докладе рассказывается об основных инструментах профилирования под Linux — lsof, strace, ltrace, oprofile. Но об этом знают если не все, то почти все. Интересен этот доклад другим. Joe будучи Ruby хакером создал набор довольно интересных инструментов для профилирования Ruby приложений при помощи этих низкоуровневых инструментов.&lt;/p&gt;&lt;h3&gt;Секреты Фейсбука: как выдержать 50 миллионов запросов в секунду / Robert Johnson&lt;/h3&gt;&lt;p&gt;Robert Johnson — ведущий инженер по разработке ПО в компании Facebook (а по совместительству, как и я, &lt;a href="http://www.facebook.com/profile/pic.php?oid=AQDx6Dr4kb3wd6HUdPHzb4sbFXzOmKVdmlvP77DIo3qHwL1WPrOX-uu2jInbXcoGWR4&amp;amp;size=normal"&gt;владелец Jackson'а&lt;/a&gt;). Роберт много говорил не только о технической стороне масштабирования, но и об организационных мерах и том, каких направлений должен придерживаться коллектив участвующий в разработке высоконагруженного приложения. Основные посылы следующие:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;большому проекту рано или поздно прийдется столкнутся с горизонтальным масштабированием;&lt;/li&gt;&lt;li&gt;все изменения должны быть инкрементальными (нет изменений, которые не могут быть отменены);&lt;/li&gt;&lt;li&gt;если вы не можете что-то измерить, вы не можете этим управлять;&lt;/li&gt;&lt;li&gt;работа в небольших независимых коллективах (от 3 до 7 человек) над разными проектами (фотографии, личные сообщения и т.д.);&lt;/li&gt;&lt;li&gt;люди ответственные за что-либо должны иметь контроль над этим.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;В личном общении с Робертом я смог узнать следующие вещи:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;социальные графы очень тяжело partition'ить. Я спросил не пытались ли они заимствовать опыт MMORPG проектов? По идее у них схожие задачи, но гораздо более жесткие требования к latency. Роберт сказал, что это интересная идея, но ничего подобного они не пробовали делать. Так же он не особо верит в том что алгоритмы кластеризации графов смогут помочь им решить эту проблему в общем виде и достичь при этом вменяемой производительности;&lt;/li&gt;&lt;li&gt;их custom'ный протокол репликации в mysql, обошелся им в десяток строк кода, что и обусловило использование такого, на мой взгляд, странного решения;&lt;/li&gt;&lt;li&gt;в довольно большом проекте в угоду производительности коллектива в целом приходится принимать такие решения, как писать HipHop. Насколько я понял, Роберт не особо верит в его широкий adoption за пределами Facebook. Это довольно специфические решение проблемы в их специфическом окружении.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Быстрое развертывание шаблонов и статики в Mail.ru / Николай Кондратов&lt;/h3&gt;&lt;p&gt;Несмотря на немного "попсовое" название, доклад был довольно интересный. Ребята рассказывали о том, как они организовали работу верстальщиков и какие инструменты они им дали для развертывания приложения на сервере. Основная особенность больших систем не только в высокой нагрузке, но и в очень сложных процедурах развертывания системы. Последний факт почему-то редко получает много внимания, а зря.&lt;/p&gt;&lt;p&gt;В mail.ru особенности процесса следующие:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;шаблоны пишутся на псевдоязыке и позже могут быть транслированы в C/Perl;&lt;/li&gt;&lt;li&gt;шаблоны и статика доставляются на production отдельно от кода приложения, тем самым код и шаблоны могут меняться независимо. Схема выглядит довольно хрупко. На мой вопрос: "как вы избегаете несовместимых изменений между кодом и шаблонами?", они ответили: "мы внимательно следим за этим";&lt;/li&gt;&lt;li&gt;у каждого разработчика есть свой сетевой диск (который монтируется при помощи &lt;a href="http://fuse.sourceforge.net/"&gt;FUSE&lt;/a&gt;). Этот диск связан с отдельным для разработчика доменом, на котором крутится приложение;&lt;/li&gt;&lt;li&gt;артефакт с шаблонами и статикой доставляется на production машину, с которой посредством p2p сети (torrent) распространяется по frontend'ам. Исходная машина работает в режиме суперсидирования (то есть каждый блок отдается только единожды). Ребята объяснили такое решение тем, что в противном случае у них заканчивается network bandwidth на исходной машине (размер артефакта порядка 100Mb).&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Progressive downloads and Rendering / Stoyan Stefanov (Yahoo)&lt;/h3&gt;&lt;p&gt;Yahoo, как известно "помешана" на оптимизации на клиентской стороне. Этот доклад всецело посвящен такого рода оптимизациям. Много говорилось про user perception и то как люди оценивают производительность. О том что стабильность производительности (среднеквадратичное отклонение) важнее, чем абсолютные значения latency. Про аттрибуты &lt;a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#attr-script-async"&gt;async и deffered&lt;/a&gt;, которые предоставляет HTML5. Про то что такое chunked response и как его использует Google и Amazon. Как CSS может значительно снизить производительность вашего web-приложения. Доклад довольно довольно последовательный, так что очень советую посмотреть &lt;a href="http://www.slideshare.net/stoyan/progressive-downloads-and-rendering"&gt;слайды&lt;/a&gt; а также &lt;a href="http://vimeo.com/15981041"&gt;видео&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Я из этого доклада я вынес следующее: performance is a feature (этот тезис всплывал на конференции не раз). Если вы хотите чтобы ваше приложение быстро рендерилось на стороне клиента (что очень важно еще и в связи с ростом процента мобильных клиентов), то к этому вопросу над подходить как к фиче. Несмотря на расхожее мнение, добавить такого рода оптимизации в уже существующее приложение написанное без оглядки на вопросы client side performance, может быть довольно непросто.&lt;/p&gt;&lt;h3&gt;Thinking Clearly About Performance / Carry Millsap&lt;/h3&gt;&lt;p&gt;"Пушка" первого дня. Несмотря на относительно низкую практическую ценность доклада, я считаю что излагаемая в нем информация очень важна, — она вправляет мозги. В докладе излагается последовательный, целостный, математический подход к оценке производительности и пропускной способности. Поэтому вдвойне жалко что зал был почти пустой: человек 15-20.&lt;/p&gt;&lt;p&gt;Основные постулаты:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;пропускная способность и время отклика связаны опосредованно. Узнать одну метрику по другой, вы можете только в ряде частных случаев. В общем случае, вы должны измерять обе метрики независимо.&lt;/li&gt;&lt;li&gt;арифметическое среднее не самый удачный способ оценки производительности. &lt;a href="http://en.wikipedia.org/wiki/Percentile"&gt;Percentile&lt;/a&gt; значения лучше.&lt;/li&gt;&lt;li&gt;важна не только абсолютная мера производительности, но и стабильность этой меры (дисперсия времени отклика);&lt;/li&gt;&lt;li&gt;для того чтобы достичь стабильной производительности, необходимо контролировать утилизацию исполнителей (для чего в &lt;a href="http://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B8%D1%8F_%D0%BC%D0%B0%D1%81%D1%81%D0%BE%D0%B2%D0%BE%D0%B3%D0%BE_%D0%BE%D0%B1%D1%81%D0%BB%D1%83%D0%B6%D0%B8%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F"&gt;теории систем массового обслуживания&lt;/a&gt; есть соответствующий мат. аппарат);&lt;/li&gt;&lt;li&gt;существуют некоторые &lt;a href="http://en.wikipedia.org/wiki/Gustafson's_law"&gt;фундаментальные&lt;/a&gt; &lt;a href="http://en.wikipedia.org/wiki/Amdahl's_law"&gt;законы&lt;/a&gt;, которые &lt;a href="http://dotsid.blogspot.com/2009/01/blog-post.html"&gt;ограничивают&lt;/a&gt; максимальную пропускную способность. Вы должны знать их.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Я очень советую прочитать &lt;a href="http://method-r.com/downloads/doc_details/44-thinking-clearly-about-performance"&gt;одноименную публикацию&lt;/a&gt;, чтобы получить более полное представление об этом материале.&lt;/p&gt;&lt;h2&gt;Закулисы&lt;/h2&gt;&lt;p&gt;И все же большая часть информации была получена не на докладах, а за кулисами. Именно там была возможность пообщаться с опытными людьми: как докладчиками, так и просто участниками конференции. Из этого общения я вынес для себя несколько моментов:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://sphinxsearch.com/"&gt;Sphinx&lt;/a&gt; очень популярен. Этим фактом, впрочем, никого не удивишь, но тем не менее полезно было получить тому подтверждение от людей реально его использующих;&lt;/li&gt;&lt;li&gt;&lt;a href="http://code.google.com/p/redis/"&gt;Redis&lt;/a&gt; тоже довольно популярное решение, что для меня стало новостью. Я встретил по меньшей мере пятерых человек из разных компаний, которые используют это хранилище;&lt;/li&gt;&lt;li&gt;стандартная библиотека Scala изобилует довольно "детскими" багами как функциональными, так performance-related. Если вы хотите использовать Scala в крупном проекте, есть смысл подумать об использовании стандартной библиотеки Java, как бы грустно это ни звучало;&lt;/li&gt;&lt;li&gt;системы асинхронного обмена сообщениями тоже получили довольно широкий adoption. Очень много кто использует те или иные реализации очередей (в основном memcacheQ и Redis);&lt;/li&gt;&lt;li&gt;и несмотря на это, практически никто не знаком с &lt;a href="http://www.eaipatterns.com/"&gt;EIP&lt;/a&gt;, основополагающим трудом в этой области;&lt;/li&gt;&lt;li&gt;вопреки расхожему мнению, вопрос найма специалистов стоит остро в любой точке нашей страны (а возможно и планеты). Владивосток, будучи относительно небольшим городом, в этом отношении не особо хуже Москвы.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Ложка дегтя&lt;/h2&gt;&lt;p&gt;Впрочем, не все так безоблачно. До сих пор, я намеренно игнорировал негативные моменты связанные с конференцией:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;некоторые доклады были попросту скучные, а некоторые я считаю &lt;i&gt;вредные&lt;/i&gt;. Абсолютизм пропагандируемый в докладе "Практическое создание крупного масштабируемого web 2.0 проекта с нуля" это на мой взгляд один из худших феноменов, который может настигнуть специалиста. Масштабируемость в этом докладе провозглашается чуть ли не самодостаточным свойством системы ради которого и "крутится Земля". Попытки аудитории обратить внимание докладчика на то что иногда существуют определенные требования к гарантии в отношении consistency (которые влияют на принимаемые решения и выбор инструментов) не увенчались успехом;&lt;/li&gt;&lt;li&gt;некоторые участники в личном общении беззастенчиво утоляли собственные комплексы. Один участник в течении 10 минут пытался мне объяснить что настраивать GC в Java очень просто. Объяснения сводились примерно к следующему. В Java есть один стоящий GC — Parallel GC, все остальное — ошибка природы. Серьезно к такому мнению относится не получается (на самом деле, это еще один частный случай абсолютизма описанного в предыдущем пункте);&lt;/li&gt;&lt;li&gt;в рамках конференции был круглый стол об использовании SMP систем. Intel прислала на это событие не технического специалиста, а, если я не ошибаюсь, менеджера по связям с общественностью. Стоит ли говорить, что это не тот кого мы ждали;&lt;/li&gt;&lt;li&gt;вообще, в конце докладов оставалось очень мало времени на вопросы. Иногда доходило до смешного, в конце доклада организаторы говорили: "у нас есть время на два вопроса". Два вопроса?! Вы издеваетесь?! По факту, обсуждение докладов осуществлялось за кулисами в компании 5-10 особо заинтересованных участников и самого автора.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;В сухом остатке&lt;/h2&gt;&lt;p&gt;Участие в конференции составило 15 000 рублей. Для иногородних еще стоимость билетов и проживания в городе. Поэтому я бы дал следующий совет тем, кто собирается поехать на HighLoad++ в будущем. И пожалуй я это выделю в отдельный абзац.&lt;/p&gt;&lt;p&gt;&lt;i&gt;Основной источник информации на такого рода event'ах — это личное общение.&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;i&gt;&lt;/i&gt;Не стоит ехать на конференцию если вы собираетесь ограничиться прослушиванием докладов или вы социофоб. Необходимо знакомится с участниками, общаться, задавать вопросы, выражать свое мнение и провоцировать людей на дискуссии. Только тогда участие в конференции будет &lt;i&gt;экономически целесообразным&lt;/i&gt;.&lt;/p&gt;&lt;p&gt;Вот наверное и все. Если у вас есть еще какие-либо вопросы, с удовольствием отвечу на них.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-1328616466109984933?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/1328616466109984933/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2010/10/highload-2010.html#comment-form' title='Комментарии: 4'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/1328616466109984933'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/1328616466109984933'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2010/10/highload-2010.html' title='HighLoad++ 2010 - впечатления'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-7713400146286783016</id><published>2010-08-30T21:16:00.009+12:00</published><updated>2010-08-30T21:58:43.065+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='theory'/><category scheme='http://www.blogger.com/atom/ns#' term='simplicity'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Keep it simple, stupid</title><content type='html'>&lt;p&gt;Простота. Краеугольный камень нашей профессии. Наверняка вам приходилось слышать от своих коллег: “это можно сделать гораздо проще”. Или вы говорили это вашим коллегам: “смотри, можно сделать так. Это ведь гораздо проще”, а в ответ получали взгляд полный непонимания, как бы говорящий вам: “и это ты считаешь проще?”.
&lt;p&gt;В программной инженерии &lt;i&gt;определенно произошла инфляция слова “простота”&lt;/i&gt;.
&lt;img src="http://ozguru.mu.nu/Photos/simplicity.gif" /&gt;
&lt;h2&gt;Простота — это расстояние&lt;/h2&gt;
&lt;p&gt;Не существует общепринятого определения простоты. Тем не менее, если мы заглянем в словарь то увидим, что очень часто рядом со словом простота фигурируют такие понятия как: “предсказуемость”, “прямолинейность”, “ясность”, “очевидность”.
&lt;p&gt;Что мы считаем простым? Процессы и объекты чье поведение легко предсказать. Если “оно” ведет себя так как я от “него” ожидаю, значит я понимаю как “оно” работает и скорее всего смогу “его” изменить не поломав. В противоположность этому, если “оно” ведет себя непредсказуемо, значит я не понимаю основополагающих принципов лежащих в “его” основе. И любая попытка “его” изменить может окончится таким же непредсказуемым результатом. В этом случае процесс постулируется слишком сложным.
&lt;p&gt;Такие “предсказания” возможны благодаря тому что в голове человека есть ментальная (поведенческая) модель описывающая этот процесс или объект. И чем сильнее соответствует модель реальности, тем проще расценивается объект. Так что да, простота есть расстояние. &lt;i&gt;Расстояние между ожидаемым и действительным&lt;/i&gt;.
&lt;h2&gt;Мера простоты — понятие субъективное&lt;/h2&gt;
&lt;p&gt;Теперь это должно быть очевидно. У двух людей могут быть различные поведенческие модели, а значит и своя мера простоты относительно того или иного процесса. Если вы считаете какой-то процесс простым, это значит что у вас в голове есть удачная модель происходящего. Именно она позволяет вам эффективно понимать и управлять происходящим. Мнение другого человека по этому же вопросу может быть диаметрально противоположенным, и это может обосновываться, например, отсутствием опыта работы в какой-либо сфере.
&lt;p&gt;Любой взрослый человек без проблем восстановит порядок букв в алфавите, но это не значит что алфавит объективно прост. Это всего лишь значит что вам его вбили в голову в школе. На самом деле в порядке следования букв в алфавите нет никакой логики и очевидности. Объективно алфавит сложен и непонятен. Я думаю латинский алфавит по памяти восстановят гораздо меньше людей, даже при том что порядок сильно пересекается с кириллицей.
&lt;p&gt;Но более интересна обратная ситуация. Если вы считаете какой-то процесс сложным, это еще не означает что процесс объективно непредсказуем и непонятен. Это может означать что &lt;i&gt;у вас просто нет подходящей поведенческой модели&lt;/i&gt;. Возможно не стоит клеймить все вокруг сложным, возможно просто стоит... научится?
&lt;p&gt;Например, если вы считаете что следующий код слишком сложен, так это просто потому что вы не знакомы с функциональным программированием.&lt;pre&gt;def qsort: List[Int] =&gt; List[Int] = {
  case Nil =&gt; Nil
  case pivot :: tail =&amp;gt;
    val(lessThan, greaterOrEqualThan) = tail partition(_ &amp;lt; pivot)
    qsort(lessThan) ::: pivot :: qsort(greaterOrEqualThan)
}&lt;/pre&gt;
&lt;p&gt;Это приводит нас к заключению, что если мы хотим создать простой продукт, мы обязаны учитывать ментальные особенности людей которые будут работать с продуктом. И тут возможны два варианта:&lt;ol&gt;&lt;li&gt;создавать процессы близкие к ожиданию пользователей;&lt;/li&gt;&lt;li&gt;создавать в головах пользователей новые поведенческие модели (учить пользователей).&lt;/li&gt;&lt;/ol&gt;​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​Помните этот комикс?
​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​&lt;img src="http://stuffthathappens.com/blog/wp-content/uploads/2008/03/simplicity.png" /&gt;
&lt;p&gt;Последний продукт просто ужасен, верно? Или нет? Я незнаю. Мне ясна посылка этого комикса, — существует тенденция к чрезмерному усложнению вещей (или их недостаточному упрощению, как вам больше нравится).
&lt;p&gt;Но что, если мы говорим об экспертной системе где сложность является частью предметной области, а не следствием особенностей имплементации и человек работающий с ней должен быть в нее очень хорошо погружен. Скажем... пилот реактивного лайнера.
&lt;a href="http://haikold.narod.ru/images/kabina_1.jpg"&gt;&lt;img width="100%" src="http://haikold.narod.ru/images/kabina_1.jpg" /&gt;&lt;/a&gt;

&lt;p&gt;Это кабина еще советского Ту-154М, реактивного самолета выпуска середины 80-х. Обычный человек не сможет идентифицировать большинство приборов, рычагов управления и их назначения. Значит ли это что интерфейс сложен для тех для кого он создавался, для пилотов?
&lt;p&gt;Тенденции к упрощению существуют и в авиации, так что мы можем поискать более простой реактивный лайнер (так сказать, "для нубов"). Давайте посмотрим на кокпит широкофюзеляжного A-380, который начал эксплуатироваться в 2007 году. Считается что A-380 более прост в управлении. Об этом говорит даже тот факт что экипаж A-380 составляет два человека (против 4 человек в Ту-154М).
&lt;a href='http://samchuiphotos.com/A380Sydney/SYDD2588.jpg'&gt;&lt;img src="http://samchuiphotos.com/A380Sydney/SYDD2588.jpg" width="100%" /&gt;&lt;/a&gt;

&lt;p&gt;Аналоговых приборов почти не осталось, им на смену пришли цифровые приборы и LCD панели. Рычаги управления стали больше похожи на джойстик. Основным органом управления, судя по всему, стала клавиатура. Но стал ли он проще? Для пилотов может быть, для всех остальных никакой разницы. Если вы чувствуете себя как слон в посудной лавке в кабине Ту-154, тоже самое будет в кабине A-380.
&lt;p&gt;Конечно, если вы можете создать продукт который будет решать задачи пользователя и при этом будет простым как электрочайник, то это просто прекрасно. Но есть сферы где человеку нужен полный контроль над происходящим и масса информации чтобы принять верное решение. И в этом случае может быть не лучшей идеей сводить интерфейс к одной кнопке в UI/одному методу в API и т.д.
&lt;h2&gt;Простота многомерна&lt;/h2&gt;
&lt;p&gt;Но вернемся к программированию. Простота в отношении программного кода обманчиво проста. Эта простота (или ее отсутствие) может проявляться в разных контекстах, в зависимости от процесса разработки.
&lt;h3&gt;Простота использования&lt;/h3&gt;
&lt;p&gt;Если речь идет об API или бибилиотеке, то как просто ее развернуть на клиенте и начать использовать?
&lt;h3&gt;Простота написания&lt;/h3&gt;
&lt;p&gt;Насколько просто писать код с использованием той или иной технологии. Как много кода прийдется писать? Как много ошибок я могу допустить в процессе написания? Здесь все просто, и зачастую именно об этом виде простоты идет речь когда два программиста спорят о простоте. Но это далеко не единственное (и иногда не главное) качество.
&lt;h3&gt;Простота тестирования&lt;/h3&gt;
&lt;p&gt;Люди по своей природе не надежны. Наша интуиция не всегда хорошо работает, иногда мы ленимся, иногда теряем концентрацию. Поэтому, мое жизненное кредо заключается в том, что там где это возможно вопросами верификации корректности должны заниматься машины. Человек не всегда может подтвердить что код все еще работает увидеть что код уже не работает. Машина может.
&lt;p&gt;Именно поэтому для меня простота тестирования (тестируемость) — очень важное качество кода. Если я пишу код и не могу сделать так, чтобы верификацией работоспособности за меня занималась машина, то скорее всего этого кода не должно быть.
&lt;h3&gt;Простота верификации&lt;/h3&gt;
&lt;p&gt;Автоматическое тестирование не панацея конечно же, особенно если мы говорим о параллельных приложениях, которые очень тяжело тестировать. Насколько просто вы можете загрузить в голову коллеги такую ментальную модель при помощи которой он мог бы сказать: “да, я уверен, этот код работает”? Это и есть простота верификации.
&lt;h3&gt;Простота понимания&lt;/h3&gt;
&lt;p&gt;Последнее по порядку, но не по значению. Насколько код очевиден, насколько легко понять что он делает, и как он это делает, а следственно и насколько легко изменить его. Простота понимания и верификации очевидно связаны, потому как тяжело верифицировать то, чего ты не понимаешь. Тем не менее я склонен считать это разными факторами.
&lt;p&gt;Это не исчерпывающий список. Вы можете изобразить все это на spider графике. Как и всегда, достичь максимума по всем направлениям вряд ли получится. Надо определить приоритеты и следовать им. Кому-то важнее простота написания, кому-то простота тестирования. Но в любом случае этот выбор должен быть осмыслен. Это то, что в наши дни очень тяжело найти — осмысленный выбор. В большинстве случаев выбор делается неявно в угоду упрощения процесса которым разработчик занимается именно сейчас. Это свойственно всем людям, — идти по пути наименьшего сопротивления.
&lt;p&gt;Проблема в том, что упрощений в чистом виде не так много. Чаще &lt;i&gt;приходится иметь дело с разменом&lt;/i&gt;. Упрощая одно, вы автоматически усложняете другое. Например, упрощение тестирования зачастую связанно с “расслоением” системы и усложнением ее понимания. Это размен на который мы идем, неважно отдаем мы себе в этом отчет или нет. Вопрос в том, &lt;i&gt;выгоден ли этот размен для нас&lt;/i&gt;? Но это тема совсем другого разговора...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-7713400146286783016?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/7713400146286783016/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2010/08/keep-it-simple-stupid.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/7713400146286783016'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/7713400146286783016'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2010/08/keep-it-simple-stupid.html' title='Keep it simple, stupid'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-7626581651950000585</id><published>2010-08-10T22:50:00.002+12:00</published><updated>2010-08-11T22:37:13.901+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='locks'/><category scheme='http://www.blogger.com/atom/ns#' term='messaging'/><category scheme='http://www.blogger.com/atom/ns#' term='lease'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>Lock vs. Lease</title><content type='html'>&lt;p&gt;Представьте себе типичную задачу, есть поток событий и этот поток событий надо разделить между несколькими пользователями системы. Типичный пример: модерация. Скажем, есть три модератора, которые просматривают добавляемый пользователями контент (допустим рекламные кампании). Мы хотим “распилить” поток событий между модераторами, чтобы они не делали одну и ту же работу дважды.
&lt;/p&gt;&lt;p&gt;В самом простом случае, мы можем использовать mq в любом его проявлении. Пользователи просто берут сообщение из головы и обрабатывают его. Эта схема хорошо работает, но к сожалению не всегда применима. Например, если пользователи имеют возможность указывать критерии событий которые хотят обрабатывать. Последнее требование очень часто в том или ином виде всплывает в подобных задачах, потому что люди (точно так же, как и процессоры) плохо переносят context switch. Например, если я настроился на модерацию авто тематики, то я фигурально выражаясь загрузил в кеш ключевые слова относящиеся к этой тематике и буду очень расстроен если мне подсунут объект с тематикой относящийся к сотовым телефонам.
&lt;/p&gt;&lt;p&gt;Окей, мы не можем использовать mq, что тогда? Далее приходит на ум следующее — хранить события в реляционной таблице (чтобы иметь возможность фильтрации) и брать shared lock на каждое из событий/объектов при их обработке. В данном случае lock’и позволяют исключить дублирующую обработку события.
Ничего криминального в использовании локов для решения этой задач нет, но если вы начнете имплементировать подобное решение, то вы очень быстро прийдете к тому что lock’и обладают семантикой отличной от той которая необходима вам. И вот почему...&lt;/p&gt;
&lt;h2&gt;Lock timeout&lt;/h2&gt;
&lt;p&gt;Если мы говорим про web-приложение, то необходимо понимать что факт закрепления события за конкретным пользователем должен иметь timeout. HTTP — это stateless протокол. После того как вы закрепили событие за пользователем, он может просто закрыть браузер и пойти по своим делам. Если мы хотим чтобы любое событие было рано или поздно обработано, мы обязаны аннулировать устаревшие локи.
&lt;/p&gt;&lt;p&gt;Но традиционный механизм lock’ов не имеет timeout’ов, и это решение вполне обосновано, так как обычно lock’и или используются внутри одного адресного пространства (а следственно проблемы исчезновения lock owner’а нет) или используются в distributed окружении, но имеют механизм autorelease’а (при разрыве соединения текущего владельца блокировки и координатора автоматически происходит release).
&lt;/p&gt;&lt;p&gt;Это означает в традиционных use case’ах использовании lock’ов между текущим владельцем лока и координатором всегда есть канал связи, чего нет в нашем случае.&lt;/p&gt;
&lt;h2&gt;Immediate locking&lt;/h2&gt;
&lt;p&gt;В нашем примере, если мы не можем взять lock на событие это означает что данное событие уже кто-то обрабатывает. В этом случае нам надо просто пропустить это событие и попытаться заблокировать следующее.
Но в большинстве случаев использования lock’ов, если lock уже взят, то клиент вынужден ждать своей очереди. Это вполе логично, так как действия выполняемые в разных потоках представляют собой различные не связанные между собой действия. Все они должны быть выполнены последовательно, для чего и используется lock.
&lt;/p&gt;&lt;p&gt;Фактически в данной задаче нам нужен не инструмент сериализации нескольких потоков (чем и является лок), а некий флаг благодаря которому мы могли бы исключить из обработки события которые уже находятся в процессе обработки.&lt;/p&gt;
&lt;h2&gt;Lease&lt;/h2&gt;
&lt;p&gt;Проблема в том, что люди видя эту семантическую разницу пытаются расширить интерфейс локов для того чтобы покрыть им данный use case использования. Это опасно, так как в механизм добавляется элемент autorelease’а, который легко может нарушить целостность данных. Представьте, что если при традиционном использовании локов (скажем, параллельном изменении коллекции элементов) autorelease произойдет до того момента когда владелец лока успеет выполнить операцию?
&lt;/p&gt;&lt;p&gt;Вместо того чтобы пытаться приспособить локи там, где они не нужны, давайте посмотрим в сторону другого решения. &lt;strong&gt;&lt;/strong&gt;Я лично считаю поиск аналогий в реальном мире или смежных областях очень полезным занятием. Это позволяет лучше осознать задачу и процессы лежащие в ее природе.
&lt;/p&gt;&lt;p&gt;Помните DHCP? Это протокол автоматической конфигурации сетевого стека компьютера. Сегодня его можно найти буквально в любом домашнем маршрутизаторе. DHCP позволяет компьютеру автоматически получать IP-адрес и автоматически конфигурировать сетевой стек в зависимости от топологии сети в которой находится компьютер.
&lt;/p&gt;&lt;p&gt;Обратите внимание, когда компьютер получает IP-адрес по DHCP, он не посылает запрос “дайте мне IP 192.168.1.15”. Он посылает запрос: “дайте мне любой свободный адрес”. В ответ он получает lease. Lease — это аренда адреса. Аренда имеет timeout, по истечении которого, если lease не был продлен, то аренда аннулируется и IP-адрес возвращается в список доступных. Вполне подходит под нашу задачу, верно?
&lt;/p&gt;&lt;p&gt;В случае DHCP, клиенту по большому счету все равно с каким IP-адресом работать. В нашей задаче клиенту все равно какие именно события обрабатывать. Главное чтобы они удовлетворяли формальным требованиям о которых мы говорили раньше.
&lt;/p&gt;&lt;p&gt;Модель аренды гораздо ближе к природе задачи. Во-первых, timeout’ы, которые нам просто необходимы, лежат в природе механизма аренды. Во-вторых, аренда не подразумевает владения объектом. Фактически в этот же момент с этим же объектом может происходить какая-то другая операция (например, индексация), которая скорее всего имеет возможность выполнятся параллельно, а значит данные находящиеся в объекте не являются предметом защиты от конкурентного доступа. Это сигнализирует о том что локи в данном случае не уместны. В этой задаче lock’и могут служить только инструментом обеспечения целостности списка арендованных объектов, но не более того.
&lt;/p&gt;&lt;p&gt;P.S. интересно услышать к какими способами решения схожих задач приходилось сталкиваться вам?
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-7626581651950000585?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/7626581651950000585/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2010/08/lock-vs-lease.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/7626581651950000585'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/7626581651950000585'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2010/08/lock-vs-lease.html' title='Lock vs. Lease'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-983680641600374672</id><published>2010-06-05T15:42:00.001+12:00</published><updated>2010-06-07T23:30:07.241+12:00</updated><title type='text'>About motivation</title><content type='html'>&lt;p style="clear: both"&gt;Does anybody know why we do what we do? Why do we wake up everyday and go to our workplaces? It seems that the most obvious reason is money. We need money to function in the society. We need some funds to make some plans, if you wish.&lt;/p&gt;&lt;p style="clear: both"&gt;It turns out that not only money motivate people to do their job. OSS is the proof. Well, yes, we can make money from open source projects. And a lot of companies do this already, but it's not the point. The point is that there is another force motivating people. Most good programmers like some of my colleagues and buddies work not because of money, but because they feel passion to write programs. Money is a &lt;em&gt;required but not sufficient&lt;/em&gt; part of motivation.&lt;/p&gt;&lt;p style="clear: both"&gt;Daniel H. Pink some time ago gave an excellent talk about motivation factors and how to use them in work process.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;span style=" text-align: center; display: block; margin: 0 auto 10px;"&gt;&lt;object height="385" width="640"&gt;&lt;param name="movie" value="http://www.youtube.com/v/_mG-hhWL_ug&amp;hl=en_US&amp;fs=1&amp;" /&gt;&lt;param name="allowFullScreen" value="true" /&gt;&lt;param name="allowscriptaccess" value="always" /&gt;&lt;embed src="http://www.youtube.com/v/_mG-hhWL_ug&amp;hl=en_US&amp;fs=1&amp;" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" height="385" width="640"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;/span&gt;Some companies, like &lt;a href="http://www.atlassian.com/"&gt;Atlassian&lt;/a&gt;, show outstanding results in the process of self organizing their employees. They simply said to their developers:&lt;/p&gt;&lt;blockquote style="clear: both"&gt;&lt;p&gt;next 24 hours you could do &lt;em&gt;whatever you want&lt;/em&gt; with &lt;em&gt;whoever you want&lt;/em&gt;, but you should &lt;em&gt;show results&lt;/em&gt; to the company within next 24 hours.&lt;br /&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p style="clear: both"&gt;Looks familiar, right? Yes, it's some sort of &lt;a href="http://www.scottberkun.com/blog/2008/thoughts-on-googles-20-time/"&gt;Google's 20% culture&lt;/a&gt;.&lt;/p&gt;&lt;p style="clear: both"&gt;Nevertheless, so called &lt;a href="http://www.flickr.com/photos/mcannonbrookes/sets/72157600212361474/"&gt;FedEx days&lt;/a&gt; &lt;a href="http://blogs.atlassian.com/rebelutionary/archives/000495.html"&gt;was born&lt;/a&gt; (like FedEx Ground day-definite delivery, I believe):&lt;/p&gt;&lt;blockquote style="clear: both"&gt;&lt;p&gt;Overall, I'd say we did pretty damn well. People built some kick ass projects, we all learnt about new technologies, and everyone seemed to have a good time.&lt;br /&gt;[...]&lt;br /&gt;Will we repeat the exercise? Definitely. I think it was great thing to do - developers got to play, exercise their minds a little, build some fun stuff and learn about new technologies.&lt;br /&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p style="clear: both"&gt;There are a &lt;a href="http://confluence.atlassian.com/display/DEV/FedEx+XIV+Delivery+-+log4j+error+timeline"&gt;lot&lt;/a&gt; &lt;a href="http://confluence.atlassian.com/display/DEV/FedEx+9+Delivery+-+ConfluenceFS"&gt;of&lt;/a&gt; &lt;a href="http://confluence.atlassian.com/display/DEV/Fedex+9+Delivery+-+Atlassian+Invaders"&gt;really&lt;/a&gt; &lt;a href="http://confluence.atlassian.com/display/DEV/FedEx+9+-+JIRA+Hobnob"&gt;fun&lt;/a&gt; &lt;a href="http://confluence.atlassian.com/display/DEV/FedEx+XIV+Delivery+-+Atlassian+History+Chrome+Extension"&gt;things&lt;/a&gt; provided by Atlassian FedExes. Which makes me believe in the words if Daniel Pink:&lt;/p&gt;&lt;blockquote style="clear: both"&gt;&lt;p&gt;Pay people enough to take the issue of money off the table.&lt;br /&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p style="clear: both"&gt;There is no need to pay people more that that. Instead of raising salary you would better invest more aggressively in creating an attractive and efficient work environment. If you work in a IT company give your team some time to share experience and thoughts about new technologies, provide really good hardware for your employees (it's so cheap anyway), and, holy crap, &lt;em&gt;do not ever shape an Internet connection. &lt;/em&gt;I mean it, &lt;em&gt;never&lt;/em&gt;.&lt;/p&gt;&lt;br class='final-break' style='clear: both' /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-983680641600374672?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/983680641600374672/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2010/06/about-motivation.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/983680641600374672'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/983680641600374672'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2010/06/about-motivation.html' title='About motivation'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-6844884769117046848</id><published>2010-04-16T23:43:00.000+12:00</published><updated>2010-04-16T23:44:21.062+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='development'/><category scheme='http://www.blogger.com/atom/ns#' term='load-testing'/><title type='text'>ldt или нагрузочное тестирование по-простому</title><content type='html'>&lt;p style="clear: both"&gt;И снова про инструменты разработки. Часто бывает необходимо сравнить производительность/пропускную способность того или иного участка кода, а писать тестирующий код ой как не хочется. А ведь надо всего-то, запустить нужный метод N раз и померять время выполнения.&lt;/p&gt;&lt;p style="clear: both"&gt;Вот сегодня у меня возник вопрос. Сколько процессору надо времени, чтобы проитерироваться по массиву с заданной длинной?&lt;/p&gt;&lt;p style="clear: both"&gt;Недолго думая, пишем простой POJO класс описывающий тестовый случай.&lt;/p&gt;&lt;pre style="clear: both"&gt;package com.blogspot.dotsid.ldt;

public class ArrayIterationTest {

  private int size;
  private int[] data;

  public void setSize(int size) {
    this.size = size;
  }

  public void prepare() {
    data = new int[size];
  }

  public void doTest() {
    for ( int i : data );
  }
}
&lt;/pre&gt;&lt;p style="clear: both"&gt;компилируем исходник и находясь в classpath'е выполняем:&lt;/p&gt;&lt;pre style="clear: both"&gt;bazhenov@home ldt -z com.blogspot.dotsid.ArrayIterationTest#doTest -n 100 -p "size=1000000"
                     RESULTS
--------------------------------------------------
 Concurrency level             : 1
 Samples count (per thread)    : 100
 Total time                    : 180ms
 Min. time                     : 1ms
 Max. time                     : 5ms
 Throughput                    : 553 tps
&lt;/pre&gt;&lt;p style="clear: both"&gt;В этом тесте мы создали массив размером 1 миллион позиций и проитерировались по нему 100 раз. Как видно мой процессор по этому массиву пробегает со скоростью несколько миллисекунд на одну полную итерацию.&lt;/p&gt;&lt;p style="clear: both"&gt;Довольно простой и эффективный инструмент для выполнения нагрузочных тестов на отдельные модули системы. Основные возможности:&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;ul style="clear: both"&gt;&lt;li&gt;поддержка многопоточного тестирования;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;поддержка warm up периода (указанное число первых прогонов теста может не участвовать в измерениях. Это бывает необходимо для обеспечения hot code execution path);&lt;br /&gt;&lt;/li&gt;&lt;li&gt;поддержка фикстур (prepare/cleanup);&lt;br /&gt;&lt;/li&gt;&lt;li&gt;sub millisecond accuracy;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;поддержка maven;&lt;/li&gt;&lt;/ul&gt;Утилита open source и доступна на &lt;a href="http://github.com/bazhenov/load-test-tool"&gt;github&lt;/a&gt;. Еще один маленький и незатейливый инструмент, который полезно иметь под рукой. Ведь именно из таких инструментов и формируется окружение которое позволяет нам работать эффективно. Как говорится, "что нельзя измерить, тем нельзя управлять". &lt;/p&gt;&lt;br class='final-break' style='clear: both' /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-6844884769117046848?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/6844884769117046848/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2010/04/ldt.html#comment-form' title='Комментарии: 8'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/6844884769117046848'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/6844884769117046848'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2010/04/ldt.html' title='ldt или нагрузочное тестирование по-простому'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-477790002680835664</id><published>2010-03-20T00:56:00.000+11:00</published><updated>2010-03-20T00:56:30.254+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='theory'/><category scheme='http://www.blogger.com/atom/ns#' term='hardware'/><title type='text'>Программисты и железо</title><content type='html'>&lt;p style="clear: both"&gt;Не так давно у нас на работе (Виктор, Игорь, Олег привет вам) состоялась дискуссия на тему: должны ли программисты знать как работает железо на котором выполняются их программы? И я еще раз убедился в том, что большинство программистов придерживаются мнения: "железо само по себе, а я сам по себе". Точка зрения вполне ожидаемая и ничего принципиально неправильного в ней нет, но я хотел бы "копнуть глубже".&lt;/p&gt;&lt;p style="clear: both"&gt;Вообще, мне кажется вопрос "стоит ли программистам изучать железо" в чем-то похож на вопрос "стоит ли программистам изучать математику", — ответ неоднозначен. Существует множество вопросов, ответы на которые могут повлиять на окончательное решение: в какой предметной области вы работаете, насколько эти конкретные знания ценны для вас и т.д. Это вопрос &lt;a href="http://ru.wikipedia.org/wiki/Окупаемость_инвестиций"&gt;инвестиций и прибыли&lt;/a&gt;. В данном случае под инвестициями подразумевается в первую очередь ваше свободное время, а под прибылью — профессиональные навыки и знания, которые позволят вам более эффективно делать вашу работу. Тогда вопрос сводится к следующему: &lt;em&gt;чем изучение железа может помочь нам программистам делать свою работу лучше?&lt;/em&gt;&lt;/p&gt;&lt;p style="clear: both"&gt;Мы с вами живем в эпоху высокоуровневых языков. Программисты большинства отраслей с успехом забыли про unmanaged языки, не говоря уже об ассемблерных вставках. В этом есть свои преимущества. Во-первых, это позволило нам программистам быть более эффективными. При равных доступных ресурсах теперь мы можем решать более сложные задачи. Во-вторых, не требуя от программистов "погружения в железо", мы позволяем им специализироваться в своей области, а также увеличить их количество за счет более короткой learning curve. Последнее впрочем имеет и свои негативные последствия, но я не буду сейчас акцентировать на них внимание.&lt;/p&gt;&lt;p style="clear: both"&gt;И все же знание железа может быть полезно. Но позвольте мне сразу оговорится, я &lt;strong&gt;не &lt;/strong&gt;считаю что результатом погружения в hardware должен стать код более быстрый с точки зрения машины. Да, иногда это является самоцелью и приходится экономить буквально говоря миллисекунды, но в текущем контексте я хочу акцентировать внимание на другом аспекте.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;strong&gt;Почему стоит изучать?&lt;/strong&gt;&lt;/p&gt;&lt;p style="clear: both"&gt;Как бы программистам не хотелось думать, что они никоим образом не зависят от аппаратной платформы, — это не так. Да, компиляторы, виртуальные машины и операционные системы перевели среднестатистического программиста на "дальнюю орбиту" в аппаратных вопросах. Но железо все же влияет на то, какими абстракциями мы пользуемся для написания программ. Просто мы не всегда это замечаем.&lt;/p&gt;&lt;p style="clear: both"&gt;Возьмите хотя бы возросший интерес, к &lt;a href="http://en.wikipedia.org/wiki/Actor_model"&gt;actor'ам&lt;/a&gt;. В общем и целом actor'ы представляют собой модель кооперативной многозадачности. Если вы хорошо помните историю, то кооперативная многозадачность в широких масштабах последний раз "&lt;a href="http://ru.wikipedia.org/wiki/Windows_3.x"&gt;упоминалась&lt;/a&gt;" в начале 90-х. У нее есть один существующий минус — она не работает на маленьком количестве исполнителей. Когда у вас один процессор легко может случится так, что какая-либо задача оккупирует его и другие задачи не смогут выполнятся. Тем не менее, у кооперативной многозадачности есть плюсы которые заставили нас опять обратить свой взор на эту модель...&lt;/p&gt;&lt;p style="clear: both"&gt;Дело в том, что основная проблема с которой сталкиваются современные модели распараллеливания (которые преимущественно основаны на потоках) — это context switch. Доступ к памяти не является random'ным, чтобы ни говорили разработчики железа. Latency доступа к памяти на современных платформах составляет &lt;em&gt;сотни тактов процессора&lt;/em&gt;. За это время процессор может сделать много работы. Эту проблему сейчас адресуют довольно простым путем — кеш, размер которого уже достигает 2Mb и больше на ядро. Проблемы кеша вам уже &lt;a href="http://dotsid.blogspot.com/2009/08/blog-post_24.html"&gt;должны быть известны&lt;/a&gt;, — они одни и те же, неважно говорим ли мы о CPU cache или о memcached. Как только вы получаете промах кеша, вы платите performance penalty. Именно cache miss'ы являются &lt;a href="http://www.cs.rochester.edu/u/cli/research/switch.pdf"&gt;источником&lt;/a&gt; деградации производительности в случае высокого context switching'а. Об этом говорило очень &lt;a href="http://www.infoq.com/presentations/brian-goetz-concurrent-parallel"&gt;много&lt;/a&gt; &lt;a href="http://www.infoq.com/presentations/click-crash-course-modern-hardware"&gt;умных&lt;/a&gt; &lt;a href="http://www.youtube.com/watch?v=BBMeplaz0HA"&gt;дядек&lt;/a&gt;.&lt;/p&gt;&lt;p style="clear: both"&gt;Решение довольно очевидно. Не переключатся между задачами лишний раз, только после завершения всей задачи целиком. Здравствуй кооперативная многозадачность. Не верите мне, &lt;a href="http://users.livejournal.com/_foreseer/43339.html"&gt;поверьте сотрудникам Яндекса&lt;/a&gt;. Не зря в Windows существуют &lt;a href="http://en.wikipedia.org/wiki/Fiber_(computer_science)"&gt;fiber'ы&lt;/a&gt;.&lt;/p&gt;&lt;p style="clear: both"&gt;Справедливости ради, следует заметить, что скорость не единственный (и лично для меня не главный) плюс модели actor'ов. Эта модель гораздо проще в понимании и тестировании чем традиционные thread-based приложения. Но интерес к этой модели начал появляться как раз в тот момент, когда традиционные способы увеличения производительности исчерпали себя и мы начали искать новые методы обеспечения роста. А это означает одну простую вещь — &lt;em&gt;программные абстракции используемые нами меняются в том числе и под "давлением аппаратных факторов"&lt;/em&gt;.&lt;/p&gt;&lt;p style="clear: both"&gt;У каждого технического решения есть свои причины влияющие на его формирование. Не всегда эти причины лежат непосредственно в области программной инженерии. Знание аппаратных факторов &lt;em&gt;дает программисту более полную картину того, почему мир в котором мы живем таков каков он есть&lt;/em&gt;. Это основная идея, которую на мой взгляд не обосновано игнорирует большинство программистов.&lt;/p&gt;&lt;p style="clear: both"&gt;Никто не просит вас уметь программировать на ассемблере и разбираться в opcode'ах микропроцессора. Но осознание того как работает процессор и какие bottleneck'и есть у существующих аппаратных платформ может дать вам очень хороший теоретический background для решения многих задач и ответа на многие вопросы.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;strong&gt;Root of all evil&lt;/strong&gt;&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;img src="http://windowsclient.net/blogs/damonwildercarr/WindowsLiveWriter/GetMoreLinqOperationsandIntegratet.5only_B9AA/premature_6_2.jpg" height="417" width="564" style=" text-align: center; display: block; margin: 0 auto 10px;" /&gt;Иногда можно встретить мнение что изучение железа плохо уже потому что человек изучающий железо приобретает некоторую зашоренность мышления и начинает оценивать решения лишь с точки зрения их аппаратной производительности.&lt;/p&gt;&lt;p style="clear: both"&gt;Верная предпосылка, но абсолютно не рациональный вывод. Это не проблема знаний, это проблема человека использующего эти знания. Чувство меры и здравоумие ни в коей мере не должно вас покидать, какие бы знания вы не получали.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;strong&gt;Tradeoffs&lt;/strong&gt;&lt;/p&gt;&lt;p style="clear: both"&gt;С другой стороны надо трезво оценивать свои возможности. Существует масса знаний/умений которые для программиста важнее чем знание железа. Вот, на мой взгляд, всего лишь некоторые из них (перечислено в произвольном порядке):&lt;/p&gt;&lt;ul style="clear: both"&gt;&lt;li&gt;ОО анализ и проектирование (шаблоны/SOLID принципы);&lt;br /&gt;&lt;/li&gt;&lt;li&gt;анализ и построение алгоритмов;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;классические структуры данных;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;диагностика типичных проблемных ситуаций;&lt;/li&gt;&lt;li&gt;тестирование и поддержка legacy систем;&lt;/li&gt;&lt;li&gt;умение писать корректный &lt;u&gt;многопоточный&lt;/u&gt; код, а также находить ошибки в многопоточном коде (светлое будущее с Clojure, Erlang, Scala, whatever еще не наступило);&lt;/li&gt;&lt;li&gt;функциональная декомпозиция;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;автоматизация процесса разработки;&lt;/li&gt;&lt;li&gt;продолжите список...&lt;/li&gt;&lt;/ul&gt;&lt;p style="clear: both"&gt;Если вы чувствуете, что есть важные знания из области программной инженерии которые для вас на данный момент важнее чем знание железа, то выбор очевиден. В первую очередь мы должны развиваться в тех направлениях которые диктует нам наша сфера деятельности.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;strong&gt;В итоге&lt;/strong&gt;&lt;/p&gt;&lt;p style="clear: both"&gt;Саморазвитие, как это ни странно, требует жертв. Иногда приходится тратить время на изучение вещей, изучать которые совсем не хочется. Лично для меня таким инструментом в свое время был git. Но, видимо, иногда для того чтобы стать лучше приходится "наступать своей песне на горло". Так что я верю, что рано или поздно, если вы хотите стать хорошим программистом, &lt;em&gt;вам прийдется выйти из своего дома и посмотреть как живут ваши соседи&lt;/em&gt;.&lt;/p&gt;&lt;br class='final-break' style='clear: both' /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-477790002680835664?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/477790002680835664/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2010/03/blog-post.html#comment-form' title='Комментарии: 5'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/477790002680835664'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/477790002680835664'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2010/03/blog-post.html' title='Программисты и железо'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-8043159883708546583</id><published>2010-02-15T23:21:00.000+11:00</published><updated>2010-02-15T23:22:02.369+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='scalability'/><title type='text'>Superlinear scalability</title><content type='html'>&lt;p style="clear: both"&gt;Как вы думаете во сколько раз может быть быстрее ваша программа, если вам дадут в два раза более "крутое" железо: в два раза более быстрый процессор, в два раза больше памяти и т.д. Интуитивный ответ — в два раза. Раньше я уже &lt;a href="http://dotsid.blogspot.com/2009/01/blog-post.html"&gt;писал&lt;/a&gt; о том, что не все так безоблачно. Взвесив все аргументы вы можете сказать: "окей, &lt;em&gt;максимум&lt;/em&gt; в два раза". Но что если бы я вам сказал, что при увеличении вычислительной мощности в два раза вы можете получить ускорение &lt;em&gt;больше чем в два раза&lt;/em&gt;?&lt;/p&gt;&lt;p style="clear: both"&gt;Давайте представим себе обратный случай: у вас есть алгоритм с асимптотической оценкой по времени в O(n). И вы решаете задачу эталонного размера на эталонном компьютере за эталонное время. Увеличим размер задачи в два раза. Скажем вместо 5 миллионов записей надо отсортировать 10 миллионов (например, за O(n) можно сделать топологическую сортировку). Как изменится эталонное время? Увеличится в двое... как минимум. Увеличение времени вдвое нам гарантировано, так как мы выполним в два раза больше операций. Но существуют причины для дополнительного замедления программы. Например, если удвоенный dataset не помещается в оперативную память, то вы получите большое количество page fault'ов и операционной системе прийдется одни страницы выгружать из оперативной памяти на жесткий диск, другие загружать с диска в оперативную память. В общем случае, вы можете получить замедление более чем в два раза.&lt;/p&gt;&lt;p style="clear: both"&gt;Но что если теперь мы начнем решать эту "удвоенную задачу" на "удвоенном компьютере"? Во-первых, мы получим двукратный прирост производительности связанный с удвоенной вычислительной способностью нового компьютера. Но также мы избавимся от лишних page in/page out'ов, что безусловно еще более убыстрит нашу программу, так как теперь не надо тратить лишнее время на IO. А это значит что &lt;em&gt;в сумме мы получим более чем двукратный прирост производительности&lt;/em&gt;.&lt;/p&gt;&lt;p style="clear: both"&gt;Утверждение "&lt;em&gt;при удвоении вычислительных ресурсов можно получить максимум двукратный прирост производительности&lt;/em&gt;" имеет силу только в условиях когда задача решается эффективно (то есть не делается лишней работы).&lt;/p&gt;&lt;br class='final-break' style='clear: both' /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-8043159883708546583?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/8043159883708546583/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2010/02/superlinear-scalability.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/8043159883708546583'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/8043159883708546583'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2010/02/superlinear-scalability.html' title='Superlinear scalability'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-8068699225019521492</id><published>2010-01-20T00:19:00.002+11:00</published><updated>2010-01-21T18:35:39.523+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='memcache'/><category scheme='http://www.blogger.com/atom/ns#' term='math'/><title type='text'>Миграция ключей</title><content type='html'>&lt;p style="clear: both"&gt;Сегодня в разговоре с одним &lt;a href="http://zerkms.livejournal.com/"&gt;знакомым&lt;/a&gt; всплыл следующий вопрос. В случае, если для дистрибуции ключей по нодам кластера используется типичная схема остатка от деления на количество серверов, какая доля ключей осуществляют миграцию, если один из серверов выводится из схемы? Интуитивным ответом является: "почти все" или "большинство". Тем не менее, если вы любите тренировать мозг, то вот вам небольшая задачка имеющая приложение в web-программировании.&lt;/p&gt;&lt;blockquote style="clear: both"&gt;&lt;p&gt;Формально говоря: при заданном количестве серверов &lt;code&gt;N&lt;/code&gt; и хеширующей функции &lt;code&gt;ƒ(x)&lt;/code&gt; обладающей выходным множеством с мощностью &lt;code&gt;P&lt;/code&gt; (&lt;code&gt;log(P)&lt;/code&gt; бит, для crc32 &lt;code&gt;P = 2&lt;sup&gt;32&lt;/sup&gt;&lt;/code&gt;, для md5 &lt;code&gt;P = 2&lt;sup&gt;128&lt;/sup&gt;&lt;/code&gt;) какое количество ключей осуществит миграцию в случае, если серверов станет &lt;code&gt;N-1&lt;/code&gt;, а для дистрибуции ключей используется значение &lt;code&gt;f(x) % N&lt;/code&gt;. &lt;br /&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p style="clear: both"&gt;Ответом является формула описывающая зависимость количества мигрирующих ключей от изначального количества серверов &lt;em&gt;и/или&lt;/em&gt; количества хранимых ключей.&lt;/p&gt;&lt;p style="clear: both"&gt;Допущения: функция &lt;code&gt;ƒ(x)&lt;/code&gt; имеет равномерное распределение на всем множестве входных значений, &lt;code&gt;P&lt;/code&gt; несравнимо больше &lt;code&gt;N&lt;/code&gt;.&lt;/p&gt;&lt;p style="clear: both"&gt;Удачи!&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;/p&gt;&lt;br class='final-break' style='clear: both' /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-8068699225019521492?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/8068699225019521492/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2010/01/blog-post_20.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/8068699225019521492'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/8068699225019521492'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2010/01/blog-post_20.html' title='Миграция ключей'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-5461124162687707391</id><published>2010-01-17T01:07:00.000+11:00</published><updated>2010-01-17T01:09:09.268+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='log analysis'/><title type='text'>Слежка за логами</title><content type='html'>&lt;p style="clear: both"&gt;Раньше я уже &lt;a href="http://dotsid.blogspot.com/2009/04/groovy-remote-shell.html"&gt;писал&lt;/a&gt; о том, что нам приходится разрабатывать дополнительный инструментарий для себя. Еще одна сфера которую над которой мы плодотворно поработали — это логгирование. Здесь я не буду говорить о пользе логгирования и о том как надо логгировать. В интернете полно информации по этим аспектам. Я хочу рассказать о том, как мы анализируем логи.&lt;/p&gt;&lt;p style="clear: both"&gt;Дело в том, что в сутки у нас генерируется порядка 100 мегабайт логов. Часть из этой информации — это информация об ошибках (при средней длине сообщения в 5 килобайт нам достаточно 4 секунд чтобы набрать мегабайт информации), часть это аудиторская информация. Просматривать такой объем информации в виде текстового файла — это просто нереально, поэтому мы создали для себя инструмент позволяющий аггрегировать информацию со всех компонентов системы и производить поиск по ней.&lt;/p&gt;&lt;p style="clear: both"&gt;Компоненты системы посылают сообщения по протоколу UDP. UDP был выбран не случайно. Мы не хотим чтобы коллектор логов мог пагубно влиять на production приложение, поэтому мы специально выбрали такой протокол, который позволил бы не блокировать компоненты в случае, если коллектор медленно работает, или вообще отключен. Похожим образом работает syslog-ng, но мы решили все же "изобрести свое колесо", чтобы иметь более изолированное решение и гибкость в определении протокола передачи данных.&lt;/p&gt;&lt;p style="clear: both"&gt;В сообщениях которые генерируют компоненты находится следующая информация:&lt;/p&gt;&lt;ul style="clear: both"&gt;&lt;li&gt;тестовое сообщение;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;stacktrace exception'а (опционально);&lt;br /&gt;&lt;/li&gt;&lt;li&gt;диагностический контекст (key-value набор аттрибутов).&lt;/li&gt;&lt;/ul&gt;&lt;p style="clear: both"&gt;Это позволяет иметь следующий довольно удобный web-frontend, который предоставляет общую информацию о возникавших проблемах и аудиторских сообщениях.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;a href="http://lh6.ggpht.com/_olxzclEPtBk/S1HG8JooDSI/AAAAAAAAANE/GJzsT7zN_HI/s800/Screen_shot_2010-01-16_at_23-full.1.png" class="image-link"&gt;&lt;img class="linked-to-original" src="http://lh6.ggpht.com/_olxzclEPtBk/S1HG7nmefJI/AAAAAAAAANA/KHbiit9W-7E/s800/Screen_shot_2010-01-16_at_23-thumb.1.png" height="112" width="373" style=" text-align: center; display: block; margin: 0 auto 10px;" /&gt;&lt;/a&gt;Для каждого события есть severity (error/warning/info и т.д.), дата последнего возникновения, сколько всего раз происходило это событие, а также application id (символическое имя компонента приславшего сообщение).&lt;/p&gt;&lt;p style="clear: both"&gt;По каждому отдельному сообщению можно посмотреть дополнительную информацию: stacktrace, а также диагностический контекст (на какой физической машине произошло событие, во время обслуживания какого клиента и т.д.).&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;a href="http://lh6.ggpht.com/_olxzclEPtBk/S1HG9bGLHYI/AAAAAAAAANM/UcGVi0IgRpc/s800/Screen_shot_2010-01-16_at_23-full.52.png" class="image-link"&gt;&lt;img class="linked-to-original" src="http://lh3.ggpht.com/_olxzclEPtBk/S1HG8hfv0DI/AAAAAAAAANI/APc7wWxjsSk/s800/Screen_shot_2010-01-16_at_23-thumb.52.png" height="126" width="380" style=" text-align: center; display: block; margin: 0 auto 10px;" /&gt;&lt;/a&gt;Бывает так, что стандартных интерфейсов недостаточно. В этом случае есть поиск который позволяет найти какие-то специфические записи.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;a href="http://lh5.ggpht.com/_olxzclEPtBk/S1HG-GTfXWI/AAAAAAAAANU/hoQCRahhQQc/s800/Screen_shot_2010-01-16_at_23-full.2.png" class="image-link"&gt;&lt;img class="linked-to-original" src="http://lh3.ggpht.com/_olxzclEPtBk/S1HG96sC_UI/AAAAAAAAANQ/KR71HYxXgYg/s800/Screen_shot_2010-01-16_at_23-thumb.2.png" height="75" width="380" style=" text-align: center; display: block; margin: 0 auto 10px;" /&gt;&lt;/a&gt;Поиск можно осуществлять по имени компонента приславшего событие, по дате и по всему диагностическому контексту. Приведу примеры некоторых запросов.&lt;/p&gt;&lt;ul style="clear: both"&gt;&lt;li&gt;&lt;code&gt;at: frontend severity: error&lt;/code&gt; — все ошибки произошедшие в компоненте frontend;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;occurred: last 2 days @user: bazhenov&lt;/code&gt; — все сообщения за последние два дня спровоцированные обработкой запроса для пользователя bazhenov;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;@machine: n25.baza.loc caused-by: slrDbConnectionFailedException&lt;/code&gt; — все записи пришедшие с сервера n25.baza.loc содержащие stacktrace exception'а типа slrDbConnectionFailedException.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p style="clear: both"&gt;Прямо здесь "на месте" из события можно создать тикет в нашей issue tracking системе. Вот так мы работаем с логами. А как вы следите за своими логами? :)&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;/p&gt;&lt;br class='final-break' style='clear: both' /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-5461124162687707391?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/5461124162687707391/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2010/01/blog-post.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/5461124162687707391'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/5461124162687707391'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2010/01/blog-post.html' title='Слежка за логами'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_olxzclEPtBk/S1HG7nmefJI/AAAAAAAAANA/KHbiit9W-7E/s72-c/Screen_shot_2010-01-16_at_23-thumb.1.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-8909321941128675238</id><published>2010-01-16T14:27:00.000+11:00</published><updated>2010-01-16T16:16:53.441+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='apache cassandra'/><category scheme='http://www.blogger.com/atom/ns#' term='scalaris'/><category scheme='http://www.blogger.com/atom/ns#' term='memcachedb'/><category scheme='http://www.blogger.com/atom/ns#' term='couchdb'/><category scheme='http://www.blogger.com/atom/ns#' term='key-value'/><category scheme='http://www.blogger.com/atom/ns#' term='scalability'/><category scheme='http://www.blogger.com/atom/ns#' term='project voldemort'/><category scheme='http://www.blogger.com/atom/ns#' term='redis'/><category scheme='http://www.blogger.com/atom/ns#' term='mongodb'/><title type='text'>KV-хранилища</title><content type='html'>&lt;p&gt;В последнее время в web-программировании появился очередной тренд — Key-Value базы данных. Существует просто великое множество KV-решений, — одно лучше другого. Но так ли они важны и какая от них польза? Разрешите мне немного порассуждать о происхождении KV-хранилищ.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Нет дыма без огня&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Недовольство реляционными базами данных начало появляться давно. Оказалось что на некоторых use case'ах они не такие быстрые как хотелось бы. Они слишком сложные для того чтобы большинство программистов понимало как они работают, а следственно и то, как их использовать. Идея изоляции программиста от физических деталей хранения данных с треском провалилась. Если вы не знаете нюансов вашей РСУБД, то эффективно использовать ее на большом dataset'е или при высокой конкурентной нагрузке у вас не получится.&lt;/p&gt;&lt;p&gt;Помните чему вас учили в университете? Первая нормальная форма, вторая, тертья, Бойса-Кодда и т.д. Бурный рост интернет-аудитории с одной стороны и функциональности web-приложений с другой привел к тому, что пришлось пересмотреть паттерны использования реляционных СУБД. Сейчас, для того чтобы получить от реляционной БД приемлемый уровень пропускной способности, необходимо &lt;a href="http://www.infoq.com/news/2007/08/denormalization"&gt;денормализовывать данные&lt;/a&gt;, &lt;a href="http://www.infoq.com/articles/ebay-scalability-best-practices"&gt;отказываться от распределенных транзакций&lt;/a&gt; и проверки целостности по внешним ключам.&lt;/p&gt;&lt;p&gt;Но не стоит спихивать эти проблемы на не дальновидность или не профессионализм разработчиков реляционных СУБД. Насколько хорошо вы себе представляете как работает реляционная СУБД? Давайте проведем quick test. Ответьте на следующие вопросы:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;вы знаете что такое план выполнения запроса и как БД его строит?&lt;br /&gt;&lt;/li&gt;&lt;li&gt;вы знаете как БД использует несколько индексов для фильтрации в случае, если нет index'а покрывающего все условия фильтрации?&lt;br /&gt;&lt;/li&gt;&lt;li&gt;вы знаете как БД использует индексы для сортировки, группировки и join'ов?&lt;br /&gt;&lt;/li&gt;&lt;li&gt;вы знаете сильные и слабые стороны hash и btree индексов?&lt;/li&gt;&lt;li&gt;вы знаете разницу между nested loop join, merge join и hash join?&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Если вы утвердительно ответили хотя бы на половину вопросов, то вы знаете, что какая бы продвинутая БД у вас не была, сложные join'ы, сложные критерии фильтрации и группировки требуют много времени, какие бы индексы вы ни создавали. Сделать эффективный индекс (индекс позволяющий не делать лишней работы и определить результат только по этому индексу) можно только если у вас невысокая вариативность выборок и вы знаете все виды возможных запросов.&lt;/p&gt;&lt;p&gt;Но реляционные базы данных очень хорошо справляются с простыми запросами. Например, извлечение по первичному ключу. Вместе с тем, оказалось, что многие задачи можно свести к тому, чтобы они решались преимущественно такими простыми запросами. В этом случае, вы можете добиться от вашей БД большего... значительно большего.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;KV-storage&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Однажды приведя свою систему к тому состоянию, когда большинство выборок в ней производится по первичному ключу, вы можете задаться вопросом: а на кой черт мне здесь реляционная СУБД? Действительно, все эти статистические выкладки по cardinality индексов, эвристики заменяющие множество lookup'ов на один sequential read нужны были только тогда когда мы не знали какой запрос прийдет от клиента. Теперь мы знаем — это lookup по id. А раз мы знаем, то мы можем написать хранилище не делающее ничего лишнего — KV-storage. &lt;/p&gt;&lt;p&gt;Вот так они и появились. KV-хранилища — это не "серебрянная пуля" и не "RDBMS killer". &lt;em&gt;Это следствие эволюции взглядов на паттерны доступа к данным&lt;/em&gt;. KV-хранилища быстрые лишь потому, что они предоставляют только один способ доступа к данным — &lt;a href="http://en.wikipedia.org/wiki/Hash_table"&gt;lookup по id&lt;/a&gt;. Они быстрые потому, что не обременены, как РСУБД, необходимостью тратить лишнее время на определение оптимального плана выполнения запроса, чтобы сэкономить гораздо больше времени во время выполнения этого запроса.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Фронт NoSQL&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Но современные постреляционные базы данных (если позволите так их назвать) ушли гораздо дальше чем просто lookup по id. Сейчас начинают набирать популярность документо-ориентированные базы данных (&lt;a href="http://couchdb.apache.org/"&gt;CouchDB&lt;/a&gt;, &lt;a href="http://www.mongodb.org/"&gt;MongoDB&lt;/a&gt;), которые предоставляют более сложные способы извлечения и модификации данных. &lt;a href="http://code.google.com/p/redis/"&gt;Некоторые&lt;/a&gt; KV-хранилища умеют нативно работать с коллекциями. Но эти возможности все равно меньше чем у реляционных баз данных.&lt;/p&gt;&lt;p&gt;Весь &lt;a href="http://en.wikipedia.org/wiki/NoSQL"&gt;фронт NoSQL&lt;/a&gt; держится на том, что большинству web-приложений не нужны все возможности реляционной БД. Ну или скажем так, они могут обойтись без всех возможностей реляционной БД в угоду производительности.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Масштабируемость KV-решений&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Очень распространенный стереотип заключается в том, что KV-хранилища очень легко масштабируются. По отношению к некоторым продуктам это утверждение есть ни что иное как подмена понятий.&lt;/p&gt;&lt;p&gt;Задумайтесь, что дает вам, например, &lt;a href="http://memcachedb.org/"&gt;memcachedb&lt;/a&gt; для того чтобы легко масштабироваться? Кто-то из вас может сказать: "Легко. Берем остаток от деления хеша первичного ключа на количество серверов и...". Ну ладно ладно, я понял. Кто-то может вспомнить про &lt;a href="http://www.spiteful.com/2008/03/17/programmers-toolbox-part-3-consistent-hashing/"&gt;consistent hashing&lt;/a&gt;. Отлично. Но дело в том, что это не хранилище дает вам эту возможность partitioning'а, а тот паттерн доступа к данным которым вы пользуетесь. С таким же успехом, я могу легко вместо memcachedb использовать MySQL и утверждать что "MySQL легко масштабируется".&lt;/p&gt;&lt;p&gt;Справедливости ради, надо сказать что некоторые решения (&lt;a href="http://incubator.apache.org/cassandra/"&gt;Cassandra&lt;/a&gt;, &lt;a href="http://project-voldemort.com/"&gt;Project Voldemort&lt;/a&gt;, &lt;a href="http://code.google.com/p/scalaris/"&gt;Scalaris&lt;/a&gt;) сами по себе предоставляют решения для автоматического partitioning'а ключей по нодам кластера. В отношении этих решений утверждение о масштабируемости все же верно.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Выводы&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Суровая реальность научила нас тому, что для того чтобы быстро получать доступ к данным их необходимо хранить в удобном для оперирования над ними виде. Данные и их структура первичны в случае если вы хотите получить максимум производительности. Этому существует &lt;a href="http://research.scee.net/files/presentations/gcapaustralia09/Pitfalls_of_Object_Oriented_Programming_GCAP_09.pdf"&gt;немало&lt;/a&gt; &lt;a href="http://www.catonmat.net/blog/mit-introduction-to-algorithms-part-fourteen/"&gt;подтверждений&lt;/a&gt; (впрочем, пока эти подтверждения находятся в областях не связанных напрямую с web-программированием, поэтому они могут показаться вам безосновательными).&lt;/p&gt;&lt;p&gt;Мой совет web-программистам заключается в том, чтобы они пересматривали паттерны доступа к данным в своих приложениях, и по возможности сводили их к более простым. Это может вам обеспечить необходимый уровень производительности, а позже и масштабируемости. &lt;/p&gt;&lt;p&gt;К KV-хранилищам я бы советовал относится более осторожно. 100K запросов в секунду выглядит конечно заманчиво, но помните, — любая реляционка на подобных запросах ведет себя довольно шустро. Внедрение же еще одного продукта в проект увеличивает его себестоимость владения.&lt;/p&gt;&lt;br class='final-break'  /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-8909321941128675238?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/8909321941128675238/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2010/01/kv.html#comment-form' title='Комментарии: 7'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/8909321941128675238'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/8909321941128675238'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2010/01/kv.html' title='KV-хранилища'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-3605594842666896250</id><published>2009-12-21T01:35:00.002+11:00</published><updated>2009-12-21T11:27:04.110+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='dependency injection'/><category scheme='http://www.blogger.com/atom/ns#' term='spring'/><category scheme='http://www.blogger.com/atom/ns#' term='spring beans'/><category scheme='http://www.blogger.com/atom/ns#' term='ooad'/><category scheme='http://www.blogger.com/atom/ns#' term='ioc-container'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>IoC контейнеры</title><content type='html'>&lt;p style="clear: both"&gt;Если вы пишете на объектно-ориентированном языке, то вы должны быть знакомы с "джентльменским набором" принципов, которые очень полезны при написании кода. К таким принципам относятся: &lt;a href="http://www.objectmentor.com/resources/articles/srp.pdf"&gt;single responsibility principle&lt;/a&gt;, &lt;a href="http://www.objectmentor.com/resources/articles/ocp.pdf"&gt;open-closed principle&lt;/a&gt;, &lt;a href="http://www.objectmentor.com/resources/articles/isp.pdf"&gt;interface segregation principle&lt;/a&gt; и другие. Общий эффект их использования заключается в том, что количество сущностей (классов и интерфейсов) в системе стремительно растет. В этом есть как положительные, так и отрицательные стороны.&lt;/p&gt;&lt;p style="clear: both"&gt;Один из положительных моментов заключается в том, что у каждой сущности появляется строго выделенная роль, для работы которой требуется меньше кода. Такую роль проще протестировать, ее поведение проще понять, а следственно и изменить.&lt;/p&gt;&lt;p style="clear: both"&gt;Но есть в росте количества сущностей и свой негативный момент. Они не могу работать в полной изоляции, поэтому растет количество связей между сущностями. В большом проекте задача управления этими связями может доставить немало головной боли.&lt;/p&gt;&lt;p style="clear: both" class="h2"&gt;Два представления конфигурации&lt;/p&gt;&lt;p style="clear: both"&gt;В правильно построенной объектно-ориентированной системе нет конфигурации. Довольно революционное заявление, верно? Суть в том, что с точки зрения самого кода несущего полезную нагрузку конфигурации не должно существовать. Одни сущности требуют в качестве зависимостей другие (опираясь на интерфейс). Где, кто и как будет инициализировать зависимости, — вопрос не имеющий к пользовательскому коду никакого отношения.&lt;/p&gt;&lt;blockquote style="clear: both"&gt;&lt;p&gt;Я репозиторий пользователей, дайте мне в конструктор подключение к базе данных, и я смогу искать для вас пользователей по идентификатору и логину.&lt;/p&gt;&lt;/blockquote&gt;&lt;p style="clear: both"&gt;Именно так должны вести себя все сущности. Другими словами, они не должны сами заниматься поиском и разрешением своих зависимостей.&lt;/p&gt;&lt;p style="clear: both"&gt;Можно сказать иначе. С точки зрения самой системы во время выполнения, ее &lt;em&gt;конфигурация задается графом объектов&lt;/em&gt;. У нас есть граф объектов по которому туда сюда бегают сообщения. Конечное поведение системы зависит от того как именно сконфигурирован этот граф.&lt;/p&gt;&lt;p style="clear: both"&gt;Это очень удобное описание системы с точки зрения компьютера, но не с точки зрения человека. Человеку нужен какой-то текстовый файл, в котором можно было бы подправить настройки подключения к БД, цвет шрифта, timeout подключения к внешнему сервису и т.д.&lt;/p&gt;&lt;p style="clear: both"&gt;Давайте остановимся на минутку и подумаем что это значит. Мы имеем два способа которые описывают поведение системы: граф объектов и конфигурационные файлы.&lt;/p&gt;&lt;p style="clear: both"&gt;Проблема в том, что для компьютера конфигурационные файлы не имеют смысла, — компьютер не знает что с ними делать. Поэтому нам постоянно приходится писать код конвертирующий одно представление в другое. Нам приходится писать код превращающий конфигурационный файл в граф объектов, который делает всю нужную нам работу.&lt;/p&gt;&lt;p style="clear: both"&gt;Это вам ничего не напоминает? Object-relational impedance, но только в области конфигурирования.&lt;/p&gt;&lt;p style="clear: both" class="h2"&gt;IoC контейнеры&lt;/p&gt;&lt;p style="clear: both"&gt;Возможно вы уже задались вопросом, если системе нужен граф объектов, то может стоит описывать конфигурацию в виде графа объектов? Тогда можно было бы один раз написать транслятор превращающий конфигурацию в тот самый runtime граф, который необходим для работы системы.&lt;/p&gt;&lt;p style="clear: both"&gt;Такими трансляторами и являются IoC контейнеры.&lt;/p&gt;&lt;blockquote style="clear: both"&gt;&lt;p&gt;Здесь я рассматриваю только те IoC контейнеры, которые позволяют описывать конфигурацию отдельно от самого кода. Библиотеки, называющиеся IoC контейнерами, и не выполняющие вышеуказанное требование, таковыми не являются, так как не выполняют самой главной задачи IoC контейнера, — изоляции production кода от механизма локализации и удовлетворения зависимостей.&lt;br /&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p style="clear: both"&gt;У себя в проекте мы написали свой IoC контейнер. Фактически это порт &lt;a href="http://static.springsource.org/spring/docs/2.5.x/reference/beans.html"&gt;Spring beans&lt;/a&gt; на PHP. Согласен, звучит странно. Но spring beans, по моему мнению, идеологически верен, так как он не требует менять код для своего использования.&lt;/p&gt;&lt;p style="clear: both"&gt;Конфигурация в нашем случае описывается примерно следующим образом:&lt;/p&gt;&lt;pre style="clear: both"&gt;&amp;lt;?xml version="1.0" encoding="utf8"?&amp;gt;
&amp;lt;beans xmlns="http://farpost.com/slr/injector"&amp;gt;
  &amp;lt;bean id="masterConnection" class="MySqlConection"&amp;gt;
    &amp;lt;property name="host" value="hostname" /&amp;gt;
    &amp;lt;property name="user" value="john" /&amp;gt;
    &amp;lt;property name="password" value="secret" /&amp;gt;
    &amp;lt;property name="db" value="orders" /&amp;gt;
  &amp;lt;/bean&amp;gt;
    
  &amp;lt;bean id="userRepository" class="SqlUserRepository"&amp;gt;
    &amp;lt;constructor-arg ref="masterConnection" /&amp;gt;
  &amp;lt;/bean&amp;gt;
&amp;lt;/beans&amp;gt;
&lt;/pre&gt;&lt;p style="clear: both"&gt;Фактически, это эквивалентно следующему коду:&lt;/p&gt;&lt;pre style="clear: both"&gt;$masterConnection = new MySqlConnection();
$masterConnection-&amp;gt;setHost("hostname");
$masterConnection-&amp;gt;setUser("john");
$masterConnection-&amp;gt;setPassword("secret");
$masterConnection-&amp;gt;setDb("orders");

$userRepository = new SqlUserRepository($masterConnection);&lt;/pre&gt;&lt;p style="clear: both"&gt;Многословно? Да, наверное. Но это дешевле чем каждый раз писать код создающий объекты на основании конфигурации, когда у вас сотни таких объектов.&lt;/p&gt;&lt;blockquote style="clear: both"&gt;&lt;p&gt;Существует несколько способов, как можно сделать вышеприведенный XML гораздо более лаконичным. Некоторые из них мы уже опробовали и они работают вполне хорошо, некоторые мы только хотим опробовать. Здесь я не буду углубляться в детали. Если вам интерестно, дайте мне знать, быть может это стоит отдельной заметки.&lt;/p&gt;&lt;/blockquote&gt;&lt;p style="clear: both"&gt;Использование IoC контейнеров дает следующие выгоды:&lt;/p&gt;&lt;ul style="clear: both"&gt;&lt;li&gt;единый формат описания конфигурации вне зависимости от подсистемы;&lt;/li&gt;&lt;li&gt;автоматический lazy для всех объектов;&lt;/li&gt;&lt;li&gt;декларативное управление зависимостями, — не требуется менять код для изменения поведения системы;&lt;/li&gt;&lt;li&gt;гибкое управление зависимостями, — в отличии от singleton и toolkit/registry разным клиентам можно подпихивать разные имплементации.&lt;/li&gt;&lt;/ul&gt;&lt;p style="clear: both"&gt;Последний пункт имеет далекоидущие последствия. Очень часто бывает что разным клиентам нужны разные имплементации одного и того же интерфейса. Например, в большинстве случаев клиенты используют различного рода data provider'ы обернутые в кеширующие декораторы. Тем не менее, некоторые клиенты в силу специфики задач могут генерировать большое количество cache miss'ов. Становится разумным просто отключить кеш для этих клиентов. Если ссылка на data provider хранится в singleton'е или реестре (registry), то подменить ее для конкретных клиентов становится просто невозможно.&lt;/p&gt;&lt;p style="clear: both"&gt;Конечно, теперь конфигурация становится сложнее. Но в любой сложной системе конфигурация будет самой сложной ее частью. Я думаю смело можно сказать, что сложность большинства систем определяется сложностью их конфигурации. От этой сложности никуда не деться. Но мы можем обеспечить себя инструментами позволяющими справляться с этой сложностью. IoC контейнер это, по моему мнению, один из таких инструментов.&lt;/p&gt;&lt;p style="clear: both" class="h2"&gt;Стоит ли?&lt;/p&gt;&lt;p style="clear: both"&gt;Решать вам. Идея применения IoC контейнера в скриптовых языках, может показаться абсурдом. Тем не менее, существует рубикон перевалив через который становится понятно, чтос точки зрения сложности эксплуатации разницы между скриптовыми языкам и всеми остальными не существует.&lt;/p&gt;&lt;p style="clear: both"&gt;Поэтому ответ на вопрос "стоит ли?" заключается в вопросе "&lt;em&gt;перевалили ли вы через этот рубикон?&lt;/em&gt;".&lt;/p&gt;&lt;br class='final-break' style='clear: both' /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-3605594842666896250?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/3605594842666896250/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/12/ioc.html#comment-form' title='Комментарии: 12'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/3605594842666896250'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/3605594842666896250'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/12/ioc.html' title='IoC контейнеры'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-3607696969814334537</id><published>2009-12-20T12:58:00.000+11:00</published><updated>2009-12-20T13:01:33.813+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='out-of-memory'/><category scheme='http://www.blogger.com/atom/ns#' term='cache'/><category scheme='http://www.blogger.com/atom/ns#' term='jdk'/><title type='text'>Диагностика OutOfMemoryError подручными средствами</title><content type='html'>&lt;p style="clear: both"&gt;Один мой коллега является адептом философии "дефолтных настроек". Эта философия пропагандирует следующий подход: не пытайтесь менять environment под свои нужды, — просто научитесь пользоваться стандартным environment'ом.&lt;/p&gt;&lt;p style="clear: both"&gt;Несмотря на то что сам по себе этот подход довольно спорен, в нем есть свои плюсы. Умение решать задачи штатными средствами особенно выручает когда необходимо быстро продиагностировать какую-то проблему, а у вас под рукой нет настроенного environment'а. Например, вы временно работаете за другой машиной, или географически отдалены от вашего милого сердцу, прекрасно настроенного environment'а. Поэтому, я считаю, очень важно уметь диагностировать типовые проблемные ситуации пользуясь только штатными утилитами. Так что давайте посмотрим как мы можем диагностировать memory leak'и в java, когда у вас под рукой нет ничего кроме JDK.&lt;/p&gt;&lt;p style="clear: both"&gt;Итак, в логах вы нашли &lt;code&gt;OutOfMemoryError&lt;/code&gt;. Что делать? Во-первых, надо уяснить чего делать не надо. &lt;em&gt;Ни в коем случае не надо перезапускать процесс&lt;/em&gt;. Сделав это вы потеряете весь heap приложения, а в нашем случае heap — это единственная улика, которая может натолкнуть вас на причины OOM. Вам надо сделать heap dump. Это позволит понять кто занимает память, а также почему эта память не была конкретно высвобождена garbage collector'ом.&lt;/p&gt;&lt;p style="clear: both"&gt;Самый простой способ сделать dump — это использовать утилиту jmap из пакета JDK.&lt;/p&gt;&lt;pre style="clear: both"&gt;jmap -dump=format=b,file=heap-dump.hprof $PID&lt;/pre&gt;&lt;p style="clear: both"&gt;Вот теперь, когда вы сделали dump, вы можете смело возвращать систему к жизни и перезапускать JVM, если это требуется.&lt;/p&gt;&lt;p style="clear: both"&gt;Перед доставкой dump'а на вашу машину советую его пережать, так как heap dump'ы очень хорошо жмутся. Когда dump будет на вашей машине возникает вопрос. А каким образом вообще понять что там у "не внутря"? &lt;/p&gt;&lt;p style="clear: both"&gt;С последними версиями JDK поставляется приложение &lt;a href="https://visualvm.dev.java.net/"&gt;VisualVM&lt;/a&gt;, которое содержит в себе в том числе и memory profiler. Загружаем heap dump в VisualVM и открываем вкладку "Classes".&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;a href="http://lh5.ggpht.com/_olxzclEPtBk/Sy10CLL7TLI/AAAAAAAAALg/nFOtbaNZVCM/s800/Screen_shot_2009-12-19_at_22-full.33.png" class="image-link"&gt;&lt;img class="linked-to-original" src="http://lh3.ggpht.com/_olxzclEPtBk/Sy10BiYXgOI/AAAAAAAAALc/tyt18MPrl64/s800/Screen_shot_2009-12-19_at_22-thumb.33.png" height="126" width="377" style="  text-align: center; display: block; margin: 0 auto 10px;" /&gt;&lt;/a&gt;На этой вкладе мы видим распределение памяти в dump'е по классам объектов. В данном случае большего всего памяти занимает тип &lt;code&gt;char[]&lt;/code&gt;. Видимо кто-то хранит много строчек в памяти. Кто же это?&lt;/p&gt;&lt;p style="clear: both"&gt;Дважды щелкаем на типе и переходим в instance view. Здесь мы видим все экземпляры данного типа, а также кто на них ссылается, а следственно и то, почему GC их не собрал. Просматриваем несколько экземпляров.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;a href="http://lh6.ggpht.com/_olxzclEPtBk/Sy14amUC0AI/AAAAAAAAAMQ/Ae4LzTLDGkY/s800/Screen_shot_2009-12-20_at_10-full.54.png" class="image-link"&gt;&lt;img class="linked-to-original" src="http://lh6.ggpht.com/_olxzclEPtBk/Sy14aMWEKwI/AAAAAAAAAMM/3LHlTacR73A/s800/Screen_shot_2009-12-20_at_10-thumb.54.png" height="132" width="380" style=" text-align: center; display: block; margin: 0 auto 10px;" /&gt;&lt;/a&gt;В моем случае большинство ссылок на строку удерживается базой данных &lt;a href="http://www.h2database.com/html/main.html"&gt;H2&lt;/a&gt; при помощи soft reference. Немного погуглив можно узнать, что H2 использует soft reference для хранения кеша базы данных. Отличительной особенностью soft ссылок является то, что JVM собирает их только тогда, когда ей не хватает памяти (перед генерацией OOM). Это делает soft ссылки довольно удобным механизмом для различного рода кешей. Тем не менее JVM не гарантирует что она успеет собрать все soft ссылки перед генерацией exception'а. Что, судя по всему, и происходит в моем случае.&lt;/p&gt;&lt;p style="clear: both"&gt;Также стоит отметить, что VisualVM может сам находить ближайших GC root, удерживающий данный instance от garbage collector'а.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;a href="http://lh4.ggpht.com/_olxzclEPtBk/Sy14bRcVHLI/AAAAAAAAAMY/R4ond6tisuo/s800/Screen_shot_2009-12-20_at_11-full.03.png" class="image-link"&gt;&lt;img class="linked-to-original" src="http://lh4.ggpht.com/_olxzclEPtBk/Sy14bDOSE-I/AAAAAAAAAMU/D1L_Uz2lURU/s800/Screen_shot_2009-12-20_at_11-thumb.03.png" height="128" width="336" style=" text-align: center; display: block; margin: 0 auto 10px;" /&gt;&lt;/a&gt;Это избавляет вас от необходимости сайгаком прыгать по дереву referent'ов в поисках ближайшего GC root'а.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;strong&gt;Auto dump&lt;/strong&gt;&lt;/p&gt;&lt;p style="clear: both"&gt;Иногда бывает так, что причиной &lt;code&gt;OutOfMemoryException&lt;/code&gt; служит не нехватка памяти, а другие причины. Например, если JVM видит, что она тратит большую часть процессорного времени на сборку мусора, а не на выполнение собственно приложения, она генерирует следующий exception.&lt;/p&gt;&lt;pre style="clear: both"&gt;java.lang.OutOfMemoryError: GC overhead limit exceeded&lt;/pre&gt;&lt;p style="clear: both"&gt;В зависимости от того как написано приложение оно может остаться живо, и даже продолжать выполнять свои функции. Причем уже через несколько минут heap может быть чистенький и без лишнего мусора (GC не зря жрал так много процессорного времени, и в конце концов собрал весь мусор).&lt;/p&gt;&lt;p style="clear: both"&gt;Отладка затрудняется, — у вас нет heap'а, хоть на кофейной гуще гадай. В этом случае, стоит перезапустить приложение с ключом &lt;code&gt;-XX:-HeapDumpOnOutOfMemoryError&lt;/code&gt;. Это заставит JVM сделать heap dump автоматически перед тем как кидать в бедное приложение OOM'ом. После следующего подобного инцидента у вас появится пища для размышлений.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;strong&gt;Runtime статистика&lt;/strong&gt;&lt;/p&gt;&lt;p style="clear: both"&gt;Часто бывает так, что у программиста появляется теория относительно того, почему возникает memory leak. Например, зная список последних изменений кодовой базы, можно предположить что проблема локализована в каком-то конкретном участке системы. В этом случае, вам снова может помочь jmap. Эта утилита позволяет просмотреть количество экземпляров и занимаемую ими память по типам.&lt;/p&gt;&lt;pre style="clear: both"&gt;bazhenov@home core$ jmap -histo 11507 | egrep "(#instances|---|java.util.LinkedList)"
 num     #instances         #bytes  class name
----------------------------------------------
 110:           506          20240  java.util.LinkedList$Entry
 166:           220           8800  java.util.LinkedList
1021:             5            240  java.util.LinkedList$ListItr&lt;/pre&gt;&lt;p style="clear: both"&gt;Вы также можете легко посчитать суммарную память занимаемую типами определенного пакета.&lt;/p&gt;&lt;pre style="clear: both"&gt;bazhenov@home core$ jmap -histo 11507 | grep "org.netbeans" | awk '{SUM += $3} END {print SUM/1024 "K"}'
1745.28K&lt;/pre&gt;&lt;p style="clear: both"&gt;Да пребудет с вами сила дефолтных настроек.&lt;/p&gt;&lt;br class='final-break' style='clear: both' /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-3607696969814334537?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/3607696969814334537/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/12/outofmemoryerror.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/3607696969814334537'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/3607696969814334537'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/12/outofmemoryerror.html' title='Диагностика OutOfMemoryError подручными средствами'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_olxzclEPtBk/Sy10BiYXgOI/AAAAAAAAALc/tyt18MPrl64/s72-c/Screen_shot_2009-12-19_at_22-thumb.33.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-6414788279525686986</id><published>2009-12-06T20:46:00.006+11:00</published><updated>2009-12-06T21:29:08.487+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='activemq'/><category scheme='http://www.blogger.com/atom/ns#' term='message ordering'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='asynchronous processing'/><category scheme='http://www.blogger.com/atom/ns#' term='scalability'/><category scheme='http://www.blogger.com/atom/ns#' term='messaging'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>Гарантия доставки сообщений и ее последствия</title><content type='html'>&lt;p&gt;В &lt;a href="http://dotsid.blogspot.com/2009/11/blog-post.html"&gt;прошлой заметке&lt;/a&gt;, я затронул тему порядка доставки сообщений MQ-системами. Отсутствие гарантий в отношении этого порядка вызвало некоторое возмущение со стороны читателей, поэтому я решил раскрыть эту тему более полно.&lt;/p&gt;&lt;p&gt;Почему же многие очереди сообщений не гарантирую порядок? И так ли он вообще важен — этот порядок доставки?&lt;/p&gt;&lt;h2&gt;Почему многие системы очередей не гарантируют порядок доставки?&lt;/h2&gt;&lt;p&gt;Здесь потребуется договорится об обозначениях. Для любых двух событий &lt;span class="monospace"&gt;A&lt;/span&gt; и &lt;span class="monospace"&gt;B&lt;/span&gt; запись “&lt;span class="monospace"&gt;A→B&lt;/span&gt;” означает, что событие &lt;span class="monospace"&gt;A&lt;/span&gt; происходит перед событием &lt;span class="monospace"&gt;B&lt;/span&gt;. Людям знакомым с &lt;a href='http://en.wikipedia.org/wiki/Java_Memory_Model'&gt;Java Memory Model&lt;/a&gt; эта запись должна быть известна как отношение happens-before. &lt;/p&gt;&lt;p&gt;Давайте представим себе очередь сообщений, которая гарантирует вышеупомянутый порядок и двух подписчиков читающих сообщения из этой очереди.&lt;/p&gt;&lt;img alt="image #1" src="http://lh6.ggpht.com/_olxzclEPtBk/Sxt9M-UHRZI/AAAAAAAAAIw/ABylwNGlm_k/dispatch-in-order.png" /&gt;&lt;p&gt;Исходя из этого мы можем определить порядок доставки сообщений следующим образом. Для двух любых сообщений &lt;span class="monospace"&gt;m1&lt;/span&gt; и &lt;span class="monospace"&gt;m2&lt;/span&gt; для которых выполняется условие &lt;span class="monospace"&gt;receive(m1)→receive(m2)&lt;/span&gt; (то есть брокер получил сначала сообщение &lt;span class="monospace"&gt;m1&lt;/span&gt; потом &lt;span class="monospace"&gt;m2&lt;/span&gt;), если гарантируется что &lt;span class="monospace"&gt;dispatch(m1)→dispatch(m2)&lt;/span&gt;, значит очередь сообщений гарантирует порядок доставки.&lt;/p&gt;&lt;p&gt;Тем не менее гарантия в отношении порядка доставки мало что дает. Даже если сообщения доставлены в порядке (in-order), consumer’ы могут обработать сообщения в порядке отличном от порядка доставки (out-of-order).&lt;/p&gt;&lt;img alt="image #2" src="http://lh6.ggpht.com/_olxzclEPtBk/Sxt9M3grOjI/AAAAAAAAAI0/2q80x0GbSgo/dsipatch-out-of-order.png" /&gt;&lt;p&gt;Как любит говорить один мой коллега: “Тому есть тысяча причин”. Машины на которых работают consumer’ы могут быть разной конфигурации: у них могут быть разные процессоры, разный объем памяти, разная по производительности подсистема I/O. Но даже если они идентичны, у вас нет контроля над детерминизмом cpu и I/O scheduler’ов. Любая машина может отказать или начать медленно работать из-за большого количества pagefault’ов и т.д. Все это говорит о том, что порядок доставки сообщений не имеет ничего общего с порядком их обработки. А ведь именно порядок обработки, а не доставки должен интересовать нас в первую очередь.&lt;/p&gt;&lt;p&gt;Порядок обработки сообщений мы можем определить следующим образом. Для двух любых сообщений &lt;span class="monospace"&gt;m1&lt;/span&gt; и &lt;span class="monospace"&gt;m2&lt;/span&gt; для которых выполняется условие &lt;span class="monospace"&gt;receive(m1)→receive(m2)&lt;/span&gt;, если гарантируется что &lt;span class="monospace"&gt;ack(m1)→ack(m2)&lt;/span&gt;, значит очередь сообщений гарантирует порядок обработки.&lt;/p&gt;&lt;p&gt;Но соблюсти это правило очередь сообщений может только одним способом. Путем форсирования порядка &lt;span class="monospace"&gt;ack(m1)→dispatch(m2)&lt;/span&gt;. Другими словами, брокер не должен отправлять следующее сообщение пока предыдущее не будет обработано. Это подразумевает следующую картину.&lt;/p&gt;&lt;img alt="image #3" src="http://lh6.ggpht.com/_olxzclEPtBk/Sxt9NBueMzI/AAAAAAAAAI4/Voplo4G7GAY/process-in-order.png" /&gt;&lt;p&gt;Не тяжело догадаться, что в любой момент времени в обработке будет находится только одно сообщение. В этом случае, пропускная способность кластера из десяти машин будет равна пропускной способности одной машины. Можете забыть про горизонтальное масштабирование.&lt;/p&gt;&lt;p&gt;Получается что порядок доставки сам по себе ничего не значит, — важен порядок обработки, предоставление гарантий в отношении которого убивает на корню пропускную способность.&lt;/p&gt;&lt;h2&gt;А важен ли порядок обработки?&lt;/h2&gt;&lt;p&gt;Иногда да. Есть ситуации когда порядок обработки очень важен. Предыдущая заметка иллюстрирует один из таких случаев. Но вы должны понимать, что любой порядок в web-приложении является имитацией. В любом web-приложении клиенты посылают запросы параллельно. Эти запросы параллельно идут по сети, параллельно обрабатываются frontend’ами, параллельно идут на backend’ы и так же параллельно ответы идут обратно к пользователям.&lt;/p&gt;&lt;p&gt;Место где порядок обработки начинает проявляться — это реляционная база данных. Если два потока в транзакции модифицируют одни и те же кортежи, база данных останавливает выполнение одного из потоков до тех пор пока другой не завершит свою работу. &lt;i&gt;База данных линеаризует выполнение нескольких потоков таким образом, что в системе появляется порядок обработки&lt;/i&gt;. Но вместе с появлением порядка обработки &lt;i&gt;испаряется пропускная способность&lt;/i&gt;. К тому же тот порядок который навязывает база данных может не совпадать с порядком поступления запросов на frontend. Для двух любых запросов &lt;span class="monospace"&gt;req1&lt;/span&gt; и &lt;span class="monospace"&gt;req2&lt;/span&gt; связанных порядком &lt;span class="monospace"&gt;receive(req1)→receive(req2)&lt;/span&gt; соблюдение правила &lt;span class="monospace"&gt;transaction(req1)→transaction(req2)&lt;/span&gt; не гарантируется.&lt;/p&gt;&lt;p&gt;В свою очередь, это означает что сложность, которая присуща решению продемонстрированному в &lt;a href="http://dotsid.blogspot.com/2009/11/blog-post.html"&gt;предыдущей заметке&lt;/a&gt;, не является сложностью присущей асинхронным системам обработки сообщений. Это сложность присущая web-приложениям в целом. И даже если бы вы исключили MQ-систему из транка обработки и, скажем, посылали бы сообщения через SOAP или напрямую писали в базу данных, вам все равно пришлось бы реализовывать те же механизмы, чтобы обеспечить сохранность данных.&lt;/p&gt;&lt;p&gt;&lt;i&gt;Любое web-приложение — это конкурентная система в природе которой порядок обработки запросов отсутствует.&lt;/i&gt;&lt;/p&gt;&lt;p&gt;Некоторых людей такая ситуация не устраивает. Отчасти являясь идеалистами (что особенно характерно для программистов) они не могут ужиться с мыслью, что те или иные процессы протекают стохастично и не имеют порядка в своей природе. Попытки навязать этот порядок обрекают web-приложение на деградацию пропускной способности. Вы можете наблюдать этот синдром повсюду — начиная от многопоточного программировния и использования mutex’ов до баз данных и протоколов XA-транзакций. В свое время Dan Pritchett написал об этом отличное эссе — “&lt;a href="http://www.addsimplicity.com/adding_simplicity_an_engi/2007/05/chaotic_perspec.html"&gt;Chaotic Perspectives&lt;/a&gt;”.&lt;/p&gt;&lt;p&gt;Если вы web-программист, то я вас поздравляю. Судьба сделала вам подарок. В других отраслях программистам приходится прилагать титанические усилия для распараллеливания задач. У вас же б&lt;i&gt;о&lt;/i&gt;льшая часть процессов протекает и так параллельно. Все что вам надо, — смирится с мыслью, что порядок обработки запросов в системе не детерминистичен. С этим бессмысленно бороться, &lt;i&gt;этим надо уметь пользоваться&lt;/i&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-6414788279525686986?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/6414788279525686986/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/12/blog-post.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/6414788279525686986'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/6414788279525686986'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/12/blog-post.html' title='Гарантия доставки сообщений и ее последствия'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_olxzclEPtBk/Sxt9M-UHRZI/AAAAAAAAAIw/ABylwNGlm_k/s72-c/dispatch-in-order.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-7359869470371292073</id><published>2009-11-04T23:35:00.007+11:00</published><updated>2009-11-05T09:47:15.825+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='activemq'/><category scheme='http://www.blogger.com/atom/ns#' term='asynchronous processing'/><category scheme='http://www.blogger.com/atom/ns#' term='messaging'/><title type='text'>Прикладное применение оптимистической блокировки</title><content type='html'>&lt;p&gt;В прошлый раз я писал про &lt;a href="http://dotsid.blogspot.com/2009/10/blog-post.html"&gt;оптимистическую блокировку&lt;/a&gt;. Сегодня я хочу описать одну разновидность прикладного применения оптимистической блокировки.&lt;/p&gt;&lt;p&gt;Это прикладное применение относится к области систем асинхронного обмена сообщений (таких как ActiveMQ, RabbitMQ, OpenMQ, memcacheQ и другие). Очереди сообщений — это очень полезная разновидность промежуточного хранилища данных. В очередь можно положить сообщение и можно из нее взять сообщение. MQ системы позволяют частично решить проблему именуемую "&lt;a href="http://en.wikipedia.org/wiki/Producer-consumer_problem"&gt;Producer—Consumer problem&lt;/a&gt;". Кто-то ложит, а кто-то читает, — все просто. Вообще, подобную систему можно реализовать и поверх вашей любимой реляционной базы данных. Причина, почему существуют очереди сообщений как отдельный тип middleware, заключается в эффективности их реализации. Когда вы знаете, что все что может делать клиент — это писать в хвост и читать с головы, вы можете написать действительно эффективную реализацию с очень большой пропускной способностью. &lt;span style="color: gray"&gt;Предыдущее предложение метафорично. Я надеюсь, вы не будете пытаться написать свою mq-систему.&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Однако, у MQ систем есть одна особенность. Они не гарантируют порядок доставки. Это значит что вы можете положить в очередь сообщение А, затем Б, а из очереди вылетит сначала Б, а затем А. Я не буду вдаваться в технические подробности почему так происходит. Гарантия порядка доставки и его последствия для приложения, — тема отдельная.&lt;/p&gt;&lt;p&gt;Тем не менее, порядок следования сообщений иногда бывает важен. Настолько важен, что его нарушение может привести к потери целостности данных. Представьте себе такой сценарий, вы хотите написать сервис, который позволит собирать, хранить и просматривать историю изменения каких-либо объектов в системе. Возьмем, скажем, объявления. Объявления меняются и мы хотим иметь историю того когда и как именно они менялись с течением времени. Мы в проекте пришли к схеме, в которой сервис через очередь сообщений принимает сообщения следующего формата:&lt;/p&gt;&lt;pre&gt;
&amp;lt;?xml version=“1.0” encoding=“UTF-8”?&amp;gt;
&amp;lt;state time=“2009-11-02T12:01:00+11:00” revision="15"&amp;gt;
  &amp;lt;object type=“bulletin” id=“3461432” /&amp;gt;
  &amp;lt;attributes&amp;gt;
    &amp;lt;attribute name=“subject” type=“string”&gt;Продам автомагнитолу&amp;lt;/attribute&amp;gt;
    &amp;lt;attribute name=“ownerId” type=“integer” value=“15” /&amp;gt;
  &amp;lt;/attributes&amp;gt;
&amp;lt;/state&amp;gt;
&lt;/pre&gt;&lt;p&gt;В данном XML сообщении содержится информация об объекте и о его состоянии. Сервис аккумулирует все эти изменения и позже позволяет просмотреть когда и как менялось состояние отдельно взятого объявления.&lt;/p&gt;&lt;p&gt;Если вы будете реализовывать нечто подобное, то скорее всего, вы очень быстро придете к тому, что надо хранить дельты между состояниями, иначе в вашей БД будет очень много дублирующихся данных, а ее размер будет очень быстро расти.&lt;/p&gt;&lt;p&gt;Но вы не можете отправлять на ваш сервис дельты, так как очередь не гарантирует порядок доставки. Дельты могут прийти в другом порядке, и тогда вы получите некорректную историю изменений. И вот тут то нам может помочь тот самый счетчик, который мы добавили для реализации оптимистической блокировки. Этот счетчик, являясь уникальным, монотонно возрастающим номером ревизии, позволяет восстановить порядок посылки сообщений клиентом.&lt;/p&gt;&lt;p&gt;Общая схема работы выглядит следующим образом. Клиенты всегда посылают в сервис полное состояние объекта на момент изменения (snapshot). Сервис принимая сообщение проверяет, есть ли в БД  запись состояния для предыдущей ревизии. Если есть, то мы можем посчитать дельту и записать только ее. Если предыдущего состояния нет, мы пишем в БД полное состояние, ожидая, что предыдущее состояние поспеет позже (или это первая запись для этого объекта). Даже если предыдущее состояние  не дойдет до сервиса мы не потеряем всю последующую историю. Более того, мы будем знать, что история по этому объекту неполная.&lt;/p&gt;
&lt;div align="center"&gt;&lt;img src="http://lh5.ggpht.com/_olxzclEPtBk/SvFwXmTB6sI/AAAAAAAAAIQ/zSKZDVHhas4/Screen%20shot%202009-11-04%20at%2021.40.png" /&gt;&lt;/div&gt;&lt;p&gt;Также, если сервис получает сообщение содержащие состояние объекта с ревизией ß, то следует проверить есть ли в хранилище состояние ß+1. Если так, то можно его сократить.&lt;/p&gt;
&lt;div align="center"&gt;&lt;img src="http://lh4.ggpht.com/_olxzclEPtBk/SvFwXgUADcI/AAAAAAAAAIU/hDIwylJcDM8/Screen%20shot%202009-11-04%20at%2022.14.png" /&gt;&lt;/div&gt;&lt;p&gt;Вот такое вот прикладное применение оптимистической блокировки. Буду рад услышать ваши комментарии по этому поводу. Может кто-то использует что-то подобное?&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-7359869470371292073?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/7359869470371292073/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/11/blog-post.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/7359869470371292073'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/7359869470371292073'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/11/blog-post.html' title='Прикладное применение оптимистической блокировки'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_olxzclEPtBk/SvFwXmTB6sI/AAAAAAAAAIQ/zSKZDVHhas4/s72-c/Screen%20shot%202009-11-04%20at%2021.40.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-9211116921693904463</id><published>2009-10-23T23:14:00.008+12:00</published><updated>2009-10-24T14:59:38.362+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='ooad'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>Оптимистическая блокировка</title><content type='html'>&lt;p&gt;Shared state, как известно необходимо защищать. Иначе параллельные потоки могут его "поломать". Это относится и к web-приложениям. Несмотря на отсутствие вменяемой поддержки параллелизма в большинстве web-ориентированных языков (PHP, Python, Ruby), concurrency в web-приложениях хватает. Запросы приходят на web-сервер параллельно, исполняются на разных процессорах параллельно и т.д.
По этой причине следующий код некорректен:&lt;/p&gt;&lt;pre&gt;$bulletin = $manager-&gt;findBulletinById($request-&gt;id);
$bulletin-&gt;setHits( $bulletin-&gt;getHits()+1 );
$manager-&gt;saveBulletin($bulletin);&lt;/pre&gt;&lt;p&gt;Два параллельных потока могут одновременно загрузить объявление, одновременно инкрементировать счетчик, и записать объявление обратно. Это приведет к тому, что один инкремент потеряется. В общем случае, если &lt;i&gt;N&lt;/i&gt; потоков выполняют данный код одновременно, может быть потеряно до &lt;i&gt;N - 1&lt;/i&gt; update'ов.&lt;/p&gt;&lt;p&gt;Существует два принципиально разных способа обеспечения целостности данных: пессимистическая и &lt;a href="http://en.wikipedia.org/wiki/Optimistic_concurrency_control"&gt;оптимистическая блокировка&lt;/a&gt;. Пессимистическая блокировка исходит из предположения, что если мы выполняем код, конкурентное выполнения которого может  привести к "поломке" данных, то необходимо исключить его конкурентное исполнение. То есть сериализовать потоки в этой точке. Достигается это или при помощи distributed lock'ов или транзакций в БД.&lt;/p&gt;&lt;p&gt;У нас в системе есть небольшая библиотека позволяющая реализовать в коде исключающую блокировку. Ее использование выглядит примерно следующим образом:&lt;/p&gt;&lt;pre&gt;$lock = LockManager::getLock("bulletin:{$request-&gt;id}");
try {
  $bulletin = $manager-&gt;findBulletinById($request-&gt;id);
  $bulletin-&gt;setHits( $bulletin-&gt;getHits()+1 );
  $manager-&gt;saveBulletin($bulletin);
  $lock-&gt;release();
} catch ( Exception $e ) {
  $lock-&gt;release();
}&lt;/pre&gt;&lt;p&gt;Пессимистическая блокировка схожа с принципом Мерфи. Она предполагает, что если что-то плохое может случится, это обязательно случится. В отличии от пессимистической, оптимистическая блокировка предполагает что во время обновления записи в БД мы будем единственными кто ее меняет. В большинстве случаев, так и есть, так что оптимизм оправдан. Тем не менее, во время UPDATE'а мы проверяем наверняка изменилась ли запись с момента ее чтения. И если изменилась, то мы обязаны прочитать последнюю версию записи из БД и повторить нашу операцию с ней.&lt;/p&gt;&lt;h2&gt;Реализация&lt;/h2&gt;&lt;p&gt;Реализуется это довольно просто. Достаточно хранить с каждой записью в БД идентификатор версии и при записи проверять что он не изменился и менять его. Алгоритм выглядит следующим образом.&lt;/p&gt;&lt;pre&gt;public function saveBulletin(Bulletin $bulletin) {
  $connection-&gt;prepareStatement("UPDATE bulletins SET version = version + 1 ... ".
    "WHERE id = :id AND version = :version")
  -&gt;int('id', $bulletin-&gt;getId())
  -&gt;int('version', $bulletin-&gt;getVersion())
  -&gt;execute();
  if ( $connection-&gt;rowsAffected() &lt;= 0 ) {
    throw new ConcurrentModificationException();
  }
}&lt;/pre&gt;&lt;p&gt;В данном методе при обновлении мы проверяем, что версия не изменилась, а это значит, что и запись в БД никто не менял. Если версия изменилась, мы обязаны известить об этом клиента.&lt;/p&gt;&lt;p&gt;Но тут есть одна загвоздка. Что будет делать клиент с этим exception'ом?&lt;/p&gt;&lt;pre&gt;try {
  $manager-&gt;saveBulletin($bulletin);
} catch ( ConcurrentModificationException $e ) {
  // Huh?!
}&lt;/pre&gt;&lt;p&gt;По идее, клиент должен заново прочитать объявление из БД, заново выполнить свою операцию и заново сохранить объявление. И вполне возможно что... заново получить exception, заново прочитать объявление и... Нет, так не пойдет.&lt;/p&gt;&lt;p&gt;В случае, если вы реализуете оптимистическую блокировку, то модель должна взять на себя логику сохранения объектов, иначе &lt;i&gt;вы опухнете писать клиентов&lt;/i&gt;. Модель должна предоставить иной, более удобный интерфейс для оперирования над объектами. Используя возможности PHP/5.3 можно сделать следующее:&lt;/p&gt;&lt;pre&gt;$manager-&gt;processBulletin($request-&gt;id, function(Bulletin $b) {
  $b-&gt;setHits( $b-&gt;getHits()+1 );
});&lt;/pre&gt;&lt;p&gt;Как видно, в данном случае клиент не загружает и не сохраняет объявление. А это значит, что и с конкурентными изменениями ему иметь дело не требуется, — все это ответственность модели. Реализовать в модели цикл сохранения объекта с обработкой ошибок — дело техники.&lt;/p&gt;&lt;h2&gt;Преимущества "оптимистов над пессимистами"&lt;/h2&gt;&lt;h3&gt;Не блокирует клиентов, которые не меняют состояние&lt;/h3&gt;&lt;p&gt;Представьте себе такой код:&lt;/p&gt;&lt;pre&gt;$bulletin = $manager-&gt;findBulletinById(...);
if ( $bulletin-&gt;getText() != $request-&gt;text ) {
  $bulletin-&gt;setText($request-&gt;text);
  $manager-&gt;saveBulletin($bulletin);
}&lt;/pre&gt;&lt;p&gt;В случае пессимистической блокировки мы обязаны взять lock перед загрузкой объявления. Но возможно, что нам даже менять его не потребуется. Тем не менее, мы потенциально можем быть заблокированы на lock'е, даже если ничего не будем менять. В случае оптимистической блокировки мы просто не сохраняем объявление.&lt;/p&gt;&lt;h3&gt;Избавляет клиента от необходимости заботится о lock'ах&lt;/h3&gt;&lt;p&gt;В случае оптимистической блокировки клиентский код &lt;i&gt;проще&lt;/i&gt; и этого кода требуется &lt;i&gt;меньше&lt;/i&gt;. Меньше кода → меньше проблем.&lt;/p&gt;&lt;h3&gt;Гарантировано защищает данные&lt;/h3&gt;&lt;p&gt;Когды вы оперируете lock'ами могут случиться четыре типа проблем:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;вы возьмете слишком мало локов (поломанные данные);&lt;/li&gt;&lt;li&gt;вы возьмете слишком много локов (&lt;a href="http://en.wikipedia.org/wiki/Deadlock"&gt;deadlock&lt;/a&gt;, &lt;a href="http://en.wikipedia.org/wiki/Resource_starvation"&gt;starvation&lt;/a&gt;);&lt;/li&gt;&lt;li&gt;вы возьмете не те локи (поломанные данные);&lt;/li&gt;&lt;li&gt;вы возьмете те локи, но не в том порядке (deadlock).&lt;/li&gt;&lt;/ul&gt;Если вы хотите чтобы пессимистическая блокировка корректно работала в больших системах, требуется чтобы программисты, которые пишут клиентский код, четко осознавали суть конкурентных процессов, были очень внимательны и чтобы у них было 5 килограммов мозга. Скорее всего у них, как и у большинства других нормальных людей, мозг весит только 3 килограмма. Так что не стоит спихивать на них задачу модели, а именно — обеспечение целостности данных.
&lt;p&gt;Худшее что может случится в случае оптимистической блокировки — клиент получит exception. Худшее что может случится в случае пессимистической блокировки — вы &lt;i&gt;"поломаете" данные&lt;/i&gt;.&lt;/p&gt;&lt;p&gt;Что вы выбираете?&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-9211116921693904463?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/9211116921693904463/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/10/blog-post.html#comment-form' title='Комментарии: 4'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/9211116921693904463'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/9211116921693904463'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/10/blog-post.html' title='Оптимистическая блокировка'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-7049121488121175814</id><published>2009-09-04T00:54:00.001+12:00</published><updated>2010-02-02T11:20:47.729+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='multi-threading'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='interruption'/><title type='text'>Interrupted exception</title><content type='html'>&lt;p style="clear: both"&gt;Недавно еще раз встретился с некорректной обработкой &lt;a href="http://java.sun.com/javase/6/docs/api/java/lang/InterruptedException.html"&gt;InterruptedException&lt;/a&gt; в java. InterruptedException — это checked exception генерируемый многими методами стандартной библиотеки, которые блокируют поток исполнения. К таким относятся: &lt;a href="http://java.sun.com/javase/6/docs/api/java/util/concurrent/locks/Lock.html#lockInterruptibly()"&gt;interruptible версии lock'ов&lt;/a&gt;, метод &lt;a href="http://java.sun.com/javase/6/docs/api/java/lang/Thread.html#sleep(long)"&gt;Thread.sleep()&lt;/a&gt;, некоторые &lt;a href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/BlockingQueue.html#take()"&gt;операции над блокирующими очередями&lt;/a&gt;, некоторые &lt;a href="http://java.sun.com/javase/6/docs/api/java/nio/channels/SocketChannel.html#open(java.net.SocketAddress)"&gt;операции над каналами&lt;/a&gt; и другие.&lt;/p&gt;&lt;p style="clear: both"&gt;По своей сути InterruptedException сигнализирует о том, что поток просят завершить его работу. При этом вас не просят &lt;em&gt;немедленно&lt;/em&gt; завершить свою работу. Вас просят &lt;em&gt;корректно&lt;/em&gt; завершить работу. На это может понадобится некоторое время.&lt;/p&gt;&lt;p style="clear: both"&gt;Прерывание потока осуществляется при помощи метода &lt;a href="http://java.sun.com/javase/6/docs/api/java/lang/Thread.html#interrupt()"&gt;Thread.interrupt()&lt;/a&gt;. Существует два способа которыми JVM уведомляет поток о том, что его прерывают. Первый — это собственно InterruptedException. Второй — это флаг потока INTERRUPT, который может быть получен при помощи метода Thread.isInterrupted(). Игнорирование второго метода сигнализирования о прерывании и является типичной ошибкой.&lt;/p&gt;&lt;p style="clear: both"&gt;Важно понимать, что interrupt адресуется не только вам (коду, который выполняется в потоке), но и владельцу потока. Другими словами, не только вы в этом потоке заинтересованы в том, чтобы определить что наступило прерывание.&lt;/p&gt;&lt;pre style="clear: both"&gt;try {
  Object o = queue.take();
} catch ( InterruptedException e ) {}&lt;/pre&gt;&lt;p style="clear: both"&gt;Данный код неверен, потому что он "давит" сигнал прерывания. Если этот код выполняется в thread pool'е, то на этом таске worker (который вызвал вас) должен был бы завершить исполнение задач, так как поток получил прерывание. Но этого не произойдет потому что сигнал прерывания был перехвачен вышеприведенным кодом. Как правило, это выражается в том, что после посылки kill [pid] java машине у нее остаются висеть потоки worker'ов из-за чего jvm не может завершить свою работу. В данном случае единственный выход — kill -9.&lt;/p&gt;&lt;p style="clear: both"&gt;Следующий код так же некорректен, потому что мы "маскируем" interrupt в исключительную ситуацию другого типа. Тем самым он не дает возможности вызывающей стороне зафиксировать ситуацию прерывания.&lt;/p&gt;&lt;pre style="clear: both"&gt;try { 
  Object o = queue.take();
} catch ( InterruptedException e ) {
  throw new RuntimeException(e);
}&lt;/pre&gt;&lt;p style="clear: both"&gt;Корректный код будет выглядеть следующим образом.&lt;/p&gt;&lt;pre style="clear: both"&gt;try {
  Object o = queue.take();
} catch ( InterruptedException e ) {
  Thread.currentThread().interrupt();
}&lt;/pre&gt;&lt;p style="clear: both"&gt;Здесь мы ловим InterruptedException и выставляем флаг потока сигнализирующий о прерывании.&lt;/p&gt;&lt;p style="clear: both"&gt;Политика прерывания метода является частью его контракта. Если вы декларируете что InterruptedException &lt;strong&gt;не может&lt;/strong&gt; произойти во время выполнения метода, но сами при этом вызываете методы генерирующие эту исключительную ситуацию, то вы должны корректно конвертировать InterruptedException во флаг потока о прерывании. То же самое относится и к вызывающей стороне. Если вы вызываете метод, который по контракту генерирует InterruptedException, то вы должны ожидать что метод будет уведомлять вас о прерывании посредством исключительной ситуации. Если нет, то посредством флага.&lt;/p&gt;&lt;p style="clear: both"&gt;Внимательный читатель мог задать себе вопрос: "А зачем два способа сигнализации? Разве нельзя обойтись одним?".&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;strong&gt;Зачем нужен Thread.isInterrupted()?&lt;/strong&gt;&lt;/p&gt;&lt;p style="clear: both"&gt;InterruptedException бывает неудобен по причине того, что это checked exception. Это значит что компилятор заставляет вас или обработать его по месту, или указать в своем контракте. В случае, если вы не можете указать InterruptedException в своем контракте (например, вы имплементируете интерфейс где в контракте InterruptedException не указан), флаг потока остается единственным способом передать вызывающей стороне информацию о прерывании.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;strong&gt;Зачем нужен InterruptedException?&lt;/strong&gt;&lt;/p&gt;&lt;p style="clear: both"&gt;InterruptedException позволяет прервать поток уже выполняющий блокирующий вызов. В случае, если метод уже выполняется, то существует только один способ прервать его выполнение без возврата какого-либо значения и не нарушая при этом его контракт, — сгенерировать исключительную ситуацию. В этом случае возвращаемое значение метода просто неопределено.&lt;/p&gt;&lt;br class='final-break' style='clear: both' /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-7049121488121175814?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/7049121488121175814/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/09/interrupted-exception.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/7049121488121175814'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/7049121488121175814'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/09/interrupted-exception.html' title='Interrupted exception'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-975020848775062705</id><published>2009-08-24T22:55:00.000+12:00</published><updated>2009-10-16T23:52:57.401+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='memcache'/><category scheme='http://www.blogger.com/atom/ns#' term='cache'/><title type='text'>Что если цепь рванет?</title><content type='html'>Сегодня со мной произошел форс-мажор. Я приехал на работу на велосипеде так что, когда рабочий день закончился, я сел на вел и сопровождаемый прохладным ветерком покатил в центр города. Далеко уехать не получилось. Буквально через 40-50 метров я услышал хруст и педаль под ногой резко провалилась. Оказалось, что я поломал крепление заднего переключателя.
&lt;p style="text-align: center"&gt;​&lt;/p&gt;
&lt;p style="text-align: center"&gt;​&lt;/p&gt;

Вообще, поломать крепление заднего переключателя еще надо умудрится. Переключатель не испытывает больших нагрузок (разве что в момент переключения), так как находится на прямом ходу цепи. На прямом ходу цепь практически расслаблена, — основную нагрузку она испытывает на обратном ходу проходя через заднюю звезду.
При ближайшем рассмотрении оказалось, что всему виной цепь. Разорвавшись она застряла в транке переключателя (цепь проходит через ритейнер переключателя по змейке) как раз в тот момент когда я в очередной раз перенес вес тела на педаль.
&lt;p style="text-align: center"&gt;​&lt;/p&gt;
Моего веса хватило, чтобы хрупкий металл на котором переключатель крепится к раме не выдержал.
Таким образом, разорвавшаяся цепь спровоцировала ситуацию, которая конструктивно не должна была произойти — высокая нагрузка на задний переключатель и его крепление. Конструктивно не должна была произойти... Лично меня как потребителя это не особо утешает.
Не буду лукавить, в этом инциденте виноват в первую очередь я сам. Цепи просто так не лопаются. Не так давно во время поездки на остров Русский у меня был еще один инцидент с этой цепью. В тот день я ее починил при помощи подручных средств. Мне следовало было сменить ее по приезду домой. Теперь цепь стоимостью 300-500 рублей спровоцировала ущерб на 6000-8000 рублей. Но давайте не будем искать виноватых.
Наверное, вы уже спрашиваете себя: “Это конечно прекрасно, но мы то тут причем?”. Благодаря этой ситуации в моей памяти всплыл пост &lt;a href="http://softwaremaniacs.org/"&gt;Ивана Сагалаева&lt;/a&gt;, который не так давно написал небольшую статью о том, что “&lt;a href="http://softwaremaniacs.org/blog/2009/06/22/caching-is-not-hack/"&gt;кеш не хак&lt;/a&gt;”. В целом, я согласен с постулатами которые излагает Иван. Кеширование действительно стало неотъемлемой частью большинства web-проектов. Все знают что такое кеш, во всех языках программирования уже есть интерфейсы к memcached и т.д. А это значит, что кеш, действительно не хак — это наши суровые будни.
Но это лишь одна сторона медали. Вторая ее сторона, на мой взгляд, заключается в том, что кеш для некоторых программистов является тем самым “прямым ходом цепи, на котором нет нагрузки”. Когда в приложении появляется кеш, так легко пойти на поводу у собственной лени и сказать: “ну ладно, здесь информация быстро попадет в кеш и все будет оки доки”. Но фактически каждый раз когда вы себе это позволяете вы превращаете кеш в неотъемлемое звено вашего приложения, без которого оно возможно уже не будет работать.
Существуют ситуации, когда это обоснованно. Когда кеш действительно является неотъемлемой частью системы. В этом случае он, как и все другие критичные звенья системы, дублируется. Вы дублируете свой кеш? Или может сохраняете его в persistent store чтобы предотвратить эффект “холодного старта”? Хорошо, если так.
Полагаясь на кеш, вы не должны складывать с себя обязательств по обеспечению нужного уровня производительности и масштабируемости вашего storage’а. В противном случае, “когда цепь рванет” вы можете потерять что-нибудь более важное чем “просто цепь”. Например, вы можете потерять базу данных, которая не справилась с большим потоком конкурентных запросов. Конечно же, в этом случае вы скажете: “высокая нагрузка на этот агрегат конструктивно не предусмотрена”. Пользователям от этого не легче, скажу я вам.
Безусловно, ситуации бывают разные, и возвращаясь к моей маленькой “трагедии”, я понимаю инженеров shimano, которые скорее всего гнались за низким весом (а это тоже очень важно) и поэтому сделали крепеж из хрупкого металла. Но это не меняет главного...
&lt;em&gt;В любой момент времени вы должны себе отдавать отчет в том, что произойдет с приложением, если “рванет ваша цепь”.&lt;/em&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-975020848775062705?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/975020848775062705/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/08/blog-post_24.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/975020848775062705'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/975020848775062705'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/08/blog-post_24.html' title='Что если цепь рванет?'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-7980880470326892535</id><published>2009-08-18T02:12:00.000+12:00</published><updated>2009-08-19T23:51:07.540+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='scalability'/><category scheme='http://www.blogger.com/atom/ns#' term='sacling vectors'/><title type='text'>О проблемах растущего размера</title><content type='html'>&lt;p style="clear: both"&gt;Все таки интернет индустрия развивается. Теперь на каждом углу высоконагруженные проекты, миллионы пользователей, требования к high-availability, масштабиремости. Причем вот ведь странно. К масштабиремости всегда относятся как-то однобоко. Иногда ее &lt;a href="http://dotsid.blogspot.com/2009/06/perfomance-vs-scalability_28.html"&gt;путают&lt;/a&gt; с производительностью. А иногда не понимают что масштабируют.&lt;/p&gt;  &lt;p style="clear: both"&gt;Dan Pritchett, один из архитекторов eBay (теперь уже бывший), еще в 2006 году написал отличное эссе &amp;quot;&lt;a href="http://www.addsimplicity.com/adding_simplicity_an_engi/2006/11/you_scaled_your.html"&gt;You scaled your what?&lt;/a&gt;&amp;quot; на тему векторов масштабирования (scaling vectors). Действительно, существует масса направлений которые требуют внимания при интенсивном росте проекта, а следственно и сложности решаемых в проекте проблем. А это значит, что употребляя слово &amp;quot;масштабируемость&amp;quot;, мы должны отдавать себе отчет в том, в каком контексте мы употребляем это слово.&lt;/p&gt;  &lt;p style="clear: both"&gt;&lt;strong&gt;Transactional scalability&lt;/strong&gt;&lt;/p&gt;  &lt;p style="clear: both"&gt;Наверное, самый привычный вектор. Пропускная способность системы выражающаяся в количестве транзакций которые система может обслужить в единицу времени. Это есть ни что иное, как мера количества пользователей которые одновременно могут пользоваться системой. Когда говорят о масштабирумости в большинстве случаев имеют ввиду именно этот вектор. Простой и понятный большинству, он легко поддается измерению.&lt;/p&gt;  &lt;p style="clear: both"&gt;&lt;strong&gt;Data scalability&lt;/strong&gt;&lt;/p&gt;  &lt;p style="clear: both"&gt;Тоже довольно популярный вектор. Насколько хорошо система справляется с ростом dataset'а. Бывает так, что система хорошо приспособлена к росту пользовательской аудитории, но плохо переносит возрастающий набор данных. Причиной может быть природа реляционных баз данных, которые плохо приспособлены к высокой конкурентной нагрузке.&lt;/p&gt;  &lt;p style="clear: both"&gt;Важно понимать, что цена хранения одного мегабайта данных зависит от цены потери одного мегабайта данных. При росте dataset'а, использовать один и тот же подход для хранения данных разной важности крайне неэффективно. Как и в случае с transactional scalability типичным ответом на data scalability является горизонтальное масштабирование.&lt;/p&gt;  &lt;p style="clear: both"&gt;&lt;strong&gt;Operational scalability&lt;/strong&gt;&lt;/p&gt;  &lt;p style="clear: both"&gt;Очень важный вектор, который до поры до времени игнорируется многими командами. Программное обеспечение нуждается в поддержке. Его необходимо контролировать, мониторить, тестировать и выявлять ошибки (желательно раньше чем это сделают ваши пользователи), определять потенциально узкие места с точки зрения производительности (до того, как они вас &amp;quot;задушат&amp;quot;). Сложность всех этих процессов растет вместе со сложностью системы.&lt;/p&gt;  &lt;p style="clear: both"&gt;Представьте себе простое web-приложение работающее на одном сервере и использующее в качестве базы данных простые текстовые файлы. Такое приложение может представлять из себя буквально пару десятков скриптов. Вряд ли бы у вас возникли проблемы с организацией процесса разработки и тестирования такого приложения. А что если это система распределена по 15 серверам, написана на разных языках и использует самое различное middleware обеспечение? Как бы вы вообще запустили бы такую систему на машине разработчика? И стали бы вы вообще это делать?&lt;/p&gt;  &lt;p style="clear: both"&gt;Если не уделять внимания этому вектору, то при росте инфраструктурной сложности он вам нанесет &amp;quot;ответный удар&amp;quot;, не сомневайтесь.&lt;/p&gt;  &lt;p style="clear: both"&gt;&lt;strong&gt;Feature time-to-market scalability&lt;/strong&gt;&lt;/p&gt;  &lt;p style="clear: both"&gt;Ничто так не омрачает большой проект, как большое количество программистов. Большое количество программистов = большое количество проблем. Но тем не менее, бизнес не стоит на месте — он растет. Много идей, кто-то должен их реализовывать. Для этого нужны новые люди. Но новые люди не всегда увеличивают производительность команды. Иногда все происходит совсем наоборот.&lt;/p&gt;  &lt;p style="clear: both"&gt;Умение при помощи растущего коллектива решать пропорционально растущий объем задач — это искусство.&lt;/p&gt;  &lt;p style="clear: both"&gt;Я назвал всего четыре вектора. Есть и другие, такие как: power scalability, deployability и другие. Более того внутри компании могут быть свои scaling vectors, которые обусловлены спецификой ее функционирования.&lt;/p&gt;  &lt;p style="clear: both"&gt;Конечно же (и это уже классика жанра), вы не сможете добиться максимума по всем направлениям — необходимо выбирать. Надо понимать, какие направления для вас являются приоритетными в краткосрочной перспективе, и что может изменится в долгосрочной. Поэтому, когда вы в следующий раз будете употреблять слово &amp;quot;масштабируемость&amp;quot;, задумайтесь — &lt;em&gt;масштабируемость чего?&lt;/em&gt;&lt;/p&gt;  &lt;br style="clear: both" class="final-break" /&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-7980880470326892535?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/7980880470326892535/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/08/blog-post.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/7980880470326892535'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/7980880470326892535'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/08/blog-post.html' title='О проблемах растущего размера'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-6375063615816620106</id><published>2009-08-17T21:39:00.010+12:00</published><updated>2009-08-19T23:54:12.039+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='non-blocking collections'/><category scheme='http://www.blogger.com/atom/ns#' term='multi-threading'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='pipelining'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>Pipelining</title><content type='html'>&lt;p style="clear: both"&gt;Существует одна очень старая и эффективная техника — конвейерная обработка данных (pipelining). Ее смысл заключается в том, что разные физические исполнители, которые могут работать не блокируя друг друга (хвала &lt;a href="http://en.wikipedia.org/wiki/Direct_memory_access"&gt;DMA&lt;/a&gt;), такие как: процессоры, жесткие диски, сетевые карты, — должны работать не блокируя друг друга. Это позволяет повысить их утилизацию, и, если правильно все организовать, не допустить перегрузки отдельно взятых исполнителей. Отличительной особенностью этой техники является то, что она используется в &lt;a href="http://en.wikipedia.org/wiki/Instruction_pipeline"&gt;микропроцессорах&lt;/a&gt; с незапамятных времен, и сейчас является неотъемлемой частью любого &lt;a href="http://en.wikipedia.org/wiki/Superscalar"&gt;суперскалярного процессора&lt;/a&gt;.&lt;/p&gt;  &lt;p style="clear: both"&gt;Представьте себе следующий код:&lt;/p&gt;  &lt;pre style="clear: both"&gt;while ( !isInterrupted() ) {
  byte[] data = performComplexIOOperation();
  performComplexProcessing(data);
} &lt;/pre&gt;

&lt;p style="clear: both"&gt;Довольно типичная ситуация. Сначала мы читаем данные, потом обрабатываем. Если каждая из фаз (чтение/обработка) в отдельности требует много времени, то этот код не сможет полностью утилизовать ни процессор ни подсистему ввода/вывода. Что мы можем сделать для того, чтобы более полно утилизовать ресурсы машины, на которой работает приложение?&lt;/p&gt;

&lt;p style="clear: both"&gt;Давайте посмотрим на то, чем занимается наш поток выполняющий этот код.&lt;/p&gt;

&lt;p style="clear: both"&gt;&lt;img style="text-align: center; margin: 0px auto 10px; display: block" class="linked-to-original" src="http://picasaweb.google.com/data/media/api/user/dotsid/albumid/5369766107676319985/photoid/5369809340746363874/1?authkey=Gv1sRgCPaF7orSscK-Dw" width="380" height="87" /&gt;Вполне очевидно, что ни IO, ни CPU не загружены на 100%. Причиной является то, что один поток работает сразу с двумя физическими исполнителями, не давая возможности этим исполнителям работать параллельно. В тот момент, когда процессор обрабатывает данные, жесткому диску нечем заняться. Пока жесткий диск читает данные, у процессора нет работы. Вы можете сказать: &amp;quot;Так ведь процессор не просто ждет. Он ждет данных от жесткого диска&amp;quot;. Все верно, но чего ждет жесткий диск пока процессор обрабатывает очередную порцию данных?&lt;/p&gt;

&lt;p style="clear: both"&gt;Это приводит нас к следующим двум правилам:&lt;/p&gt;

&lt;ul style="clear: both"&gt;
  &lt;li&gt;поток занимающий процессор &lt;em&gt;не должен иметь причин для блокировки&lt;/em&gt;;&lt;/li&gt;

  &lt;li&gt;поток не занимающий процессор &lt;em&gt;может иметь только один ресурс&lt;/em&gt;, который может служить причиной его блокировки (конечно все иначе, если вы используете какую-либо из моделей неблокирующего ввода/вывода). &lt;/li&gt;
&lt;/ul&gt;

&lt;p style="clear: both"&gt;Другими словами, поток управления должен работать только с одним исполнителем. Можете это считать &lt;a href="http://en.wikipedia.org/wiki/Single_responsibility_principle"&gt;принципом единичной ответственности&lt;/a&gt; для многопоточного программирования.&lt;/p&gt;

&lt;p style="clear: both"&gt;Мы можем вынести чтение в отдельный поток и связать потоки при помощи очереди задач.&lt;/p&gt;

&lt;pre style="clear: both"&gt;class ReadingThread implements Runnable {

  private final Queue&amp;lt;ByteBuffer&amp;gt; queue;
  [...]
  public void run() {
    while ( !isInterrupted() ) {
      queue.add( performComplexIOOperation() );
    }
  }
}

class ProcessingThread implements Runnable {

  private final Queue&amp;lt;ByteBuffer&amp;gt; queue;
  [...]
  public void run() {
    while ( !isInterrupted() ) {
      ByteBuffer buffer = queue.poll();
      performComplexProcessing(buffer);
    }
  }
}&lt;/pre&gt;

&lt;p style="clear: both"&gt;В этом случае картина будет выглядеть примерно следующим образом.&lt;/p&gt;

&lt;p style="clear: both"&gt;&lt;img style="text-align: center; margin: 0px auto 10px; display: block" class="linked-to-original" src="http://picasaweb.google.com/data/media/api/user/dotsid/albumid/5369766107676319985/photoid/5369809357226788738/1?authkey=Gv1sRgCPaF7orSscK-Dw" width="380" height="146" /&gt;Теперь мы имеем загруженный жесткий и один загруженный процессор. Что мы можем сделать еще?&lt;/p&gt;

&lt;p style="clear: both"&gt;Если задача обработки хорошо распараллеливается, то мы можем делегировать обработку данных не отдельному потоку, а пулу потоков. Тем самым мы можем утилизовать несколько процессоров.&lt;/p&gt;

&lt;p style="clear: both"&gt;&lt;img style="text-align: center; margin: 0px auto 10px; display: block" class="linked-to-original" src="http://picasaweb.google.com/data/media/api/user/dotsid/albumid/5369766107676319985/photoid/5369809362460152242/1?authkey=Gv1sRgCPaF7orSscK-Dw" width="369" height="213" /&gt;Стоит отметить, что java предоставляет все необходимые синхронизационные примитивы для реализации конвейерной обработки данных, такие как: &lt;a href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/ConcurrentLinkedQueue.html"&gt;конкурентные очереди&lt;/a&gt; и &lt;a href="http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/Executor.html"&gt;пулы потоков&lt;/a&gt;.&lt;/p&gt;

&lt;p style="clear: both"&gt;Закрепление за отдельным исполнителем отдельного потока дает ряд преимуществ: &lt;/p&gt;

&lt;ul style="clear: both"&gt;
  &lt;li&gt;не имея других причин для блокировки кроме как на этом исполнителе, мы способны утилизовать его полностью. По-крайней мере, до тех пор пока коммуникационный механизм (например, очередь при помощи которой передаются данные между потоками) не станет bottleneck'ом;&lt;/li&gt;

  &lt;li&gt;так как не существует большого количества потоков работающего с исполнителем мы не допускаем его перегрузки.&lt;/li&gt;
&lt;/ul&gt;
Второй аспект очень важен. Вы должны понимать, что большое количество потоков вредит системе. В случае процессора это высокий context switch'инг, что пагубно влияет на производительность. В случае жесткого диска мы можем забыть про sequential read.

&lt;p&gt;&lt;/p&gt;

&lt;p style="clear: both"&gt;Стоит отметить, что &lt;em&gt;коммуникация между потоками не может быть бесплатной&lt;/em&gt;. Не смотря на то, что существуют довольно эффективные алгоритмы для реализации &lt;a href="http://www.ibm.com/developerworks/java/library/j-jtp04186/index.html"&gt;неблокирующих очередей&lt;/a&gt;, делегировать сложение двух чисел в отдельный поток все же не стоит. Коммуникационные расходы будут дороже, чем выполнение задачи.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-6375063615816620106?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/6375063615816620106/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/08/pipelining.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/6375063615816620106'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/6375063615816620106'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/08/pipelining.html' title='Pipelining'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-4010741548853203739</id><published>2009-08-02T23:54:00.000+12:00</published><updated>2009-08-03T00:16:24.763+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='scatter-gather'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><category scheme='http://www.blogger.com/atom/ns#' term='asynchronous'/><category scheme='http://www.blogger.com/atom/ns#' term='mysqlnd'/><title type='text'>Возожности mysqlnd в PHP/5.3</title><content type='html'>&lt;p style="clear: both"&gt;Если вы не знаете, то с PHP/5.3 поставляется новый mysql драйвер — &lt;a href="http://ru.php.net/mysqli.mysqlnd"&gt;mysqlnd&lt;/a&gt;. У него есть несколько особенностей и воможностей, которые отличают его от libmysql.&lt;/p&gt;&lt;p style="clear: both"&gt;Первое не очень интерестно. Теперь при fetch'e результата не происходит копирования из памяти libmysql в память, находящуюся под управлением zend engine. Фактически весь result set находится в памяти zend engine. Это значит что выборки превышающие по размеру memory limit теперь таки будут генерировать out of memory. Наверное, это представляет ценность для владельцев shared хостинга, но для тех у кого выделенный сервер это имеет мало пользы.&lt;/p&gt;&lt;p style="clear: both"&gt;Второе уже интерестно. Появилась возможность выполнять запросы к базе данных в неблокирующей манере (в документации эта возможность называется "асинхронными запросами", что буквально говоря некорректно). Это позволяет:&lt;/p&gt;&lt;ul style="clear: both"&gt;&lt;li&gt;распараллеливать чтение/запись;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;реализовать SLA в отношении запросов к БД.&lt;/li&gt;&lt;/ul&gt;&lt;p style="clear: both"&gt;Распараллеливание записи может быть полезным, когда логическая операция записи физически приводит к обновлению информации хранящейся на нескольких серверах. Особенно это может быть выгодно, когда ваш процесс большую часть времени занят ожиданием подтверждения от БД об этой самой записи (i/o bound). Потребность "писать сразу в несколько мест" может возникнуть, например, в случае, если вы делаете программное зеркало вашей базы данных. На первый взгляд это может показатся глупостью, почему бы просто не настроить репликацию? Тем не менее, существуют ситуации когда application layer репликация имеет преимущества над mysql-репликацией. Также распараллеливание записи может быть полезно в случае осуществления сложных механизмов sharding'а. Иногда объект должен быть записан не в один shard, а в несколько (это уже не совсем зеркало). Такие операции записи хорошие кандидаты для распараллеливание (по-крайней, мере до тех пор пока shard'ы находятся на физически разных серверах).&lt;/p&gt;&lt;p style="clear: both"&gt;Распараллеливание чтения может быть полезно в случае реализации scatter-gather. Если у вас сложное разбиение данных по серверам, то может возникнуть ситуация когда при построении SELECT запроса неизвестно какому серверу его адресовать (представьте что вы пытаетесь извлечь данные с фильтрацией по полю, которое не учавствует в критериях разбивки) — соответствующие данные "распылены" по всем серверам. В этом случае необходимо послать SELECT запрос всем серверам (scatter) и затем обьединить все результаты в один result set (gather). Вообще, &lt;em&gt;эта техника очень дорога &lt;/em&gt;и лучше делать так, чтобы вам не приходилось ее использовать. Например, &lt;em&gt;выбрать разбиение, которое позволит обращатся только к одному серверу. &lt;/em&gt;Тем самым вы увеличите data locality и благотворно повлияете на availability, так как теперь для обслуживания запроса вам нужны не все сервера, а всего один. Но если уж без scatter-gather не обойтись, то будет неплохо хотя бы отправлять запросы серверам параллельно, а не последовательно. В этом случае общее время ожидения ответа будет равно времени ожидания самого медленного сервера, а не сумме времени ожидания всех серверов.&lt;/p&gt;&lt;p style="clear: both"&gt;И конечно же SLA. Лично нам этого очень сильно нехватало. Если запрос нас не блокирует, то технически мы можем и не ждать результатов его выполнения. Совсем не ждать смысла конечно нет, а вот не ждать дольше чем строго определенное количество времени, бывает очень полезно. Я уже писал раньше про &lt;a href="http://dotsid.blogspot.com/2009/07/fail-fast.html"&gt;fail fast&lt;/a&gt;. Всякий раз когда вы делаете более менее сложный запрос к БД вы не можете быть уверены сколько времени займет выполнение этого запроса. А пользователи ждать не любят. Все наверное были свидетелями как БД при растущей нагрузке все медленее и медленее обрабатывает существующие запросы. В такой ситуации новые запросы дают эффект снежного кома. С другой стороны, вполне возможно, что результат нам не так уж и нужен. Ну подумаешь не покажем пользователю на странице поиска сколько у него новых сообщений в "личке", — он не для этого поиск инициировал. Неблокирующие запросы позволяют реализовать поведение когда мы спрашиваем у БД: "сколько у пользователя личных сообщений?" и недождавшись ответа в оговоренный timeout как бы говорим: "не очень-то и хотелось" и просто скрываем блок с личными сообщениями. Более того, можно реализовать схему в которой клиент не дождавшись ответа, через отдельный control connection, прибьет свой собственный запрос, чтобы он не генерировал дополнительную нагрузку на БД, — ей судя по всему и так не сладко, раз она не успела ответить вовремя (конечно, все это не работает с DML запросам).&lt;/p&gt;&lt;p style="clear: both"&gt;Это все. &lt;a href="http://www.scribd.com/doc/7588165/mysqlnd-Asynchronous-Queries-and-more"&gt;Остальное&lt;/a&gt; — &lt;a href="http://schlueters.de/blog/archives/86-Direct-MySQL-Stream-Access.html"&gt;сомнительные мелочи&lt;/a&gt;.&lt;/p&gt;&lt;br class='final-break' style='clear: both' /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-4010741548853203739?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/4010741548853203739/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/08/mysqlnd-php53.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/4010741548853203739'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/4010741548853203739'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/08/mysqlnd-php53.html' title='Возожности mysqlnd в PHP/5.3'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-8710883604358318482</id><published>2009-07-19T02:19:00.006+12:00</published><updated>2009-07-19T15:47:53.214+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='multi-threading'/><category scheme='http://www.blogger.com/atom/ns#' term='perfomance'/><category scheme='http://www.blogger.com/atom/ns#' term='multi-core cpu'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>Энди и Билл</title><content type='html'>&lt;p style="clear: both"&gt;Вчера моя девушка купила себе Sims 3. Ну вы знаете... Маленькие человечки, которые постоянно требуют спать и есть. Эдакие тамагочи, которым хватает вычислительной мощности чтобы обсчитывать нравятся ли им их соседи, босс и прическа того волосатого парня, который косится на них в течении всей вечеринки. Полторы тысячи за лицензионную копию. И на обороте бокса вы можете прочитать:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote style="clear: both"&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;Системные требования

CPU: (XP) процессор P4 с частотой 2,0 ГГц или аналогичный: (Visa) процессор с частотой 2,4 ГГц или аналогичный.

RAM: (XP) 1 Гб оперативной памяти: (Vista) 1,5 Гб оперативной памяти.&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p style="clear: both"&gt;Кто-нибудь задумывался над смыслом таких требований? Почему Vista требует на 20% более мощный процессор и на 50% больше оперативной памяти?&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p style="clear: both"&gt;Существует шуточный закон "Что Энди дал, то Билл забрал" (“Andy giveth, and Bill taketh away"). Это означает, что прирост производительности даваемый каждым новым чипом компании Intel (Andy Grove) уравновешивается performance penalty нового продукта Microsoft (Bill Gates).&lt;/p&gt;&lt;p style="clear: both"&gt;Конечно, это шутка, но в кажой шутке есть доля &lt;a href="http://geeklikemetoo.blogspot.com/2007/11/andy-giveth-and-bill-taketh-away.html"&gt;правды&lt;/a&gt;. &lt;i&gt;Дайте программисту в два раза более мощный процессор и он найдет причину делать в два раза больше работы или не будет чувствовать особой вины в том, чтобы делать работу в два раза менее эффективно.&lt;/i&gt; Зачем тратить время на оптимизацию ПО, если можно просто купить более мощный процессор? Это называется вертикальным масштабированием. И иногда, это чертовски хороший подход, потому что время программиста стоит дороже, чем время процессорное. Возьмие для примера Amazon'овский &lt;a href="http://aws.amazon.com/elasticmapreduce/"&gt;Elastic Cloud&lt;/a&gt;. 1 час работы на восьми EC2 Unit (каждый unit это Opteron/Xeon c частотой примерно 2.5 ГГц) стоит... 80 центов. Это примерно &lt;i&gt;18500 рублей в месяц&lt;/i&gt;. За такие деньги квалифицированного программиста вы себе не наймете. Но вертикальное масштабирование не панацея. И рано или поздно оно перестает работать. Учитывая, что тот экспоненциальный рост частоты процессоров (читай производительности) о котором говорят на каждом углу перестал быть экспоненциальным еще в 2003 году, оно пересает работать скорее рано чем поздно. Да да, у нас появилось много ядер. &lt;a href="http://en.wikipedia.org/wiki/Intel_Core_i7"&gt;Новая линейка&lt;/a&gt; чипов Intel для настольных систем содержит 4 физических/8 логических процессоров (кто бы мог подумать, HyperThreading по прежнему жив). Но значит ли это что приложения стали выполнятся в 4 раза быстрее? Нет.&lt;/p&gt;&lt;p style="clear: both"&gt;Тому есть несколько причин. Во-первых, есть задачи, которые не параллелятся в принципе. Разбор XML хороший пример. Это чистого вида sequential задача. Во-вторых, есть так называемые точки сериализации, о которых я &lt;a href="http://dotsid.blogspot.com/2009/01/blog-post.html"&gt;писал раньше&lt;/a&gt;. Из-за них процессоры должны тратить некоторое время на обеспечение когерентности кеша, чтобы иметь корректное отображение оперативной памяти в кешах. Эти точки сериализации есть всегда (многие процессы, такие как выделения памяти, требуют координации на уровне операционной системы). Безусловно, то что я сказал, это некоторое упрощение реальной ситуации. Но смысл в том, что &lt;i&gt;2 * 2 ГГц не равно 4 ГГц&lt;/i&gt;.&lt;/p&gt;&lt;p style="clear: both"&gt;Наша проблема в том, что &lt;i&gt;долгое время мы ставили знак равенства между частотой и производительностью&lt;/i&gt;. Что может быть проще, чем просто дождаться "следующего шага" Intel? Своего рода &lt;a href="http://www.gotw.ca/publications/concurrency-ddj.htm"&gt;free lunch&lt;/a&gt;, о котором писал Herb Sutter еще в 2005 году.&lt;/p&gt;&lt;p style="clear: both"&gt;Справедливости ради хочу сказать, что я не виню Microsoft. Наоборот Windows 7, по моему мнению, делает успехи, и не только в плане производительности. Но мы&lt;i&gt; должны отказаться от той мысли, что инженеры Intel и AMD смогут легко увеличить производительность наших приложений путем наращивания частоты&lt;/i&gt;. Теперь эти самые инженеры делают все для того, чтобы экспоненциальная гонка не останавливалась, даже если для этого придется &lt;i&gt;заставить программистов сменить парадигмы программирования&lt;/i&gt;.&lt;/p&gt;&lt;p style="clear: both"&gt;Что мы и наблюдаем. Функциональные языки набирают обороты. Microsoft Research занимается разработкой &lt;a href="http://blog.functionalfun.net/2009/02/maestro-new-net-language-for-parallel.html"&gt;Maestro&lt;/a&gt;. Java получает &lt;a href="http://www.infoq.com/news/2008/03/fork_join"&gt;fork/join framework&lt;/a&gt; и обзаводится реализацией &lt;a href="http://www.deucestm.org/"&gt;software transactional memory&lt;/a&gt;. И это только верхушка айсберга.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;a href="http://www.gotw.ca/publications/concurrency-ddj.htm"&gt;По словам&lt;/a&gt; того же Herb'а Sutter'а, &lt;i&gt;существующие реалии развития микропроцессоров должны совершить революцию во взглядах на то, как мы должны писать программы&lt;/i&gt;. Последнюю такую революцию, по его мнению, совершило объектно-ориентированное программирование. Я более спокоен во взглядах на то, какую роль в нашей жизни играют параллельные вычисления. Но в чем-то Herb прав. Если хотите быть хорошим специалистом, самое время начать получать опыт работы с конкурентыми задачами.&lt;/p&gt;&lt;p style="clear: both"&gt;Пока же Intel выжимает из своих процессоров все для того чтобы повысить sequential execution speed. И пока это так, я могу смело запустить Sims 3, продать старую раковину, купить новые обои в гостиную и пригласить кого-нибудь на сегодняшнюю вечеринку. Чертовски забавная игра, все таки.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-8710883604358318482?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/8710883604358318482/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/07/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/8710883604358318482'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/8710883604358318482'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/07/blog-post.html' title='Энди и Билл'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-3178038992564786303</id><published>2009-07-15T02:15:00.002+12:00</published><updated>2009-07-15T02:35:56.997+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='asynchronous processing'/><category scheme='http://www.blogger.com/atom/ns#' term='scalability'/><category scheme='http://www.blogger.com/atom/ns#' term='fail-fast'/><title type='text'>Fail fast</title><content type='html'>&lt;pre&gt;$data = file_get_contents($location);&lt;/pre&gt;&lt;p style="clear: both"&gt;Что не так с этим кодом? На первый взгяд все хорошо. Вряд ли он может нам чем-нибудь навредить.&lt;/p&gt;&lt;p style="clear: both"&gt;Хорошо, предположим, что у вас есть 3 web frontend'а, каждый из которых может обслуживать 60 параллельных запросов (итого 180 конкурентных запросов). На минутку предположим, что вышеприведенный код — это единственный код, который выполняется при обработке запроса. Если location указывает на локальный ресурс (на локальной файловой системе), то вряд ли что-то может пойти не так. Latency чтения с локальной файловой системы достаточно низкий для того, чтобы не беспокоится о возможных проблемах (если файлы достаточно маленькие).&lt;/p&gt;&lt;p style="clear: both"&gt;А что если location указывает на удаленный ресурс? Например, на какой-нибудь ресурс доступный по протоколу http. Предположим, что ресурс недоступен. Как поведет себя ваше приложение?&lt;/p&gt;&lt;p style="clear: both"&gt;В лучшем случае, вы моментально получите управление назад, сможете определить ошибочную ситуацию и каким-либо образом восстановить поток исполнения. Но так бывает далеко не всегда. В случае если недоступна физический машина (а не приложение на этой машине), или если на сервере некорректно настроен firewall, то вы не получите управление назад. Клиент отсылает &lt;a href="http://en.wikipedia.org/wiki/Tcp_handshake#Connection_establishment"&gt;TCP SYN&lt;/a&gt; запрос и... тишина. Некому вам сказать, что ресурс недоступен, и все что вам остается — это ждать.&lt;/p&gt;&lt;p style="clear: both"&gt;Возможно у вас возник вопрос: "А как это меня касается? Я же не могу оживить удаленный ресурс". Два момента.&lt;/p&gt;&lt;p style="clear: both"&gt;Во-первых, пользователи не любят ждать. Особенно они не любят когда после ожидания им говорят: "извините, мы не смогли обработать ваш запрос". Зачем было ждать ЭТО? Во-вторых, путем довольно &lt;a href="http://en.wikipedia.org/wiki/Little%27s_law"&gt;нехитрых расчетов&lt;/a&gt; можно вычислить, что если скорость поступления запросов равна 30 запросам в секунду, а запрос обрабатывается дольше 6 секунд, то при 180 обработчиках у вас переполнится очередь входящий запросов и вы перестанете отвечать на эти самые запросы. А пользователи, как известно, не любят ждать. Числа, может быть и ничего не говорящие, но смысл в том, что они конечны, а следственно конечен и тот timeout превышение которого для вас может закончится отказом в обслуживании для всех новых запросов.&lt;/p&gt;&lt;p style="clear: both"&gt;В современном мире распределенных приложений, в котором даже яичницу приготовить сложнее, чем отправить SOAP запрос, — подобная ситуация с зависимостью между различными приложениями не редка.&lt;/p&gt;&lt;p style="clear: both"&gt;Поэтому реализуя системы которые имеют синхронные зависимости от 3rd party ресурсов необходимо соблюдать ряд правил:&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;/p&gt;&lt;ul style="clear: both"&gt;&lt;li&gt;если обращение к внешней системе никак не влияет на user experience пользователя и выполняет чисто служебные функции (например, логгирование статистики), то отличным вариантом будет замена синхронного вызова, на асинхронный (например, при помощи &lt;a href="http://activemq.apache.org/"&gt;ActiveMQ&lt;/a&gt; и &lt;a href="http://stomp.codehaus.org/"&gt;STOMP&lt;/a&gt;);
&lt;/li&gt;&lt;li&gt;если асинхронная обработка невозможна, то необходимо корректным образом настроить connection и read timeout'ы. Это позволит ограничить то время, которое клиент будет ждать ответа от сервера и исключить возможность отказа в обслуживании;&lt;/li&gt;&lt;li&gt;если сервер на котором располагается ресурс находится под вашим управлением, необходимо проверить, что в iptables нет правил, которые осуществляют DROP пакетов. DROP правила, в отличии от REJECT, откидывают входящий пакет, без соответствующего ICMP-ответа клиенту о недоступности ресурса, что и провоцирует такие ситуации;
&lt;/li&gt;&lt;/ul&gt;Помните, для того чтобы исправить ошибку, надо сначала обнаружить ошибку.&lt;p&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-3178038992564786303?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/3178038992564786303/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/07/fail-fast.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/3178038992564786303'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/3178038992564786303'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/07/fail-fast.html' title='Fail fast'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-5876770478150984028</id><published>2009-06-28T20:34:00.005+12:00</published><updated>2009-06-28T22:46:36.215+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='perfomance'/><category scheme='http://www.blogger.com/atom/ns#' term='scalability'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>Perfomance v.s. scalability</title><content type='html'>&lt;p style="clear: both;"&gt;Иногда встречаюсь c непониманием того, что такое производительность, а что такое масштабируемость. Почему-то некоторые люди считают, что это одно и то же.&lt;/p&gt;&lt;p style="clear: both;"&gt;С производительностью все более менее понятно. Это мера скорости работы системы. Запрос поступает в систему и через некоторое время система генерирует ответ. Это время (которое иногда называют latency) и является мерой производительности системы. Для интернет-приложений время получения ответа клиентом может быть значительно больше, чем время генерации ответа на стороне сервера (потери в канале, необходимость загрузки дополнительных статических ресурсов и т.д). Обычно эти издержки не учитываются при оценке производительности, хотя надо признать, они могут очень сильно влиять на user experience работы с приложением.&lt;/p&gt;&lt;p style="clear: both;"&gt;С масштабируемостью все немного сложнее . Если производительность это ответ на вопрос "как быстро?", то масштабирумость — на вопрос "как много?". Как много пользователей одновременно могут пользоваться ресурсом? Как много данных может быть в базе данных прежде чем она перестанет справляется с нагрузкой?&lt;/p&gt;&lt;p style="clear: both;"&gt;Иначе говоря, оптимизация под производительность подразумевает делать ту же работу используя меньше ресурсов. Под масштабируемость — используя больше ресурсов, делать пропорционально больший объем работы.&lt;/p&gt;&lt;p style="clear: both;"&gt;Это может показаться странным, но оптимизация под масштабируемость нередко приводит к худшей производительности. С точки зрения производительности наверное нет ничего лучше чем sql-вызовы прямо в шаблоне (зачем тратить лишнее время на вызовы полиморфных методов, да?). Но для того чтобы хорошо масштабироваться по объему данных в БД, необходимо уметь "пилить" нагрузку между серверами. А для этого требуется гибкий механизм диспетчеризации обращений к базе данных (ORM, ActiveRecord, DataMapper, DAO или что там у вас). Нам нужен некий промежуточный слой, который бы абстрагировал клиента от знания где именно (на каком сервере) лежат данные, которые он запрашивает. В зависимости от специфики реализации такие слои могут добавлять существенный overhead к latency ответа. И все это делается только ради того чтобы быть больше, а не быстрее (на самом деле делается гораздо больше, это всего лишь частный пример), потому что в большинстве случаев &lt;em&gt;быть большим выгоднее, чем быть быстрым&lt;/em&gt;.&lt;/p&gt;&lt;p style="clear: both;"&gt;Только не поймите меня неправильно, я не хочу сказать, что быть быстрым не надо. Пользователи не будут ходить на ваш ресурс если страницы будут открываться по 10 секунд. Что я хочу сказать, так это то, что &lt;em&gt;ваши пользователи не будут платить вам больше, если страницы вашего ресурса будут открываться не 0.2 секунды, а 0.1&lt;/em&gt;. Но если у вас будет в два раза больше пользователей...&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-5876770478150984028?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/5876770478150984028/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/06/perfomance-vs-scalability_28.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/5876770478150984028'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/5876770478150984028'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/06/perfomance-vs-scalability_28.html' title='Perfomance v.s. scalability'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-2657522462220849212</id><published>2009-06-27T23:13:00.005+12:00</published><updated>2009-07-19T15:37:59.746+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='multi-threading'/><category scheme='http://www.blogger.com/atom/ns#' term='multi-core cpu'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>Future Evolution of High-Performance Microprocessors</title><content type='html'>&lt;p style="clear: both"&gt;Довольно интерестное &lt;a href="http://www.youtube.com/watch?v=BBMeplaz0HA"&gt;видео&lt;/a&gt; о тенденциях развития микропроцессоров. Norm Jouppi из Hewlett-Packard рассказывает о текущих проблемах и о том, что из себя будут представлять микропроцессоры лет эдак через 5-10.&lt;/p&gt;&lt;p style="clear: both"&gt;Было много сказано, про power wall и про то, как растущее энергопотребление не дает наращивать частоты. При этом заметно упали темпы снижения базового напряжения, используемого микросхемами. Мне, как программисту, было интерестно в первую очередь другое, — каковы тенденции в развитии микропроцессоров.&lt;/p&gt;&lt;p style="clear: both"&gt;Оказалось, что "гонка" за ядрами явление временное, а тенденции смещаются в сторону &lt;a href="http://en.wikipedia.org/wiki/Heterogeneous_computing"&gt;гетерогенных вычислений&lt;/a&gt;. А это значит, что довольно скоро компьютеры будут похожы на... PlayStation. В PlayStation 3 стоит процессор IBM Cell, который по своей архитектуре ближе всего к тому, что называется heterogeneous computing.&lt;/p&gt;&lt;p style="clear: both"&gt;Идея гетерогенных вычислений довольно проста, в отличии от &lt;a href="http://en.wikipedia.org/wiki/SMP"&gt;SMP&lt;/a&gt; где все процессоры в системе одинаковы, в гетерогенной архитектуре есть разные исполнители, которые с разной эффективностью выполняют разную работу. Существуют предпосылки для перехода к такой архитектуре.&lt;/p&gt;&lt;p style="clear: both"&gt;Во-первых, мы не можем дальше наращивать частоту отдельного исполнителя (процессора/ядра), — слишком большое энергопотребление.&lt;/p&gt;&lt;p style="clear: both"&gt;Во-вторых, просто наращивать количество ядер не получится. Даже при текущем энегргопотреблении, 256-ядерный процессор будет жрать как электрочайник. Про тепловыделение в данном случае лучше не вспоминать.&lt;/p&gt;&lt;p style="clear: both"&gt;В-третьих, мы не можем делать много процессоров с низкой частотой (в надежде сэкономить на энергопотреблении), так как по &lt;a href="http://dotsid.blogspot.com/2009/01/blog-post.html"&gt;закону Амдаля&lt;/a&gt; задачи, которые плохо параллелятся на таких процессорах будут выполнятся гораздо дольше и генерировать неравномерную загрузку исполнителей.&lt;/p&gt;&lt;p style="clear: both"&gt;Выход, по словам инженеров, в том, чтобы в системе находилось несколько "тяжеловесных" энергоемких (если хотите, классических) процессоров, которые смогли бы быстро решать sequential работу, и много маленьких процессоров с коротким pipeline'ом и низкой частотой, которые бы решали параллельные задачи. Из-за своей простоты их энергопотребление может быть очень низким.&lt;/p&gt;&lt;p style="clear: both"&gt;На самом деле, это сильное упрощение того, что говорил Norm Jouppi. Поэтому заинтересовавшимся советую посмотреть &lt;a href="http://www.youtube.com/watch?v=BBMeplaz0HA"&gt;видео&lt;/a&gt; и &lt;a href="http://cf05.ac.upc.es/micro38/01_keynote2.pdf"&gt;слайды&lt;/a&gt;.&lt;/p&gt;&lt;p style="clear: both"&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-2657522462220849212?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/2657522462220849212/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/06/future-evolution-of-high-performance.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/2657522462220849212'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/2657522462220849212'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/06/future-evolution-of-high-performance.html' title='Future Evolution of High-Performance Microprocessors'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-4207101356981600057</id><published>2009-04-23T22:06:00.000+12:00</published><updated>2009-04-23T22:39:35.469+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='remote shell'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Groovy remote shell</title><content type='html'>&lt;p&gt;Я работаю в довольно интенсивно развивающемся проекте. Мы много эксперементируем с разными инструментами. Иногда приходится инструменты разрабатывать самим&lt;/p&gt;&lt;p&gt;Последний инструментарий, который мне пришлось реализовать самому – это удаленный groovy shell. Если вы незнакомы с &lt;a href="http://groovy.codehaus.org/"&gt;groovy&lt;/a&gt;, то скажу, что это динамический язык работающий на JVM.&lt;/p&gt;&lt;p&gt;Задачей было разработать инструментарий, который бы позволил удаленно дергать некий служебный функционал внутри приложения (запускать переиндексацию, менять настройки thread pool'ов, удалять или создавать обьекты в БД и т.д.). Сначала для этих целей мы использовали JMX. Оказалось слишком сложно и неудобно. Решили сделать удаленный shell, который бы позволял выполнять groovy код в адресном пространстве приложения, что называется, на лету. Что может быть проще, чем получить ссылку на фасад, загрузить пару обьектов и послать им пару сообщений, – прямой канал с приложением в обход всех web-интерфейсов.&lt;/p&gt;&lt;p&gt;Взяв за основу &lt;a href="http://groovy.codehaus.org/Groovy+Shell"&gt;groovysh&lt;/a&gt; (стандартный интерпретирующий shell идущий в поставке с groovy) и поигравшись немного с Input/OutputStream, я написал клиента который работает с удаленно запущенной инстанцией groovysh так же, как если бы он был запущен локально (работает автодополнение по tab, command history и т.д.).&lt;/p&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;" src="http://lh6.ggpht.com/_olxzclEPtBk/SfBC3HTpafI/AAAAAAAAADM/XWCcBH_PayI/Picture%202.png" border="0" alt="Groovy Shell" id="BLOGGER_PHOTO_ID_5327831873800989170" /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-4207101356981600057?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/4207101356981600057/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/04/groovy-remote-shell.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/4207101356981600057'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/4207101356981600057'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/04/groovy-remote-shell.html' title='Groovy remote shell'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_olxzclEPtBk/SfBC3HTpafI/AAAAAAAAADM/XWCcBH_PayI/s72-c/Picture%202.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-470247103936218232</id><published>2009-01-13T23:59:00.000+11:00</published><updated>2009-01-14T03:28:39.263+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='multi-threading'/><category scheme='http://www.blogger.com/atom/ns#' term='amdahl&apos;s law'/><category scheme='http://www.blogger.com/atom/ns#' term='moore law'/><category scheme='http://www.blogger.com/atom/ns#' term='scalability'/><category scheme='http://www.blogger.com/atom/ns#' term='concurrency'/><title type='text'>Конец эры закона Мура</title><content type='html'>&lt;p&gt;Начиная с 70-х годов прошлого века производители микропроцессоров осуществляли экспоненциальный рост производительности, описанный законом Мура. Но достигнув физических ограничений, связанных с резким скачком тока утечки транзисторов и, как следствие, рассеивания тепла, стало понятно &amp;mdash; дальнейшее увеличение производительности процессоров существующими техниками невозможно. Еще в 2003 году Intel обещала предоставить 4 ГГц модель. И вроде бы годом позже intel'овцы приблизились к реализации задуманного, &amp;mdash; появился процессор с тактовой частотой 3.8 ГГц. Но 4 гигагерцовым "мечтам" так и не суждено было сбыться. А все современные модели процессоров работают на тактовой частоте до 3 ГГц. Индустрия, как мы все теперь знаем, пошла другим путем. Выходом стало увеличение количества процессоров (ядер).&lt;/p&gt;

Причем, мне кажется, что Intel давно предвидела что дни гигагерцовых гонок сочтены. &lt;a href='http://ru.wikipedia.org/wiki/Hyper-threading'&gt;Hyper-Threading&lt;/a&gt; тому доказательство. Эта технология изначально появилась в архитектуре Xeon, но позже была реализована и в Pentium 4. Это был первый сигнал нам разработчикам, &amp;mdash; учитесь распараллеливать. Затем появились &lt;a href='http://en.wikipedia.org/wiki/Multi-core_(computing)'&gt;многоядерные процессоры&lt;/a&gt;. А теперь, Anwar Ghuloum, ведущий инженер Intel, &lt;a href='http://blogs.intel.com/research/2008/06/unwelcome_advice.php'&gt;открыто просит разработчиков&lt;/a&gt; учитывать особенности SMP архитектуры.
&lt;blockquote&gt;
developers should start thinking about tens, hundreds, and thousands of cores now in their algorithmic development and deployment pipeline
&lt;/blockquote&gt;
&lt;/p&gt;

Халява кончилась. Дальнейший рост производительности возможен, только путем изменения существующих алгоритмов и их распараллеливания. И именно нам разработчикам придется поставить все эти ядра на службу человечеству. Это и имеют в виду, когда говорят, что наступил &lt;a href='http://www.researchandmarkets.com/research/ae36c8/the_end_of_moores'&gt;конец эры закона Мура&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Справедливости ради надо отметить, что технически закон Мура все же будет выполняться, так как он предсказывает экспоненциальный рост числа транзисторов в интегральный микросхемах. Однако экспоненциального роста производительности при этом наблюдаться не будет. Как такое может быть? На этот вопрос отвечает преемник закона Мура, который и будет вершить бал с этих пор, &amp;mdash; закон &lt;a href='http://en.wikipedia.org/wiki/Amdahl%27s_law'&gt;Амдаля&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Возьмите любую привычную для вас задачу. Если вы заняты в сфере разработки web-приложений &amp;mdash; это может быть обработка пользовательского запроса. Представьте, что вы обрабатываете пользовательские запросы в разных потоках (я предполагаю, что так оно и есть). В любом, случае у этих потоков есть, так называемые, точки сериализации. Это участки кода, которые не могут быть выполнены параллельно. Например, некоторые операции в базе данных при использовании транзакций не могут выполнятся параллельно. Shared lock'и и многие виды координации потоков являются точками сериализации. Именно они, &amp;mdash; эти противные точки сериализации, являются причиной того, что производительность системы падает при увеличении количества исполняемых потоков.&lt;/p&gt;

&lt;p&gt;Попробуйте прикинуть какой процент вашего кода &lt;i&gt;не может&lt;/i&gt; выполнятся параллельно. Назовем эту величину (нормированную по единице) фактором сериализации (&lt;code&gt;P&lt;/code&gt;). Отбросим пока издержки на синхронизацию памяти. Если фактор сериализации равен 1, то вы не можете ничего распараллелить. Даже появление сотни ядер не ускорит вашего приложения. Если фактор сериализации равен 0, то появление ста ядер ускорит ваше приложение в сто раз (либо позволит решать одновременно сто таких задач). Зная свой фактор сериализации вы можете посчитать, на какой теоретический прирост в производительности (speed up factor), вы можете расчитывать, если в системе будет не одно ядро, а &lt;code&gt;N&lt;/code&gt;. Посчитать это очень легко.&lt;/p&gt;

&lt;img style="display:block; margin:0px auto 10px; text-align:center; width: 106px; height: 49px;" src="http://upload.wikimedia.org/math/8/9/0/89073e92c6f758701ce24b9ed480ecd2.png" border="0" alt="" /&gt;

&lt;p&gt;Не знаю во сколько вы оценили свой фактор сериализации, но у меня для вас плохие новости. Даже если вы его оценили в 0.1 (10%) &amp;mdash; это означает, что вы перестаете эффективно масштабироваться примерно на 10 процессорах (прирост производительности от дальнейшего добавления исполнителей составляет меньше 50% от теоретически возможного).

&lt;img style="display:block; margin:0px auto 10px; text-align:center;" src="http://lh5.ggpht.com/_olxzclEPtBk/SWyuswTR1cI/AAAAAAAAABw/MsU7WGjdX5M/amdahl%27s%20law.png" /&gt;

При факторе 0.5 (половина вычислений) добавление пятого процессора в систему ускорит ее максимум на 6% по-сравнению с четырех-процессорной. Говорить об эффективном использовании 8-процессорной машины не приходится. А ведь попадаются очень &lt;a href='http://dotsid.blogspot.com/2009/01/8-8.html'&gt;выдающиеся&lt;/a&gt; сервера.
&lt;/p&gt;

&lt;p&gt;С другой стороны, если взглянуть на каждый отдельно взятый процессор/ядро, то с ростом фактора сериализации его загрузка начинает &lt;i&gt;падать&lt;/i&gt;.

&lt;img style="display:block; margin:0px auto 10px; text-align:center; " src="http://lh5.ggpht.com/_olxzclEPtBk/SWy2IY5z2UI/AAAAAAAAACE/mA852lgzORw/utilization.png" /&gt;

Происходит это по вполне понятным причинам. Из-за того, что в системе присутствует точка сериализации, которую по-определению может выполнять только один поток, другим потокам в это время просто нечем заняться.&lt;/p&gt;

&lt;p&gt;Это немного иронично, но на dedicated серверах из-за этого встает проблема, &amp;mdash; как загрузить CPU работой. А выглядит это так. MySQL в какой-то момент времени перестает обрабатывать запросы (делает это очень медленно). Вы заходите на сервер, а там все в порядке: CPU не загружен, памяти свободной куча, i/o в порядке. Но запросы обрабатываются крайне медленно.&lt;/p&gt;

&lt;p&gt;Но нет худа добра. Я верю, что мы со временем создадим эффективные и что еще более важно, удобные механизмы для распараллеливания задач. Они уже начинают появляться. У бизнес сектора начинает просыпаться интерес к кластерным вычислениям, что подтверждается появлением таких платформ как Amazon EC2/S3 и Java Stax. Появляются новые языки и инструменты, такие как &lt;a href='http://www.scala-lang.org/'&gt;Scala&lt;/a&gt; и &lt;a href='http://www.malhar.net/sriram/kilim/'&gt;Kilim&lt;/a&gt;, которые позволяют эффективно работать с concurrency. Получают более широкое распространение уже знакомые методики функционального программирования, которые очень помогают в данном случае. А это все говорит о том, что у нас с вами есть огромное поле для развития и самосовершенствования, чего я вам и желаю.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-470247103936218232?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/470247103936218232/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/01/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/470247103936218232'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/470247103936218232'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/01/blog-post.html' title='Конец эры закона Мура'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_olxzclEPtBk/SWyuswTR1cI/AAAAAAAAABw/MsU7WGjdX5M/s72-c/amdahl%27s%20law.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-3190824526335125693</id><published>2009-01-09T17:53:00.000+11:00</published><updated>2009-01-10T18:10:02.810+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='terracotta'/><category scheme='http://www.blogger.com/atom/ns#' term='memcache'/><category scheme='http://www.blogger.com/atom/ns#' term='distributed cache'/><category scheme='http://www.blogger.com/atom/ns#' term='clustering'/><title type='text'>Terracotta v.s. Memcache</title><content type='html'>&lt;p&gt;В последнее время начали сравнивать Terracotta и Memcache. Мне это сравнение кажется некорректным. Бытует мнение что &lt;a href="http://debasishg.blogspot.com/2008/09/memcached-and-terracotta-alternatives.html"&gt;terracotta - это distributed cache&lt;/a&gt;. Это все равно что называть автомобиль "четырехколесным мопедом", только потому, что он может ездить и у него четыре колеса.&lt;/p&gt;

&lt;p&gt;Terracotta часто определяют как Network attached memory. И это правда, но &lt;a href="http://www.miketec.org/serendipity/index.php?/archives/8-Introduction-to-Terracotta.html"&gt;не вся&lt;/a&gt;. Технически никто не мешает вам использовать terracotta как distributed (или, как его еще иногда называют, clustered) cache. Но тут возникает проблема с HA. В кластере Terracotta всегда есть координатор, который управляет потоком данных. &lt;a href="http://www.infoq.com/interviews/ari-zilka-terracotta"&gt;По словам Ari Zilka&lt;/a&gt; (одного из основателей Terracota Inc.), именно по этой причине terracotta очень хорошо масштабируется, так как благодаря наличию координатора нет необходимости использовать multicast передачу данных. Однако, наличие координатора означает single point of failure. При использовании memcache SPOF нету, но вам необходимо самому распределять данные по нодам.&lt;/p&gt;

&lt;p&gt;Используя terracota вы можете взять LinkedBlockingQueue&amp;lt;Runnable&amp;gt;, пометить его как shared и обрабатывать очередь задач на всех нодах кластера. Вместе с корректной имплементацией распределенных локов мы имеем отличную платформу для распределенных вычислений и возможно даже &lt;a href="http://willcode4beer.com/design.jsp?set=kill_your_db"&gt;хранения данных&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;С кешем все иначе. Его природа имеет несколько особенностей:

&lt;ul&gt;
&lt;li&gt;потерю кеша приложение должно способно пережить (не требуется HA);&lt;/li&gt;
&lt;li&gt;при работе с кешем можно обойтись без атомарных его обновлений (не требуются атомарные примитивы и локи);&lt;/li&gt;
&lt;/ul&gt;

С первым все должно быть понятно. Если приложение не смогло пережить потерю кеша, значит там хранился оригинал каких-либо данных, а следовательно это уже не кеш. Со вторым немного сложнее. Так как значение в кеше зависит только от внешних данных (данных не хранящихся в кеше), значит невозможны операции когда новое значение кеша зависит от его предыдущего значения. А значит не нужны атомарные обновления.&lt;/p&gt;

&lt;p&gt;Memсache отлично подходит для таких сценариев. Ну а если вы решили использовать memcache как storage, а не как кеш, то вы &lt;a href="http://habrahabr.ru/blogs/php/38261/"&gt;обречены на собственную реализацию атомарного обновления данных&lt;/a&gt;, как минимум.&lt;/p&gt;

&lt;p&gt;Таким образом, memcache и terracotta - это совсем разные вещи. Первое - это просто network attached memory сервер. И без дополнительного кодирования его можно использовать только для кеширования данных. Второе - полноценная платформа для кластеризации вычислений. Как говорится, know your enemy.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-3190824526335125693?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/3190824526335125693/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/01/terracotta-vs-memcache.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/3190824526335125693'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/3190824526335125693'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/01/terracotta-vs-memcache.html' title='Terracotta v.s. Memcache'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-7954595419245469168</id><published>2009-01-09T05:21:00.000+11:00</published><updated>2009-01-10T14:23:20.892+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='null-handling'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='devoxxx'/><title type='text'>Enhanced null-handling в Java</title><content type='html'>Как &lt;a href="http://www.jroller.com/scolebourne/entry/jdk_7_language_changes_devoxx"&gt;показало&lt;/a&gt; голосование по вопросу java 7 language changes, null-handling в java - это самый большой "pain in the ass" из всех, через которые Java заставляет проходить программистов. 

С другой стороны то-же голосование на devoxxx по вопросу самого популярного языка под JVM, показало, что это Groovy.
&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://chart.apis.google.com/chart?cht=p&amp;amp;chd=t:49,30,14,10,5,4,5,17&amp;amp;chds=0,49&amp;amp;chs=350x200&amp;amp;chl=Groovy%7CScala%7CJRuby%7CJython%7CFan%7CPHP%7CClojure%7CDon%27t+care&amp;amp;chp=3.14"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 350px; height: 200px;" src="http://chart.apis.google.com/chart?cht=p&amp;amp;chd=t:49,30,14,10,5,4,5,17&amp;amp;chds=0,49&amp;amp;chs=350x200&amp;amp;chl=Groovy%7CScala%7CJRuby%7CJython%7CFan%7CPHP%7CClojure%7CDon%27t+care&amp;amp;chp=3.14" alt="" border="0" /&gt;&lt;/a&gt;Видимо, все те, кто привыкли к операторам &lt;a href="http://groovy.codehaus.org/Operators#Operators-ElvisOperator%28%3F%3A%29"&gt;Элвиса&lt;/a&gt; и &lt;a href="http://groovy.codehaus.org/Operators#Operators-SafeNavigationOperator%28%3F.%29"&gt;safe-navigate&lt;/a&gt; в groovy, пришли на devoxxx и устроили флеш моб.

Вобщем &lt;a href="http://docs.google.com/View?docid=dfn5297z_3c73gwb"&gt;приветствуйте&lt;/a&gt;. &lt;a href="http://docs.google.com/View?docid=dfn5297z_3c73gwb"&gt;Proposal &lt;/a&gt;определяет 2 новых оператора в языке: null-safe и null-default.

Работает так же как и в groovy. Просто и понятно.
&lt;h3&gt;Null-safe operator&lt;/h3&gt;
&lt;pre class="code"&gt;
String a ... ;
String b;

// сегодня
b = a != null
  ? a.substring(10, 2);
  : null;

// завтра
String b = a?.substring(10, 2);
&lt;/pre&gt;

&lt;h3&gt;Null-default operator&lt;/h3&gt;

&lt;pre class="code"&gt;
// сегодня
if ( name == null ) {
  name = "Anonymous";
}

// завтра
name = name ?: "Anonymous";
&lt;/pre&gt;

Лично я ничего против не имею. Давно пора.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-7954595419245469168?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/7954595419245469168/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/01/enhanced-null-handling-java.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/7954595419245469168'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/7954595419245469168'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/01/enhanced-null-handling-java.html' title='Enhanced null-handling в Java'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-3487319308871638719</id><published>2009-01-09T03:33:00.000+11:00</published><updated>2009-01-09T04:35:12.529+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='windows'/><category scheme='http://www.blogger.com/atom/ns#' term='task manager'/><category scheme='http://www.blogger.com/atom/ns#' term='scalability'/><title type='text'>8 на 8</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://software.intel.com/en-us/blogs/wordpress/wp-content/uploads/2009/01/256lp-hpsuperdome.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 852px; height: 694px;" src="http://software.intel.com/en-us/blogs/wordpress/wp-content/uploads/2009/01/256lp-hpsuperdome.jpg" alt="" border="0" /&gt;&lt;/a&gt;&lt;p&gt;Это скриншот с Windows 2008 R2 с запущенным на нем SQLServer.&lt;/p&gt;&lt;p&gt;Примечательна сама загрузка. Загрузить 256 ядер почти на полную одним приложением не так-то просто. У MySQL, например, &lt;a href="http://bugs.mysql.com/bug.php?id=15815"&gt;проблемы начинаются уже с 8 ядрами&lt;/a&gt;. У нас в проекте мы это испытали на собственной шкуре.&lt;/p&gt;&lt;p&gt;Как &lt;a href="http://software.intel.com/en-us/blogs/2009/01/05/what-does-256-cores-look-like/"&gt;заметил &lt;/a&gt;Doug Holland, для того чтобы наблюдать такие полотна графиков, надо иметь по меньшей мере 30" монитор. Task manager плохо масштабируется по колличеству ядер, если вам угодно :).
&lt;/p&gt;&lt;p&gt;Так или иначе тенденция налицо. Да здравствует век параллельных вычислений.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-3487319308871638719?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/3487319308871638719/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2009/01/8-8.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/3487319308871638719'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/3487319308871638719'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2009/01/8-8.html' title='8 на 8'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-5718678684654744326</id><published>2008-11-25T23:52:00.000+11:00</published><updated>2008-11-26T00:01:13.222+11:00</updated><title type='text'>Очередная пачка бреда</title><content type='html'>В очередной раз наткнулся на гениальное сообщение от spring-ws. Дело было при валидации.

&lt;pre&gt;
WARN  XML validation error on request: Attribute 'login'
      is not allowed to appear in element
      'ns2:registerRequest'.
WARN  XML validation error on request: Attribute 'login'
      must appear on element 'ns2:registerRequest'.
&lt;/pre&gt;

После таких сообщений я начиню задумываться, а верно ли я выбрал профессию?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-5718678684654744326?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/5718678684654744326/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2008/11/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/5718678684654744326'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/5718678684654744326'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2008/11/blog-post.html' title='Очередная пачка бреда'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-5381306398507615754</id><published>2008-06-18T00:29:00.000+12:00</published><updated>2008-06-18T00:57:05.130+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><category scheme='http://www.blogger.com/atom/ns#' term='antlr'/><category scheme='http://www.blogger.com/atom/ns#' term='dsl'/><title type='text'>antlr и DSL языки</title><content type='html'>Задача. Есть биллинг, который аккумулирует данные по операциям. Нужно предоставить интерфейс (программный) для получения отчетов. Каждый отчет может быть параметризирован. Например, получить определенный отчет (скажем начисления за месяц) для какого-то конкретного клиента. Список отчетов будет изменятся, будут разрабатываться новые. Учитывая требования, пришел к выводу, что наилучший вариант предоставить клиенту декларативный язык для получения данных. Что-то вроде:
&lt;pre&gt;
build DepositReport
 from '2008-01-01' to '2008-02-01'
 with userId = 15, depositGround = 'cash'
&lt;/pre&gt;
Решил написать грамматику для реализации парсера на &lt;a href="http://www.antlr.org/"&gt;antlr&lt;/a&gt;. К моему удивлению, справился за пол часа, а результирующая грамматика занимала 35 строчек кода. На выходе я имел следующий интерфейс.
&lt;pre&gt;
RQLParser parser = new RQLParser()
ANTLRStringStream input = new ANTLRStringStream(
"build report "+
  "from '2005-01-15' to '2005-02-12' "+
  "with id = 25, subject = 'text'");
RQLLexer lexer = new RQLLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
RQLParser parser = new RQLParser(tokens);
parser.query();

parser.reportName; // имя отчета
parser.dateFrom; // начало диапазона
parser.dateTo; // конец диапазона

/* Список объектов представляющий
  собой правила фильтрации */
parser.criteriaList;

&lt;/pre&gt;Вполне удобно и легко поддается модификации. Поменял грамматику, запустил генератор, - вот тебе и новый парсер.

Пожалуй пора отказываться от регулярок. Достал этот write-only.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-5381306398507615754?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/5381306398507615754/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2008/06/antlr-dsl.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/5381306398507615754'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/5381306398507615754'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2008/06/antlr-dsl.html' title='antlr и DSL языки'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-6466495610900680132</id><published>2008-06-17T01:13:00.000+12:00</published><updated>2008-06-17T02:45:58.803+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ooad'/><category scheme='http://www.blogger.com/atom/ns#' term='programming'/><title type='text'>Чайник и сложность ПО</title><content type='html'>Случайно наткнулся на свой давешний &lt;a href="http://forum.agiledev.ru/index.php?t=msg&amp;amp;goto=5650&amp;amp;#msg_5650"&gt;пост&lt;/a&gt; основной идеей которого, было то, что все программные модели являются суть упрощением обьектов реального мира. И задача программиста упростить модель настолько насколько это возможно, но не больше.

Как жаль, что я сам порой об этом забываю.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-6466495610900680132?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/6466495610900680132/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2008/06/blog-post_5972.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/6466495610900680132'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/6466495610900680132'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2008/06/blog-post_5972.html' title='Чайник и сложность ПО'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-241164918537745539.post-598049382926259733</id><published>2008-06-16T21:49:00.000+12:00</published><updated>2008-06-17T02:15:26.571+12:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='q4m'/><category scheme='http://www.blogger.com/atom/ns#' term='scalability'/><category scheme='http://www.blogger.com/atom/ns#' term='messaging'/><title type='text'>MySQL Queue</title><content type='html'>Случайно нашел интерестное программное решение. &lt;a href="http://q4m.31tools.com/"&gt;Q4M&lt;/a&gt; - иммитация сервера очереди сообщений посредством MySQL сервера (начиная с версии 5.1). Написано что fast, robust и flexible (интерестно, бывает что-нибудь одно?). Однако, дозволяются условные выборки из очереди, несмотря на то, что в limitations указано, - индексы при этом не используются.

Вообще, довольно трудно представить себе где бы могла мне пригодится такая очередь сообщений. Cуществует куча бесплатных специализированных решений, которые создавались специально для обработки очередей сообщений. Взять хотя бы &lt;a href="http://activemq.apache.org/"&gt;ActiveMQ&lt;/a&gt; или JBossMQ. Поддержка транзакционности, оптимизирована для быстрой вставки (в принципе, как и любая другая MQ система), поддержка практически всех популярных скриптовых языков посредством &lt;a href="http://stomp.codehaus.org/"&gt;STOMP&lt;/a&gt;, встроенный &lt;a href="http://activemq.apache.org/camel/"&gt;message router&lt;/a&gt;, поддержка репликации, поддержка "forward on demand bridge" и куча еще чего.

Учитывая то, что MQ системы обычно применяются либо при интеграции приложений либо в целях масштабируемости приложений (что говорит о больших обьемах данных), непонятно зачем нужно решение подобное Q4M. Рано или поздно вы упретесь в пропускную способность sql-сервера по чтению, добавите slave серверов, а затем упретесь в ту  же пропускную способность, но уже по записи. И тут уже никакая репликация не спасет.

Справедливости ради надо отметить, что Q4M предоставлят возможности, которых в других mq системах нет. Например, conditional consuming и join'ы очередей с обыкновенными таблицами! В прицнипе, кому-то может пригодится. Ebay, если верить некоторым &lt;a href="http://natishalom.typepad.com/nati_shaloms_blog/2007/11/architecture-yo.html"&gt;источникам&lt;/a&gt;, использует самописный messaging стек, который поддерживает conditional consuming. Это упрощает использование очереди, да и &lt;a href="http://www.enterpriseintegrationpatterns.com/"&gt;EIP&lt;/a&gt; была бы раза в два тоньше. Вот только за все приходится платить, и за удобство использования тоже. Особенно, когда за очередью прячется реляционная база данных. В данном случае платить придется производительностью, failover'ом и масштабирумостью.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/241164918537745539-598049382926259733?l=dotsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dotsid.blogspot.com/feeds/598049382926259733/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dotsid.blogspot.com/2008/06/blog-post_16.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/598049382926259733'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/241164918537745539/posts/default/598049382926259733'/><link rel='alternate' type='text/html' href='http://dotsid.blogspot.com/2008/06/blog-post_16.html' title='MySQL Queue'/><author><name>Denis Bazhenov</name><uri>https://profiles.google.com/113212801740110086350</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh3.googleusercontent.com/-CWOEH6rrTPY/AAAAAAAAAAI/AAAAAAAAAYA/_nmH0Qyv7gw/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry></feed>
