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

<channel>
	<title>JL&#039;s blog &#187; хостинг</title>
	<atom:link href="http://john.5070.info/tag/%d1%85%d0%be%d1%81%d1%82%d0%b8%d0%bd%d0%b3/feed/" rel="self" type="application/rss+xml" />
	<link>http://john.5070.info</link>
	<description>:-)</description>
	<lastBuildDate>Sun, 06 Dec 2009 17:11:56 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>сумасшедший алгоритм</title>
		<link>http://john.5070.info/2009/11/%d1%81%d1%83%d0%bc%d0%b0%d1%81%d1%88%d0%b5%d0%b4%d1%88%d0%b8%d0%b9-%d0%b0%d0%bb%d0%b3%d0%be%d1%80%d0%b8%d1%82%d0%bc/</link>
		<comments>http://john.5070.info/2009/11/%d1%81%d1%83%d0%bc%d0%b0%d1%81%d1%88%d0%b5%d0%b4%d1%88%d0%b8%d0%b9-%d0%b0%d0%bb%d0%b3%d0%be%d1%80%d0%b8%d1%82%d0%bc/#comments</comments>
		<pubDate>Mon, 16 Nov 2009 16:37:40 +0000</pubDate>
		<dc:creator>John Lepikhin</dc:creator>
				<category><![CDATA[Performance]]></category>
		<category><![CDATA[программирование]]></category>
		<category><![CDATA[cluster]]></category>
		<category><![CDATA[алгоритмы]]></category>
		<category><![CDATA[хостинг]]></category>

		<guid isPermaLink="false">http://john.5070.info/?p=220</guid>
		<description><![CDATA[Уже 3-й день размышляю над алгоритмом следующей задачки.
Дано:

Есть кластер. В кластере живёт несколько узлов. В узле живёт несколько веб-серверов. В веб-сервере живёт несколько аккаунтов. В аккаунте живёт несколько сайтов.
Для каждого сайта известна текущая нагрузка. Соответственно, известна нагрузка для каждого из аккаунтов.
Известно количество сайтов в каждом из аккаунтов.
Для каждого аккаунта сказано, на скольких минимум узлах (не [...]]]></description>
			<content:encoded><![CDATA[<p>Уже 3-й день размышляю над алгоритмом следующей задачки.</p>
<p>Дано:</p>
<ol>
<li>Есть кластер. В кластере живёт несколько узлов. В узле живёт несколько веб-серверов. В веб-сервере живёт несколько аккаунтов. В аккаунте живёт несколько сайтов.</li>
<li>Для каждого сайта известна текущая нагрузка. Соответственно, известна нагрузка для каждого из аккаунтов.</li>
<li>Известно количество сайтов в каждом из аккаунтов.</li>
<li>Для каждого аккаунта сказано, на скольких <em>минимум</em> узлах (не веб-серверах) он должен присутствовать. Пусть это будет <strong>μ</strong>.</li>
<li>Для каждого аккаунта известно, какие веб-серверы обрабатывают его в данный момент.</li>
</ol>
<p>Задача состоит в том, чтобы перераспределить аккаунты по веб-серверам так, чтобы</p>
<ol>
<li>Аккаунты с максимальной нагрузкой присутствовали на максимальном числе узлов. Ну и соразмерно все остальные.</li>
<li>Каждый аккаунт представлен минимум на <strong>μ</strong> узлах.</li>
<li>В пределах одного узла каждый аккаунт представлен лишь один раз.</li>
<li>Текущая нагрузка наиболее равномерно распределена между всеми узлами (или с каким-то коэффициентом — не суть).</li>
<li>Сайты максимально равномерно распределены между всеми веб-серверами, чтобы минимизировать объём памяти каждого из процессов веб-серверов.</li>
<li>Сделать это всё так, чтобы изменения коснулись по возможности минимального количества веб-серверов, чтобы затем минимум их пришлось заставить перечитывать конфиг.</li>
<li>Делать это каким-то хитрым алгоритмом т.к. простой перебор на потенциальные десятки тысяч сайтов может отожрать немало проца и памяти&#8230;</li>
</ol>
<p>Понятно, что идеально соблюсти все условия не получится. Соответственно, надо либо расставить приоритеты условиям, либо ввести коэффициент максимальной допустимой погрешности решения. Решение вырастает весьма объёмным даже для простого перебора. Мне страшно представить о часе, когда надо будет искать более изящные решения.</p>
]]></content:encoded>
			<wfw:commentRss>http://john.5070.info/2009/11/%d1%81%d1%83%d0%bc%d0%b0%d1%81%d1%88%d0%b5%d0%b4%d1%88%d0%b8%d0%b9-%d0%b0%d0%bb%d0%b3%d0%be%d1%80%d0%b8%d1%82%d0%bc/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Микроязык OpQL</title>
		<link>http://john.5070.info/2009/06/%d0%bc%d0%b8%d0%ba%d1%80%d0%be%d1%8f%d0%b7%d1%8b%d0%ba-opql/</link>
		<comments>http://john.5070.info/2009/06/%d0%bc%d0%b8%d0%ba%d1%80%d0%be%d1%8f%d0%b7%d1%8b%d0%ba-opql/#comments</comments>
		<pubDate>Wed, 03 Jun 2009 13:36:58 +0000</pubDate>
		<dc:creator>John Lepikhin</dc:creator>
				<category><![CDATA[Ocaml]]></category>
		<category><![CDATA[программирование]]></category>
		<category><![CDATA[OpQL]]></category>
		<category><![CDATA[oProxy]]></category>
		<category><![CDATA[хостинг]]></category>

		<guid isPermaLink="false">http://john.5070.info/?p=115</guid>
		<description><![CDATA[Сделал микроязык запросов для oProxy. Служит, собственно, для управления ею. Что умеет:

Показать всякую текущую статистику (устаревшее show_workers, show_nodes и т.д.)
Управлять списками наблюдения.

Второй пункт интереснее. Немного предыстории. Часто бывает, что у клиентов на shared-хостинге случился перерасход ресурсов и они хотят знать, откуда эта нагрузка растёт. Резонное желание. Но беда в том, что ни я, &#8220;заведующий всем&#8221; [...]]]></description>
			<content:encoded><![CDATA[<p>Сделал микроязык запросов для oProxy. Служит, собственно, для управления ею. Что умеет:</p>
<ol>
<li>Показать всякую текущую статистику (устаревшее show_workers, show_nodes и т.д.)</li>
<li>Управлять списками наблюдения.</li>
</ol>
<p><span id="more-115"></span>Второй пункт интереснее. Немного предыстории. Часто бывает, что у клиентов на shared-хостинге случился перерасход ресурсов и они хотят знать, откуда эта нагрузка растёт. Резонное желание. Но беда в том, что ни я, &#8220;заведующий всем&#8221; © на хостинге, ни тем более техподдержка не может точно ответить на такой вопрос. Просто неоткуда брать данные. Чтобы разрешить эту проблему и были созданы списки наблюдения. Допустим, по MySQL-пользователю evlampiy случилось превышение нагрузки на MySQL. Пишем:</p>
<blockquote><p>start save time, sent, query from mysql where user = &#8220;evlampiy&#8221; into file &#8220;/home/evlampiy/mysql_load.txt&#8221;</p></blockquote>
<p>Всё, с этого момента прокся начнёт сохранять в указанный файлик все запросы к MySQL, пришедшие от пользователя evlampiy. Будет записано время выполнения запроса, количество посланных клиенту байт и сам запрос. Как только выяснили происхождение нагрузок, пишем:</p>
<blockquote><p>stop save time, sent, query from mysql where user = &#8220;evlampiy&#8221; into file &#8220;/home/evlampiy/mysql_load.txt&#8221;</p></blockquote>
<p>Запись прекратилась. Можно и понавороченней:</p>
<blockquote><p>start save current_time, time/60, sent/1024/1024, host, uri from http where host = &#8220;john.5070.info&#8221; and ((uri = &#8220;/&#8221; and time &lt; 0.5) or (uri = &#8220;/news/&#8221; and time &gt; 0.01)) into file &#8220;/tmp/crazy_requests.txt&#8221;; stop save host from http into file &#8220;/tmp/all_http.txt&#8221;</p></blockquote>
<p>Разумеется, можно посмотреть текущий список наблюдения:</p>
<blockquote><p>show queries</p></blockquote>
<p>На моей машине оверхед на один запрос со списком наблюдения длиной 5 получился 70 микросекунд, что примерно соответствует 2.3% на одно наблюдение при отдаче напрямую и 1.7% при проксировании.</p>
]]></content:encoded>
			<wfw:commentRss>http://john.5070.info/2009/06/%d0%bc%d0%b8%d0%ba%d1%80%d0%be%d1%8f%d0%b7%d1%8b%d0%ba-opql/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>oProxy release</title>
		<link>http://john.5070.info/2009/05/oproxy-release/</link>
		<comments>http://john.5070.info/2009/05/oproxy-release/#comments</comments>
		<pubDate>Wed, 20 May 2009 13:31:28 +0000</pubDate>
		<dc:creator>John Lepikhin</dc:creator>
				<category><![CDATA[oProxy]]></category>
		<category><![CDATA[программирование]]></category>
		<category><![CDATA[cluster]]></category>
		<category><![CDATA[LWT]]></category>
		<category><![CDATA[Ocaml]]></category>
		<category><![CDATA[хостинг]]></category>

		<guid isPermaLink="false">http://john.5070.info/?p=106</guid>
		<description><![CDATA[Не прошло и полгода, как я решил разродиться на релиз прокси. Назовём его &#8220;1.1&#8243;.
Самой большой моей ошибкой в проектировании была идея использовать POSIX треды. Всё замечательно, но только до тех пор, пока активных соединений не становится больше сотни. Дело в том, что такие треды, по сути, являются почти что отдельными процессами: у них свой стек, [...]]]></description>
			<content:encoded><![CDATA[<p>Не прошло и полгода, как я решил разродиться на релиз прокси. Назовём его &#8220;1.1&#8243;.</p>
<p><span id="more-106"></span>Самой большой моей ошибкой в проектировании была идея использовать POSIX треды. Всё замечательно, но только до тех пор, пока активных соединений не становится больше сотни. Дело в том, что такие треды, по сути, являются почти что отдельными процессами: у них свой стек, их обрабатывает шедулер ядра и т.д. Единственное отличие — у них в пределах одного процесса единое адресное пространство. Так вот, всё это вызывало такие проблемы, как переполнение стека, нелинейность масштабирования и пр. В качестве альтернатиы рассматривалось два варианта:</p>
<ol>
<li>Библиотека Equeue. Знаменита тем, что предоставляет низкий уровень к управлению очередями обработки дескрипторов. Это и минус — работать с ней не слишком комфортно: &#8220;есть только очередь и события, всё остальное на ваше усмотрение&#8221;.</li>
<li>Библиотека Lwt. По сути, то же самое, но выполнено совсем, совсем с другого бока. В интерфейсе предоставляется модель монад и псевдопотоков (отсюда название: LightWeight Threads). Быдлокодеру, навроде меня, достаточно лишь описать зависимости между состояниями: что делать, когда я получу данные из вон того сокета, и куда их записать потом.</li>
</ol>
<p>Выбор пал на Lwt. А вот тут следует сказать несколько восторженных слов о типизации Хиндли — Милнера, используемой, в том числе, в Ocaml. Как я уже сказал, потоки POSIX при разработке выглядят практически как отдельные процессы. Создал поток, и забыл о нём, а он варится сам по себе. В Lwt же надо отдельно позаботиться о последствиях каждой операции IO. Прежде всего, я заменил все вызовы Unix.socket (создание сокета) — благо в коде почти все они были в моей обёртке и реально вызов встречался только в 2-3 местах — на Lwt_unix.socket. И понеслось. Типовычислялка компилятора сразу указала мне, где я, дурень, пытаюсь подсунуть в нормальную сокетную функцию параметр с каким-то странным типом Lwt_unix.file_descr. Следующая неделя прошла за чрезвычайно интеллектуальной работой. Компилятор говорил мне, что нужно сделать чтобы впасть в нирвану, а я всячески пытался её достичь. Таким образом, буквально через несколько дней негритянской работы, передо мной предстал код, который мало того, что не содержал ни одного вызова к стандартным функциям IO, так ещё и компилился.</p>
<p>Каково же было моё удивление, когда с первым запуском оно ещё и заработало! После недели тупой работы, перехода с философии &#8220;одно соединение == один самостоятельный псевдопроцесс&#8221; на &#8220;одно соединение == один конечный автомат&#8221;, после исправления тучи кода <em>исключительно</em> по указаниям компилятора! Кривенько, но заработало, результат я увидел. Последующая неделя ушла на исправление совсем новых багов, багов ещё со старой версии и дописывания новых приятных мелочей.</p>
<p>Итого, имеем. Кластер с определённым количеством клиентов (пока меньше, чем на одной машине в среднем, но уже существенно). Прокся заведует как балансировкой нагрузки между HTTP-узлами, так и между MySQL. Т.е. гоняются вполне реальные данные настоящих сайтов. На одно <strong>соединение</strong> (не запрос) HTTP в среднем уходит чуть меньше миллисекунды CPU (это и проксирование динамики, и отдача статики с помощью системного sendfile). На соединение MySQL уходит несколько больше — около 5 миллисекунд, хотя алгоритм там наоборот в целом проще. Связываю это с</p>
<ul>
<li>sendfile() там не применишь, все проксируемые данные копируются в userspace</li>
<li> Имею богатый опыт разглядывания скриптов клиентов. А это совсем другая, удивительная, не поддающаяся научному изучению культура. У них принято сказать SELECT * на большую табличку, затем сложным алгоритмом с циклами тройной вложенности вычленить из многомегабайтного результата 2 строки, затем их отправить обратно в мускуль с целью join&#8217;а в непристойной позе с другой табличкой.</li>
</ul>
<p>Памяти жрёт в рабочем режиме при текущих нагрузках стабильно 12-15MB (один мастер, 4 рабочих).</p>
<p>Планы (или мечты?) на будущее:</p>
<ul>
<li>Пропатчить Lwt на предмет использования epoll() вместо select(). Чтобы в будущем не было мучительно больно обрабатывать 10000 одновременных соединений. Если, конечно, до этого дойдёт.</li>
<li>Написать некую глюкалу, которая сильно поможет в поиске причин высоких нагрузок как на сайтах, так и в БД. Пока мне это видится чем-то вроде: oproxyreport &#8220;select response_time-start_time, uri from http where host=&#8217;ispsystem.com&#8217; and uri begins with &#8216;/ru/&#8217; save to &#8216;/tmp/load.log&#8217;&#8221;. Если будет реализовано — сразу можно будет написать интерфейсик в ISPmanager, чтобы клиенты могли сами это делать.</li>
</ul>
<p>Я всё сказал.</p>
<p>p.s. Нет, не всё :) С переходом на Lwt, обнаружилась ещё одна приятная особенность: портабельность. Например, во FreeBSD 7.1. поломали те самые POSIX треды, из-за чего мне пришлось отказаться от альфа-тестирования прокси на ней в своё время. Lwt же ни от чего не зависит. Select() есть везде, а не блокирующие сокеты криво реализованы разве что в винде.</p>
]]></content:encoded>
			<wfw:commentRss>http://john.5070.info/2009/05/oproxy-release/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Пора в production? Скоро узнаем.</title>
		<link>http://john.5070.info/2009/01/%d0%bf%d0%be%d1%80%d0%b0-%d0%b2-production-%d1%81%d0%ba%d0%be%d1%80%d0%be-%d1%83%d0%b7%d0%bd%d0%b0%d0%b5%d0%bc/</link>
		<comments>http://john.5070.info/2009/01/%d0%bf%d0%be%d1%80%d0%b0-%d0%b2-production-%d1%81%d0%ba%d0%be%d1%80%d0%be-%d1%83%d0%b7%d0%bd%d0%b0%d0%b5%d0%bc/#comments</comments>
		<pubDate>Thu, 29 Jan 2009 18:46:40 +0000</pubDate>
		<dc:creator>John Lepikhin</dc:creator>
				<category><![CDATA[Ocaml]]></category>
		<category><![CDATA[oProxy]]></category>
		<category><![CDATA[программирование]]></category>
		<category><![CDATA[cluster]]></category>
		<category><![CDATA[хостинг]]></category>

		<guid isPermaLink="false">http://john.5070.info/?p=67</guid>
		<description><![CDATA[Готовимся к продакшен тестированию. Надеюсь, завтра запустить всё воедино на тестовых машинах.
Из нового:

Балансировка нагрузки. Универсальный модуль для любых видов соединений. Написано кривовато, но работает. Заведует всеми узлами мастер-процесс. Это несколько замедляет процесс (рабочим приходится больше общаться с мастером), зато позволяет контролировать балансировку в одном месте.
Файлик: список сайтов. Пока каждый сайт можно только включить/выключить и прописать [...]]]></description>
			<content:encoded><![CDATA[<p>Готовимся к продакшен тестированию. Надеюсь, завтра запустить всё воедино на тестовых машинах.</p>
<p>Из нового:</p>
<ul>
<li>Балансировка нагрузки. Универсальный модуль для любых видов соединений. Написано кривовато, но работает. Заведует всеми узлами мастер-процесс. Это несколько замедляет процесс (рабочим приходится больше общаться с мастером), зато позволяет контролировать балансировку в одном месте.</li>
<li>Файлик: список сайтов. Пока каждый сайт можно только включить/выключить и прописать алиасы. Кроме того, на будущее есть поле &#8220;домашняя директория сайта&#8221;. В ближайшее время есть планы проксёй отдавать статичные файлы. Не понятно, что делать с .htaccess. Не хочется забивать, как это нынче делается в Nginx.</li>
<li>Файлик: список узлов. Представляет из себя IP, мастер-пароль, список ролей.</li>
<li>Роли. Что каждая машина умеет/должна делать. От этого зависит поведение балансировщика и некоторых скриптов. Предопределённые роли: worker_http (узел умеет обрабатывать HTTP-запросы), master (узел будет точкой входа, где висит балансировщик) и другие. Всё рассказывать раньше времени не буду :)</li>
<li>Мониторинг. Наконец нашёл, где заюзать функционалы. На основе этого функционала (functional) написан мониторинг файлов. Как результат, прокся умеет автоматически подгружать изменённый список сайтов или узлов.</li>
<li>Новый параметр у oproxyctl: show_nodes. Показывает известные узлы. Кто в дауне, сколько у каждого активных запросов, сколько всего обработано. Может оказаться полезным для выяснения проблемных узлов.</li>
<li>Поддерживаем новый протокол, который я сам выдумал :) Служит для различных сервисных запросов к узлу. Поскольку позволяет совершать совершенно небезопасные вещи, авторизация происходит без передачи открытого пароля по сети.</li>
<li>Новая утилита: clusterctl. Умеет 1) запустить на узлах с указанной ролью (или на всех, или на определённом IP) определённую команду и вернуть в STDOUT/STDERR что в итоге получилось 2) рассказывать список ролей текущей ноды 3) рассказывать список узлов, поддерживающих указанную роль. Служит для сервисных скриптов.</li>
<li>Прокся умеет выставлять X-Forwarded-For, чтобы в конечном итоге в логи попадал нужный IP.</li>
<li>Сервисные скрипты: apachectl (рестарт Апача на всех узлах), repquota и другие.</li>
<li>Оптимизация, стабильность.</li>
</ul>
<p>Допил коньяк. Опять потянуло на философию…</p>
]]></content:encoded>
			<wfw:commentRss>http://john.5070.info/2009/01/%d0%bf%d0%be%d1%80%d0%b0-%d0%b2-production-%d1%81%d0%ba%d0%be%d1%80%d0%be-%d1%83%d0%b7%d0%bd%d0%b0%d0%b5%d0%bc/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Торжественно заборот(заборон?) последний крупный баг</title>
		<link>http://john.5070.info/2008/12/%d1%82%d0%be%d1%80%d0%b6%d0%b5%d1%81%d1%82%d0%b2%d0%b5%d0%bd%d0%bd%d0%be-%d0%b7%d0%b0%d0%b1%d0%be%d1%80%d0%be%d1%82%d0%b7%d0%b0%d0%b1%d0%be%d1%80%d0%be%d0%bd-%d0%bf%d0%be%d1%81%d0%bb%d0%b5%d0%b4/</link>
		<comments>http://john.5070.info/2008/12/%d1%82%d0%be%d1%80%d0%b6%d0%b5%d1%81%d1%82%d0%b2%d0%b5%d0%bd%d0%bd%d0%be-%d0%b7%d0%b0%d0%b1%d0%be%d1%80%d0%be%d1%82%d0%b7%d0%b0%d0%b1%d0%be%d1%80%d0%be%d0%bd-%d0%bf%d0%be%d1%81%d0%bb%d0%b5%d0%b4/#comments</comments>
		<pubDate>Sun, 28 Dec 2008 21:58:51 +0000</pubDate>
		<dc:creator>John Lepikhin</dc:creator>
				<category><![CDATA[oProxy]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[программирование]]></category>
		<category><![CDATA[статистика]]></category>
		<category><![CDATA[хостинг]]></category>

		<guid isPermaLink="false">http://john.5070.info/?p=51</guid>
		<description><![CDATA[… В результате которого рабочий иногда падал, валя за собой всю проксю. Как и предполагал, дело оказалось в рекурсии, которая не могла быть развёрнута в tail из-за не пойманных исключений внутри. Это можно назвать минусом смешивания чистой функциональщины с чем-то ещё. Но, увы и ах, ловить все исключения внутри нельзя.
Заменил рекурсивный вызов на цикл, и [...]]]></description>
			<content:encoded><![CDATA[<p>… В результате которого рабочий иногда падал, валя за собой всю проксю. Как и предполагал, дело оказалось в рекурсии, которая не могла быть развёрнута в tail из-за не пойманных исключений внутри. Это можно назвать минусом смешивания чистой функциональщины с чем-то ещё. Но, увы и ах, ловить все исключения внутри нельзя.</p>
<p>Заменил рекурсивный вызов на цикл, и уже двое суток работаем ровно и без падений; обработано около 2000000 реальных соединений к MySQL.</p>
<p>Кстати…</p>
<p><span id="more-51"></span> Уже можно подвести итог, насколько в условиях хостинга с абстрактным набором сайтов (как правило, кривых :), нагружен мускуль. Статистика за сутки. Машина средней мощи, набито правильное количество клиентов.</p>
<p>Активных пользователей в MySQL: 453</p>
<p>Из них, имеющих соединие каждые 5 минут за сутки (т.е. работающих круглосуточно): 46</p>
<p>Принято данных от пользователя (байты голого протокола):  9015309900</p>
<p>Передано байт:  121983229037</p>
<p>В сумме потрачено времени внутри MySQL: 138884 секунды (не забываем, что мускуль умеет жрать несколько процессоров)</p>
<p>Обработано запросов (в т.ч. авторизация и протокольная команда QUIT считаются запросами):  61044220</p>
<p>Это данные за сутки назад, включая бэкапный трафик (прибавляет ~15% ко всем циферкам).</p>
]]></content:encoded>
			<wfw:commentRss>http://john.5070.info/2008/12/%d1%82%d0%be%d1%80%d0%b6%d0%b5%d1%81%d1%82%d0%b2%d0%b5%d0%bd%d0%bd%d0%be-%d0%b7%d0%b0%d0%b1%d0%be%d1%80%d0%be%d1%82%d0%b7%d0%b0%d0%b1%d0%be%d1%80%d0%be%d0%bd-%d0%bf%d0%be%d1%81%d0%bb%d0%b5%d0%b4/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
