<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" version="2.0">

<channel>
	<title>Записки искателей</title>
	
	<link>http://voituk.kiev.ua</link>
	<description>while ( isAlive() ) {doCode(); doFun();}</description>
	<lastBuildDate>Wed, 21 Apr 2010 13:29:01 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/researchers" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="researchers" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item>
		<title>MemcacheDB Quick Benchmarks</title>
		<link>http://voituk.kiev.ua/2010/03/03/memcachedb-quick-benchmarks/</link>
		<comments>http://voituk.kiev.ua/2010/03/03/memcachedb-quick-benchmarks/#comments</comments>
		<pubDate>Wed, 03 Mar 2010 16:43:20 +0000</pubDate>
		<dc:creator>Vadim Voituk</dc:creator>
				<category><![CDATA[Development]]></category>

		<guid isPermaLink="false">http://voituk.kiev.ua/?p=1923</guid>
		<description><![CDATA[Эта заметка увидела свет в результате небольшого исследования, проведенного мною в процессе работы на оптимизацией проекта www.savevid.com и призвана поделиться моим скромным опытом миграции на key-value базы дынных (БД) . Предистория состоит в том, что в процессе естественной эволюции кода и оптимизации запросов к MySQL RDBMS,  работа с &#8220;центральной&#8221; таблицей всей схемы БД (как несложно [...]]]></description>
			<content:encoded><![CDATA[<p>Эта заметка увидела свет в результате небольшого исследования, проведенного мною в процессе работы на оптимизацией проекта <a href="http://www.savevid.com/">www.savevid.com</a> и призвана поделиться моим скромным опытом миграции на key-value базы дынных (БД) .<span id="more-1923"></span></p>
<p>Предистория состоит в том, что в процессе естественной эволюции кода и оптимизации запросов к MySQL RDBMS,  работа с &#8220;центральной&#8221; таблицей всей схемы БД (как несложно догадаться &#8211; это таблица videos), свелась к простому алгоритму:</p>
<ol>
<li>выбрать сложным запросом из разных таблиц все video_id, удовлетворяющие нужным критериям</li>
<li>по полученному списку video_id, выбрать информацию о видео (aka SELECT &#8230; FROM videos WHERE id IN (&#8230;))</li>
</ol>
<p>Причиной появления подобного use-case стал внушительный размер той самой videos.  А о катастрофичности любых JOIN-ов на большие (10Gb+) InnoDB-таблицы уже было написано немало. Хотя бы на том же <a href="http://www.mysqlperformanceblog.com/">MysqlPerfomanceBlog.com</a>.</p>
<p>Следовательно, если у нас есть выборки из таблицы ТОЛЬКО по primary key – то это первый кандидат на миграцию в key-value RDBMS.  Что мы, собственно, и пытались опробовать.</p>
<p>Среди массы кандидатов (а их оказалось действительно много), для первоначальных проб выбрали MemcacheDB.<br />
Причины весьма прозаичны, и были надиктованы &#8220;путем наименьшего сопротивления&#8221;:</p>
<ul>
<li>В проекте уже активно используется Memcached, клиент которого уже 100% совместим с MemcacheDB.<br />
Следовательно никаких сторонних библиотек &#8220;тянуть&#8221; в проект не нужно</li>
<li>Реализации клиентов для Memcached/MemcacheDB есть почти под все мыслимые и немыслимые платформы и языки программирования. Тут конечно огромное спасибо Бреду Фицпатрику и его Danga.com.</li>
<li>Memcached клиенты предоставляют базовый развномерный (хоть документация  и говорит о weight-based распределении) шардинг. В результате и MemcacheDB это перенял.</li>
<li>В основе backend-a MemcachedDB лежит BerkleyDB &#8211; одна из старейших и надежнейших key-value RDBMS, которая до сих под активно поддерживается Oracle Corp.</li>
<li>BerkleyDB поддерживает репликацию. И что самое приятное &#8211; как синхронную, так полу-синхронную и асинхронную.</li>
<li>Memcached-клиенты поддерживают сжатие данных, что является немаловажым фактором при использовании дорогостоящих SSD-винчестеров.</li>
</ul>
<p>В результате всего вышесказанного был проведен небольшой performance benchmark test.</p>
<p><strong>Железо:</strong></p>
<p>Использовался тестовый сервер 2 x Core2Quad / 16GB of RAM / 4 x 146Gb SAS HDD in RAID10<br />
MySQL: v5.4.2 , InnoDB, innodb_buffer_pool=6Gb, по максимуму оптимизирован для интерсивной записи.<br />
MemcaheDB: 1Gb of RAM, index type &#8211; hash, skip transaction support</p>
<p><strong>Производительность записи</strong><strong>:</strong></p>
<p>Создание 1 000 000  записей.<br />
MySQL: среднее время записи – 5.6 sec / 10k  (5.6 секунд на 10 000 записей)<br />
MemcacheDB: среднее время записи – 4.1 sec / 10k<br />
При включенном transaction support в MemcacheDB – около 7 sec/ 10k</p>
<p>Как видно MySQL не сильно отстает на записи, но при этом предоставляет полноценный ACID.</p>
<p><strong>Производительность чтения:</strong></p>
<p>Из ранее созданного миллиона записей случайным образом выбиралась одна запись по primary key.<br />
MySQL: среднее время чтения – 1.7 sec / 10K<br />
MemcacheDB: среднее время чтения – 0.7 sec / 10K</p>
<p>Т.е. в данном случае MemcacheDB более чем в 2 раза опережает MySQL.<br />
Думаю если заняться тюннингом первого, то можно этот разрыв увеличить  куда существеннее.</p>
<p><strong>Размер данных:</strong></p>
<p>Соотношение размеров файлов с данными (и индексами) получилось 1.5Gb  (MemcacheDB) против 2.3Gb (MySQL), т.е.  в 1.5 раза в пользу MemcacheDB.<br />
Сравнение проводилось без использования сжатия в MemcacheDB и без использования MySQL ROW_FORMAT=COMPRESSED.</p>
<p>Вот такой вот небольшой sidenote вышел. В результате, пока еще мне не совсем понятно нужно ли оно мне или нет.<br />
Если все-таки решеним испольовать MemcacheDB в production environment -  обязательно отпишусь о результатах.</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/researchers?a=tBOPV17h1jA:9uzK_88SjAs:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/researchers?i=tBOPV17h1jA:9uzK_88SjAs:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/researchers?a=tBOPV17h1jA:9uzK_88SjAs:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/researchers?i=tBOPV17h1jA:9uzK_88SjAs:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://voituk.kiev.ua/2010/03/03/memcachedb-quick-benchmarks/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Hello World на Google App Engine</title>
		<link>http://voituk.kiev.ua/2010/02/15/hello-world-google-app-engine/</link>
		<comments>http://voituk.kiev.ua/2010/02/15/hello-world-google-app-engine/#comments</comments>
		<pubDate>Mon, 15 Feb 2010 11:38:49 +0000</pubDate>
		<dc:creator>juriy</dc:creator>
				<category><![CDATA[Google]]></category>
		<category><![CDATA[Java]]></category>

		<guid isPermaLink="false">http://voituk.kiev.ua/?p=1633</guid>
		<description><![CDATA[Небольшая заметка о том, как создать и разместить свой проект на сервисе Google App Engine. По &#8220;долгу службы&#8221; довелось пощупать интересную технологию, до которой раньше не доходили руки. Думаю, Google App Engine будет интересен не только мне, поэтому начинаю мини-цикл статей о разработке для App Engine.  После того, как вы впервые войдёте в сервис appengine, [...]]]></description>
			<content:encoded><![CDATA[<p>Небольшая заметка о том, как создать и разместить свой проект на сервисе Google App Engine.</p>
<p>По &#8220;долгу службы&#8221; довелось пощупать интересную технологию, до которой раньше не доходили руки. Думаю, Google App Engine будет интересен не только мне, поэтому начинаю мини-цикл статей о разработке для App Engine.  <span id="more-1633"></span></p>
<p>После того, как вы впервые войдёте в сервис appengine, вам нужно будет подтвердить свою &#8220;личность&#8221;. Google вышлет на номер указанного вами телефона SMS с текстом вроде Google App Engine Code: 1234567. Лично мне SMSка дошла меньше чем за минуту (такой способ куда лучше бумажной почты, которую Google использует для Adsense). Такая проверка проводится только для первого создаваемого приложения.</p>
<p>Следующий шаг &#8211; зарегистрировать имя-идентификатор для приложения.</p>
<p>Теперь регистрация окончена, приступаем к разработке. App Engine работает на Java 6 и именно эта платформа является рекомендуемой, хоть и Java 5 тоже поддерживается. Я навскидку не смог придумать, что может пойти не так, если приложение скомпилированное на Java 5 запустить на JVM шестой версии, но Google&#8217;у виднее.</p>
<p>Скачиваем и распаковываем Google App Engine SDK для Java <a href="http://code.google.com/appengine/downloads.html">http://code.google.com/appengine/downloads.html</a>.</p>
<p>Теперь дело за малым &#8211; написать простое приложение и отправить на сервер :-)</p>
<p>Тут Google оставил меня без материала для заметки, поскольку минимальный Hello World уже есть в комплекте SDK. Хранится он в папке demos\new_project_template. Тут есть и build.xml и статический контент, и сервлет и даже пара файлов с конфигурацией для самых распространённых logging API: Log4J и JUL (java.util.logging). Скопировав содержимое в папку, где будет жить ваш проект, вы получите отличный каркасс для начала разработки. Единственное, что нужно теперь сделать &#8211; указать путь к развёрнутому SDK: либо в параметрах при запуске ant либо прямо в build.xml.</p>
<p>Перед тем, как запустить приложение на локальном &#8220;эмуляторе&#8221; appengine, давайте посмотрим, что ещё есть в &#8220;комплекте&#8221;. Кроме стандартного web.xml в WEB-INF лежит файл appengine-web.xml &#8211; дополнительная конфигурация для движка appengine:</p>
<pre><code>&lt;appengine-web-app xmlns="http://appengine.google.com/ns/1.0"&gt;
 &lt;!-- Replace this with your application id
    from http://appengine.google.com --&gt;
 &lt;application&gt;juriytest&lt;/application&gt;
 &lt;version&gt;1&lt;/version&gt;
&lt;/appengine-web-app&gt;
</code></pre>
<p>Как и следует из комментария, в теге application нужно указать id приложения, котрый был зарегистрирован на appengine.</p>
<p>Кроме того в src/META-INF находится файл jdoconfig.xml, который является (неожиданно) конфигурацией для JDO &#8211; persistance механизма, который используется в среде appengine. Сейчас его трогать не будем, пускай живёт, hello world и без него запустится.<br />
В проекте есть файл index.html, как пример статичного контента и HelloAppEngineServlet.java, который является привычным сервлетом без экзотики.</p>
<p>Чтобы запустить приложение локально выполняем ant runserver из корня тестового проекта.<br />
Для того, чтобы &#8220;доставить&#8221; приложение на живую платформу нужно использовать утилиту из SDK: bin\appcfg.cmd</p>
<p>appcfg.cmd update www</p>
<p>www тут &#8211; путь к &#8220;развёрнутому&#8221; проекту. Appengine не поддерживает war файлы, а вместо этого работает с директориями с той же структурой. После того, как build.xml собрал для нас проект в папку www указанной выше командой можно загрузить его на appengine.</p>
<p>Пара полезных моментов и &#8220;граблей&#8221;.</p>
<p>App Engine сохраняет всё что вы напишите в System.out и System.err в логи. При этом e.printStackTrace в логи упорно не попадает. Немного сбивает с толку.</p>
<p>Логи в административном интерфейсе обновляются не моментально, а только через несколько секунд.</p>
<p>Последние грабли стоили жизни моим нервным клеткам. В конфиге appengine указывается версия проекта. Не надейтесь, что если будет указана версия выше текущей развёрнутой, то новая версия заменит старую. Всё обстоит совершенно не так. Новая версия затирает развёрнутую версию с <em>таким же</em> индексом. Затем, пользователь выбирает, какая из версий будет доступна по умолчанию. То есть, я могу развернуть версии 1, 2, 3. После разворачивания версии 3, пользователи видели только версию 1, пока в интерфейсе администратора не будет указана другая.</p>
<p>На этом пока что всё. В следующей заметке опишу, как заставить app engine работать с groovy.</p>
<p>__________<br />
Спонсор заметки: &#8220;Идеал&#8221;, <a href="http://ideal.kiev.ua/">раскрутка сайта</a> в Украине</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/researchers?a=wv786xZuCm0:n16O4fpJOHw:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/researchers?i=wv786xZuCm0:n16O4fpJOHw:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/researchers?a=wv786xZuCm0:n16O4fpJOHw:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/researchers?i=wv786xZuCm0:n16O4fpJOHw:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://voituk.kiev.ua/2010/02/15/hello-world-google-app-engine/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Corrected Ukrainian Keyboard Layout For MacBook</title>
		<link>http://voituk.kiev.ua/2010/02/12/corrected-ukrainian-keyboard-layout-for-macbook/</link>
		<comments>http://voituk.kiev.ua/2010/02/12/corrected-ukrainian-keyboard-layout-for-macbook/#comments</comments>
		<pubDate>Fri, 12 Feb 2010 15:06:44 +0000</pubDate>
		<dc:creator>crud</dc:creator>
				<category><![CDATA[Mac OS X]]></category>

		<guid isPermaLink="false">http://voituk.kiev.ua/?p=1663</guid>
		<description><![CDATA[Украинская расскладка клавиатуры (переставленны местами значения клавиш Ы &#8211; И), как в Майрософт ПиСи :) Файлы ниже нужно скопировать в папки /Library/Keyboard Layouts (в этом случае все пользователи ПК будут иметь доступ к этой раскладке) или в /Users/имя_пользователя/Library/Keyboard Layouts (доступ к расскладке будет иметь только указанный юзер). После копирования файла. Заходим в System Preferences -&#62; [...]]]></description>
			<content:encoded><![CDATA[<p>Украинская расскладка клавиатуры (переставленны местами значения клавиш Ы &#8211; И), как в Майрософт ПиСи :)<br />
Файлы ниже нужно скопировать в папки <strong>/Library/Keyboard Layouts</strong> (в этом случае все пользователи ПК будут иметь доступ к этой раскладке) или в <strong>/Users/имя_пользователя/Library/Keyboard Layouts</strong> (доступ к расскладке будет иметь только указанный юзер).<br />
После копирования файла. Заходим в <strong>System Preferences -&gt; Language &amp; Text -&gt; Input Sources</strong> находим имя нашей раскладки (в нашем случае это Ukrainian-crud) и &#8220;оптичиваем ее&#8221; заменяя старую Ukrainian.</p>
<p>Загрузить файлы раскладки: <a href="http://voituk.kiev.ua/wp-content/uploads/Ukrainian_crud_remap.icns">Ukrainian_crud_remap.icns</a> и <a href="http://voituk.kiev.ua/wp-content/uploads/Ukrainian_crud_remap.keylayout">Ukrainian_crud_remap.keylayout</a></p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/researchers?a=dIxrfA6a4hg:RHCPWiwpH8E:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/researchers?i=dIxrfA6a4hg:RHCPWiwpH8E:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/researchers?a=dIxrfA6a4hg:RHCPWiwpH8E:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/researchers?i=dIxrfA6a4hg:RHCPWiwpH8E:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://voituk.kiev.ua/2010/02/12/corrected-ukrainian-keyboard-layout-for-macbook/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Разминка для хвоста</title>
		<link>http://voituk.kiev.ua/2009/12/02/razminka-dlya-xvosta/</link>
		<comments>http://voituk.kiev.ua/2009/12/02/razminka-dlya-xvosta/#comments</comments>
		<pubDate>Tue, 01 Dec 2009 23:01:49 +0000</pubDate>
		<dc:creator>juriy</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Задачки]]></category>

		<guid isPermaLink="false">http://voituk.kiev.ua/?p=1303</guid>
		<description><![CDATA[Ух, как же давно ничего не писал в блог! Отчасти тому виной твиттер. Я обязательно напишу отдельную заметку об этом &#8220;убийце блогов&#8221;. А сейчас, хочу представить вам пару свеженьких Java задачек. 1. Не изменяя код первго метода сделать так, чтобы программа компилировалась и печатала &#8220;This is right&#8221;. У этой задачки, как минимум, 3 решения. Комментировать [...]]]></description>
			<content:encoded><![CDATA[<p>Ух, как же давно ничего не писал в блог! Отчасти тому виной твиттер. Я обязательно напишу отдельную заметку об этом &#8220;убийце блогов&#8221;. А сейчас, хочу представить вам пару свеженьких Java задачек.<br />
<span id="more-1303"></span><br />
1. Не изменяя код первго метода сделать так, чтобы программа компилировалась и печатала &#8220;This is right&#8221;. У этой задачки, как минимум, 3 решения. Комментировать код первого метода тоже нельзя.</p>
<pre>
public class A {

	public static void main(String[] args) {
		System.out.println("This is wrong");
	}

	public static void main(String[] args) {
		System.out.println("This is right");
	}
}
</pre>
<p>2. Написать стандартный Hello World на Java. При этом нельзя использовать ни одной точки с запятой. Закодированные UTF символы тоже использовать нельзя.</p>
<p>Ответы я опубликую через сутки, а пока что жду комментариев.</p>
<p>P.S. За мной уже закрепилась слава человека, обожающего абсолютно бесполезные, с практической точки зрения, задачи. Я пришел к выводу, что для таких &#8220;упражнений&#8221; слово &#8220;задача&#8221; совсем не подходит. Все привыкли, что &#8220;задачи&#8221; это упражнения, которые развивают какие-то практические навыки. К примеру &#8220;реализовать генератор случайных чисел на Java&#8221; это задачка. А для того, что публикую я, лучше подходит слово &#8220;загадка&#8221;. Так что вот вам свежая &#8220;без окон без дверей полна горница людей&#8221;.</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/researchers?a=oMx25BhA3lg:Fczfm7BlZXU:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/researchers?i=oMx25BhA3lg:Fczfm7BlZXU:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/researchers?a=oMx25BhA3lg:Fczfm7BlZXU:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/researchers?i=oMx25BhA3lg:Fczfm7BlZXU:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://voituk.kiev.ua/2009/12/02/razminka-dlya-xvosta/feed/</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
		<item>
		<title>Mac OS X: Usefull tips &amp; hints 2</title>
		<link>http://voituk.kiev.ua/2009/11/18/mac-os-x-usefull-tips-hints-2/</link>
		<comments>http://voituk.kiev.ua/2009/11/18/mac-os-x-usefull-tips-hints-2/#comments</comments>
		<pubDate>Wed, 18 Nov 2009 15:53:35 +0000</pubDate>
		<dc:creator>Vadim Voituk</dc:creator>
				<category><![CDATA[Mac OS X]]></category>

		<guid isPermaLink="false">http://voituk.kiev.ua/?p=1087</guid>
		<description><![CDATA[Следующая порция советов по обустройству вашей комфортной жизни в Mac OS X. Первая часть доступна тут. 1. Указать место для складирования ScreenShots (по умолчанию &#8211; ~/Desktop ) (те что создаются системным shortcut-ом Command+Shift+4 &#38; Command+Shift+3) vadim@Voituk:~&#62;defaults write com.apple.screencapture location ~/Pictures/Screenshots vadim@Voituk:~&#62;killall SystemUIServer Аналогично меняется и формат скриншотов (злые языки говорят что поддерживается png, jpg, tiff, [...]]]></description>
			<content:encoded><![CDATA[<p>Следующая порция советов по обустройству вашей комфортной жизни в Mac OS X.<br />
Первая часть <a href="http://voituk.kiev.ua/2009/06/11/usefull-max-os-hints/">доступна тут</a>.</p>
<p>1. Указать место для складирования ScreenShots (по умолчанию &#8211; ~/Desktop )<br />
(те что создаются системным shortcut-ом Command+Shift+4 &amp; Command+Shift+3)</p>
<p>vadim@Voituk:~&gt;defaults write com.apple.screencapture location ~/Pictures/Screenshots<br />
vadim@Voituk:~&gt;killall SystemUIServer</p>
<p>Аналогично меняется и формат скриншотов (злые языки говорят что поддерживается png, jpg, tiff, pdf):</p>
<p>vadim@Voituk:~&gt;defaults write com.apple.screencapture type jpg<br />
vadim@Voituk:~&gt;killall SystemUIServer</p>
<p>2. Иногда при перезагрузке (часто если подключен внешний монитор) пропадает Bluetooth.<br />
Решение сродни &#8220;давайте выйдем и зайдем &#8211; авось заведется&#8221; &#8211; выключаю Мак, отключаю все внешние устройства, вынимаю батарею.<br />
Жду минуты 2-3 и включаю.<br />
Время &#8220;простоя&#8221; использую для протирания ноутбука влажной салфеткой :)</p>
<p>3. Поначалу очень непривычно было, что в column mode Finder, в списках, директории и файлы идут вперемешку.<br />
Частично эту проблему можно решить, нажав Command+J и выбрав &#8220;Arrange by Kind&#8221;.<br />
В результате  директории будут в списке рядом, но не всегда в начале списка.<br />
Окончательно можно решить проблему &#8220;грязным хаком&#8221;: сделать чтоб при сортировке по типу, директория (тип Folder) шла самой первой.</p>
<p>Для этого открываем файл<strong><br />
/System/Library/CoreServices/Finder.app/Contents/Resources/English.lproj/InfoPlist.strings</strong><br />
и сразу за комментарием /* General kind strings */ меняем строку<br />
<strong>&#8220;Folder&#8221; = &#8220;Folder&#8221;;</strong><br />
на<br />
<strong>&#8220;Folder&#8221; = &#8221; Folder&#8221;; </strong><br />
(с пробелом перед буквой &#8220;F&#8221;)</p>
<p>После редактирования перезапускаем &#8220;Finder&#8221;. /me счастлив</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/researchers?a=kpWaNN4VtF8:t1QUI567SH4:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/researchers?i=kpWaNN4VtF8:t1QUI567SH4:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/researchers?a=kpWaNN4VtF8:t1QUI567SH4:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/researchers?i=kpWaNN4VtF8:t1QUI567SH4:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://voituk.kiev.ua/2009/11/18/mac-os-x-usefull-tips-hints-2/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Useful TODO-list service – GMail Hack</title>
		<link>http://voituk.kiev.ua/2009/11/06/useful-todo-list-service-gmail-hack/</link>
		<comments>http://voituk.kiev.ua/2009/11/06/useful-todo-list-service-gmail-hack/#comments</comments>
		<pubDate>Fri, 06 Nov 2009 09:44:29 +0000</pubDate>
		<dc:creator>Vadim Voituk</dc:creator>
				<category><![CDATA[Разное]]></category>

		<guid isPermaLink="false">http://voituk.kiev.ua/?p=1113</guid>
		<description><![CDATA[Самые неотложные дела очень удобно записывать на желтых стикерах, и клеить на монитор. Единственный минус &#8211; через 3-4 месяца они отваливаются. (с) Народная шутка За последние несколько лет я перепробовал порядка 2х десятков разных сервисов для формирования TODO-листов. Больше 2х дней ни одим не пользовался, и во всех случах по одной причине &#8211; о списке [...]]]></description>
			<content:encoded><![CDATA[<blockquote style="text-align:right; font-style:italic; "><p>Самые неотложные дела очень удобно записывать на желтых стикерах, и клеить на монитор.<br />
Единственный минус &#8211; через 3-4 месяца они отваливаются.<br />
(с) Народная шутка</p></blockquote>
<p>За последние несколько лет я перепробовал порядка 2х десятков разных сервисов для формирования TODO-листов. Больше 2х дней ни одим не пользовался, и во всех случах по одной причине &#8211; о списке забывается, если он не находится постояно перед глазами.<br />
Для примера, сегодня я нашел записи полу-годичной (!!!) давности в своем GMail Tasklist :) Тут уже и шутка из эпиграфа статьи превращается совсем не в шутку.<br />
И это GMail, которым я пользуюсь ежедневно! А что уже говорить про всякие &#8220;оторванные&#8221; от рабочего процесса TadaList, TooDoo и им подобные?</p>
<p>В результате уже больше года я вынашиваю идею <span style="text-decoration: line-through;">взять Юру, пива и за вечер налабать</span> реализовать нормальный удобный web-ориентированный TODO-лист, который можно будет спокойно загрузить в sidebar браузера и он всегда будет на виду.</p>
<p>Уже даже начал писать functional overview и requirements specification, и наверное даже через недельку-две уже и приступил бы к реализации, если бы не сегодняшний утренний твит <a href="http://twitter.com/maxua/status/5457812503">Макса Ищенко</a>:</p>
<p><img class="alignnone size-full wp-image-1173" title="sreenshot" src="http://voituk.kiev.ua/wp-content/uploads/2009/11/Picture-56.png" alt="sreenshot" width="572" height="207" /></p>
<p>После чего необходимость реализовывать что-то свое попросту отпало &#8211; все уже <span style="text-decoration: line-through;">украдено</span> изобрели до нас:<br />
Открываем Firefox, создаем новый Bookmark на видимой панели закладок, называем его TODO (ну или по желанию), как URL указываем &#8220;http://mail.google.com/tasks/ig?pli=1&#8243;, ставим галочку &#8220;Load this bookmark in the sidebar&#8221; &#8211; и вуаля!</p>
<p>Получится что-то вроде:</p>
<p><img class="alignnone size-full wp-image-1193" title="Gmail Tasks Screenshot" src="http://voituk.kiev.ua/wp-content/uploads/2009/11/Picture-57.png" alt="Gmail Tasks Screenshot" width="574" height="345" /></p>
<p>Т.е. на 100% то &#8211; что я искал.</p>
<p>Единственное, что смущает &#8211; не придумал как перенести этот sidebar  на правую сторону браузера, но думаю это решаемо.</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/researchers?a=hzbRK6vAAlQ:qNv4giJWTTA:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/researchers?i=hzbRK6vAAlQ:qNv4giJWTTA:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/researchers?a=hzbRK6vAAlQ:qNv4giJWTTA:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/researchers?i=hzbRK6vAAlQ:qNv4giJWTTA:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://voituk.kiev.ua/2009/11/06/useful-todo-list-service-gmail-hack/feed/</wfw:commentRss>
		<slash:comments>20</slash:comments>
		</item>
		<item>
		<title>MySQL: Slave could not parse relay log event entry</title>
		<link>http://voituk.kiev.ua/2009/09/17/mysql-slave-could-not-parse-relay-log-event-entry/</link>
		<comments>http://voituk.kiev.ua/2009/09/17/mysql-slave-could-not-parse-relay-log-event-entry/#comments</comments>
		<pubDate>Thu, 17 Sep 2009 08:09:31 +0000</pubDate>
		<dc:creator>Vadim Voituk</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[MySQL]]></category>

		<guid isPermaLink="false">http://voituk.kiev.ua/?p=1079</guid>
		<description><![CDATA[При использованиии MySQL Master-Slave (или же Master-Master) репликации бывает ситуация что по разным причинам версия слейва ниже чем версия мастера. Если при этом используется опция binlog_format=MIXED, в результате возможна ситуация когда мастер запишет в свой binlog запись вида: BINLOG 'XMawShMEAAAARwAAAEp5UwUAAGkAAAAAAAAACHNhblahblahblah' Которую слейв не сможет выполнить, ибо он ее попросту &#8220;еще не умеет&#8221;. Первой ласточкой того, [...]]]></description>
			<content:encoded><![CDATA[<p>При использованиии MySQL Master-Slave (или же Master-Master) репликации бывает ситуация что по разным причинам версия слейва ниже чем версия мастера.<br />
Если при этом используется опция <em>binlog_format=MIXED</em>, в результате возможна ситуация когда мастер запишет в свой binlog запись вида:<br />
<code>BINLOG 'XMawShMEAAAARwAAAEp5UwUAAGkAAAAAAAAACHNhblahblahblah'</code><br />
Которую слейв не сможет выполнить, ибо он ее попросту &#8220;еще не умеет&#8221;.</p>
<p>Первой ласточкой того, что это произошло станет сообщение &#8220;SLAVE DOWN&#8221; от мониторинговой системы и запись в MySQL-логе слейва сродни этой:</p>
<p><code>090917 14:55:51 [ERROR] Error in Log_event::read_log_event(): 'Found invalid event in binary log', data_len: 71, event_type: 19<br />
090917 14:55:51 [ERROR] Error reading relay log event: slave SQL thread aborted because of I/O error<br />
090917 14:55:51 [ERROR] Slave: Could not parse relay log event entry. The possible reasons are: the master's binary log is corrupted (you can check this by running 'mysqlbinlog' o<br />
n the binary log), the slave's relay log is corrupted (you can check this by running 'mysqlbinlog' on the relay log), a network problem, or a bug in the master's or slave's MySQL<br />
code. If you want to check the master's binary log or slave's relay log, you will be able to know their names by issuing 'SHOW SLAVE STATUS' on this slave. Error_code: 0<br />
090917 14:55:51 [ERROR] Error running query, slave SQL thread aborted. Fix the problem, and restart the slave SQL thread with "SLAVE START". We stopped at log 'bin-us1.000022' position 17007149<br />
</code></p>
<p>Попытки пропустить неугодные записи старым добрым методом:<br />
<code>slave stop;<br />
set global sql_slave_skip_counter=1;<br />
slave start;</code><br />
в данном случае не помогают.</p>
<p>Как вариант решения предлагаю:</p>
<ol>
<li>Останавливаем slave: <em>&#8220;SLAVE STOP;&#8221;</em></li>
<li>&#8220;Выуживаем&#8221; из текста ошибки имя бинлога и позицию, на которой произошла ошибка.<br />
В данном случае это <em>bin-us1.000022</em> и позиция <em>17007149</em></li>
<li>На мастере выполняем запрос <em>&#8220;FLUSH LOGS;&#8221;</em></li>
<li>Делаем дамп бинлога начиная с найденной позиции:<br />
<code>mysqlbinlog --start-position=17007149 bin-us1.000022  &gt;  bin-us1-from17007149.sql</code></li>
<li>Загружаем полученный дамп на слейве с ключем &#8220;&#8211;force&#8221;<br />
<code>mysql --force -uuser -ppassword database &lt;  bin-us1-from17007149.sql</code></li>
<li>Выполняем на слейве &#8220;FLUSH LOGS;&#8221;</li>
<li>В data-директории находим relay-log с максимальным номером и размером около 100 байт (в моем случае это mysqld-relay-bin.000037)</li>
<li>В той же директории в файле relay-log.info<br />
первую строку заменяем на имя только что найденного relay-log-а, во вторую вписываем 0, в третью &#8211; следующий после &#8220;испорченного&#8221; бинлог, в четвертую &#8211; 0<br />
Получится что-то вроде:<br />
<code>/var/lib/mysql/mysqld-relay-bin.000037<br />
0<br />
bin-us1.000023<br />
0</code></li>
<li>Дальше на слейве выполняем:<br />
<code>CHANGE MASTER TO MASTER_LOG_FILE='bin-us1.000023', MASTER_LOG_POS=0;<br />
SLAVE START;</code></li>
<li>Ну и потом чтоб удостоверится что все &#8220;завелось&#8221;<br />
<code>SHOW SLAVE STATUS\G</code></li>
</ol>
<p>Должно работать.<br />
Подозреваю что пункты 4 и 5 можно обьеденить в один, более простой и элегантный.<br />
Но как это сделать &#8211; не гуглил.</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/researchers?a=C-Ls-K90sJI:4FaFA7ZM8QU:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/researchers?i=C-Ls-K90sJI:4FaFA7ZM8QU:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/researchers?a=C-Ls-K90sJI:4FaFA7ZM8QU:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/researchers?i=C-Ls-K90sJI:4FaFA7ZM8QU:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://voituk.kiev.ua/2009/09/17/mysql-slave-could-not-parse-relay-log-event-entry/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Code WTF: JavaScript HTML entities</title>
		<link>http://voituk.kiev.ua/2009/07/02/javascript-html-entities/</link>
		<comments>http://voituk.kiev.ua/2009/07/02/javascript-html-entities/#comments</comments>
		<pubDate>Thu, 02 Jul 2009 08:49:43 +0000</pubDate>
		<dc:creator>Vadim Voituk</dc:creator>
				<category><![CDATA[Ajax]]></category>
		<category><![CDATA[Юмор]]></category>

		<guid isPermaLink="false">http://voituk.kiev.ua/?p=1075</guid>
		<description><![CDATA[Извините за &#8220;неформат&#8221; для данного блога, но не смог удержаться. Вот так вот мой &#8220;юннат&#8221; реализует замену html-entities на JavaScript: function properHtmlFromText(text){ while (text.search(/\&#60;/) != -1) text = text.replace(/\&#60;/, '&#38;lt;'); while (text.search(/\&#38;/) != -1) text = text.replace(/\&#38;/, '&#38;amp;'); while (text.search(/\r/) != -1) text = text.replace(/\r/, ''); while (text.search(/\n/) != -1) text = text.replace(/\n/, '&#60;br /&#62;'); [...]]]></description>
			<content:encoded><![CDATA[<p>Извините за &#8220;неформат&#8221; для данного блога, но не смог удержаться.<br />
Вот так вот мой &#8220;юннат&#8221; реализует замену html-entities на JavaScript:</p>
<pre><code>
function properHtmlFromText(text){
	while (text.search(/\&lt;/) != -1)
		text = text.replace(/\&lt;/, '&amp;lt;');
	while (text.search(/\&amp;/) != -1)
		text = text.replace(/\&amp;/, '&amp;amp;');
	while (text.search(/\r/) != -1)
		text = text.replace(/\r/, '');
	while (text.search(/\n/) != -1)
		text = text.replace(/\n/, '&lt;br /&gt;');
	return text;
}
</code></pre>
<p>Как увидел &#8211; плакал&#8230;<br />
Наверное все-таки будем возобновлять ежедневные code-review&#8230;</p>
<p>P.S. В комментариях предлагаю поделиться &#8220;правильным&#8221; решением :)</p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/researchers?a=_pA8E5afOX8:lp_tbKl1XsM:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/researchers?i=_pA8E5afOX8:lp_tbKl1XsM:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/researchers?a=_pA8E5afOX8:lp_tbKl1XsM:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/researchers?i=_pA8E5afOX8:lp_tbKl1XsM:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://voituk.kiev.ua/2009/07/02/javascript-html-entities/feed/</wfw:commentRss>
		<slash:comments>17</slash:comments>
		</item>
		<item>
		<title>MySQL: Single row lock implementation</title>
		<link>http://voituk.kiev.ua/2009/06/24/mysql-single-row-lock-implementation/</link>
		<comments>http://voituk.kiev.ua/2009/06/24/mysql-single-row-lock-implementation/#comments</comments>
		<pubDate>Wed, 24 Jun 2009 14:01:35 +0000</pubDate>
		<dc:creator>Vadim Voituk</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[MySQL]]></category>

		<guid isPermaLink="false">http://voituk.kiev.ua/?p=1066</guid>
		<description><![CDATA[При разработке приложений с использованием баз данных, часто возникает сценарий &#8220;insert if not exists&#8221;: if ( &#60;row exists in table&#62; ) then &#60;insert new record into table&#62; Из-за того, что данный блок псевдо-кода не выполяняется атомано, может возникнуть ситуация, когда между проверкой &#60;row exists in table&#62; и выполнением действия &#60;insert new record&#62; паралельный поток (клиент)  [...]]]></description>
			<content:encoded><![CDATA[<p>При разработке приложений с использованием баз данных, часто возникает сценарий &#8220;insert if not exists&#8221;: </p>
<pre><code class="sql">if ( &lt;row exists in table&gt; )
   then &lt;insert new record into table&gt;</code></pre>
<p>Из-за того, что данный блок псевдо-кода не выполяняется атомано, может возникнуть ситуация, когда между проверкой <em>&lt;row exists in table&gt;</em> и выполнением действия <em>&lt;insert new record&gt;</em> паралельный поток (клиент)  выполнит добавление новой записи и в итоге в БД окажется 2 одинаковых записи (или произойдет ошибка &#8220;duplicate key&#8221;).</p>
<p>Такой, не самый приятный case, в книгах по паралельному программированию называют race condition и обходят путем создания синхронизирующих блокировок , использованием &#8220;выпрямителей&#8221; и тд.<br />
<span id="more-1066"></span><br />
С точки зрения MySQL надо бы переписать приведенный псевдокод в таком виде:</p>
<pre><code class="sql">mysql_query('LOCK TABLE `table` WRITE')

if ( &lt;row exists in table&gt; )
   then &lt;insert new record into table&gt;

mysql_query('UNLOCK TABLES') </code></pre>
<p>В таком случае таблица будет заблокирована для записи и мы будем уверены что во время выполнения нашего кода, никто, кроме текущего потока, ничего в таблицу не &#8220;дозапишет&#8221; и race condition не возникнет.</p>
<p>Способ вроде простой, и если мне не изменяет память, даже рекомендовался в какой-то книге по разработке web-приложений.<br />
Самый же существенный его минус &#8211; он блокирует <strong>ВСЮ</strong> таблицу и все запросы на запись в эту таблицу будут ждать окончания выполнения блока кода и вызова &#8220;UNLOCK TABLES&#8221;.<br />
Это, в условиях интенсивной записи в таблицу, может создать весьма критичный bottleneck в производительности всей системы.</p>
<p>Очень неплохо бы в таком случае выполнять блокировку на уровне не всей таблицы, а только определенной строки. Немного покопавшись в мануале MySQL, отыскал несколько встроенных функций, которые помогают это реализовать:</p>
<dl>
<dt><a href="http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock" rel="nofollow">GET_LOCK(str, timeout)</a></dt>
<dd>Создает глобальный мютекс с именем <em>str</em> и временем жизни <em>timeout</em>. При попытке другого потока &#8220;занять&#8221; уже созданный мютекс, он будет ожидать пока он не освободится вызовом RELEASE_LOCK() или не пройдет <em>timeout</em> секунд</dd>
<dt><a href="http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock" rel="nofollow">RELEASE_LOCK(str)</a></dt>
<dd>Освобождает глобальный мютекс с именем <em>str</em></dd>
</dl>
<p>Кроме того есть еще 2 вспомогательные фунцкции для проверки создан ли уже мютекс, и если создан, то каким ProcessID: <a href="http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_is-free-lock" rel="nofollow">IS_FREE_LOCK()</a> и <a href="http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_is-used-lock" rel="nofollow">IS_USED_LOCK()</a></p>
<p>С учетом вышесказанного описанный выше псевдокод, с использованием row-based блокировки будет выглядеть таким образом:</p>
<pre><code class="sql">mysql_query('DO GET_LOCK("my-prefix.my-row-id", 60)')

if ( &lt;row exists in table&gt; )
   then &lt;insert new record into table&gt;

mysql_query('DO RELEASE_LOCK("my-prefix.my-row-id")') </code></pre>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/researchers?a=8DeG588nH_Q:x8_ksTjA4eQ:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/researchers?i=8DeG588nH_Q:x8_ksTjA4eQ:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/researchers?a=8DeG588nH_Q:x8_ksTjA4eQ:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/researchers?i=8DeG588nH_Q:x8_ksTjA4eQ:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://voituk.kiev.ua/2009/06/24/mysql-single-row-lock-implementation/feed/</wfw:commentRss>
		<slash:comments>25</slash:comments>
		</item>
		<item>
		<title>Using iptables to prevent SSH brute force</title>
		<link>http://voituk.kiev.ua/2009/06/21/using-iptables-to-prevent-ssh-brute-force/</link>
		<comments>http://voituk.kiev.ua/2009/06/21/using-iptables-to-prevent-ssh-brute-force/#comments</comments>
		<pubDate>Sun, 21 Jun 2009 14:34:37 +0000</pubDate>
		<dc:creator>Vadim Voituk</dc:creator>
				<category><![CDATA[Development]]></category>

		<guid isPermaLink="false">http://voituk.kiev.ua/?p=1061</guid>
		<description><![CDATA[Себе на заметку, дабы потом опять не гуглить. Эти 2 простых iptables-правила спасают сервер от brute force перебора пароля от SSH: iptables -I INPUT -i eth0 -p tcp -m tcp --dport 22 -m state --state NEW -m recent --set --name DEFAULT --rsourceiptables -I INPUT -i eth0 -p tcp -m tcp --dport 22 -m state --state [...]]]></description>
			<content:encoded><![CDATA[<p><em>Себе на заметку, дабы потом опять не гуглить.</em><br />
Эти 2 простых iptables-правила спасают сервер от brute force перебора пароля от SSH:<br />
<code>iptables -I INPUT -i eth0 -p tcp -m tcp --dport 22 -m state --state NEW -m recent --set --name DEFAULT --rsource</code><code>iptables -I INPUT -i eth0 -p tcp -m tcp --dport 22 -m state --state NEW -m recent --update --seconds 180 --hitcount 5 --name DEFAULT --rsource -j DROP</code><br />
В результате те, кто делает более 5-ти попыток SSH-подключений за последние 3 минуты &#8211; блокируются на 3 минуты.<br />
Для того, чтоб эти правила запускались при старте системы делаем dump rules-файла:<br />
<code>iptables-save  > /etc/sysconfig/iptables</code></p>
<div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/researchers?a=kXTtSqKdqGg:ijJ0Jjh1x2M:D7DqB2pKExk"><img src="http://feeds.feedburner.com/~ff/researchers?i=kXTtSqKdqGg:ijJ0Jjh1x2M:D7DqB2pKExk" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/researchers?a=kXTtSqKdqGg:ijJ0Jjh1x2M:F7zBnMyn0Lo"><img src="http://feeds.feedburner.com/~ff/researchers?i=kXTtSqKdqGg:ijJ0Jjh1x2M:F7zBnMyn0Lo" border="0"></img></a>
</div>]]></content:encoded>
			<wfw:commentRss>http://voituk.kiev.ua/2009/06/21/using-iptables-to-prevent-ssh-brute-force/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>
