Severe reality
The endless way...
07 мая, 2012
Перенос блога
17 апреля, 2012
Один на миллион
Вам наверное приходилось слышать что-то вроде: “Этого не может быть. Вероятность этого менее одной миллионной”. В бытовом понимании одна миллионная это некая несвершимая вероятность. Другими словами, этого просто не может произойти. Но насколько это вeрятное событие, если вы повторите эксперимент миллион раз? Конечно, вероятность наблюдать событие растет если вы повторяете эксперимент много раз подряд. Но все равно мы зачастую подсознательно пытаемся игнорировать такие события считая их невероятными.
Тем не менее, в современных Интернет-системах которые обрабатывают миллионы запросов в сутки не заметить в течение дня событие с вероятностью в одну миллионную довольно сложно. И вот почему. Вероятность что интересующее нас событие (с исходной вероятностью 1/n) не проявит себя ни в одном из n независимых экспериментов равно:
(1 - n–1)n
В этой формуле n–1 — вероятность события которое реализуется в одном эксперименте из n. 1 - n–1 соответственно вероятность что это событие не реализуется в ходе эксперимента. Ну и не тяжело догодаться что (1 - n–1)n это вероятность с которой это событие не реализуется в ходе n независимых экспериментов. Для n = 1 000, если вы его подставите в формулу, итоговое значение вероятности составит 0.368 или 36.8%. Для n = 1 000 000, значение вероятности составит… те же самые 36.8%! Редкость события всегда уравновешивается частотой повторения испытаний.
Обоснование этого возможно контринтуитивного результата кроется во втором замечательном пределе, который большинству из нас преподавали в ходе курса высшей математики.
limn→∞(1 + n–1)n = e
Второй замечательный предел интенсивно используется при решении дифференциальных уравнений, но нас интересует одно из следствий этого предела.
Этот предел говорит нам о том что при растущем n, значение выражения (1 - n–1)n стремится к значению e–1, которое является той самой константой приблизительно равной 0.368.
Я не буду здесь приводить доказательство этого следствия второго замечательного предела. Предел разумного количества формул на один пост достигнут :).
Теперь давайте соберем все это вместе. Предположим что у вас есть некий баг (скажем, race condition в многопоточном приложении), который воспроизводится с вероятностью один на миллион. Фактически что говорит этот предел – если вы обслужите за день миллион запросов то вероятность того что этот баг не порявит себя ни в одном из запросов ~37%. То есть с вероятностью ~63% этот баг даст о себе знать хотя бы один раз за сутки.
Из этого можно сделать еще один вывод. Чем больше запросов вы обрабатываете за сутки, тем более редкие явления вы будете наблюдать в течение суток. 100 000 запросов в сутки? Тогда вероятность 1/100 000 для вас не редкость. 10 000 000? Тогда и одной десятимиллионной вас не удивишь.
Да пребудет с вами нормальное распределение!
05 марта, 2012
О высокой нагрузке
В последнее время все чаще говорят о высоконагруженных приложениях. Нельзя не заметить что это теперь очень популярная, можно даже сказать модная, область знаний. Сам же термин “высоконагруженная система” при этом не имеет в нашей отрасли четкого определения. В этой заметке я хочу привести свои рассуждения по этому вопросу. Я не ставлю перед собой цель дать исчерпывающее определение этого термина. Моя цель, предоставить читателю ключевую информацию о системах такого рода, которая определяет стиль мышления при работе с ними.
Итак, что такое высоконагруженная система? Ответ на этот вопрос стоит начать с описания качественных свойств такого рода систем.
Традиционные качества высоконагруженной системы
Как правило, к таким качествам относят большое количество пользователей и данных. В целом это правда, но тут есть несколько загвоздок:
- это не вся правда;
- приведенные факторы являются количественными, а не качественными.
Ниже на основании предпосылки “много пользователей, много данных” я сформирую список качественных факторов присущих высоконагруженным системам. Начнем с самого простого.
Многопользовательская система
Конечно же высоконагруженное приложение в первую очередь является многопользовательским. То есть в один момент времени с ним работает более чем один человек. Сейчас, в эру стремительного развития Интернета, это тысячи и сотни тысяч человек.
Устойчивая ассоциация высоконагруженных систем с большим количеством пользователей в нашей индустрии появилась давным давно. Ничего принципиально неверного в такой связи нет. Но если высокая нагрузка подразумевает большое количество пользователей, то большое количество пользователей совсем не обязательно подразумевает высоконагруженную систему.
Если посмотреть на статистику Московского метрополитена за 2010 год, то окажется что средняя часовая нагрузка на систему максимальна в диапазоне от 8 до 9 часов утра. За этот час через турникеты проходят приблизительно 720 тысяч человек. Что порождает необходимость не менее 200 раз в секунду проверять статус предъявленных проездных и принимать решение о пропуске того или иного человека через турникет. В Интернете существует масса высоконагруженных ресурсов с подобными показателями пропускной способности. Например, статистика по StackOverflow за тот же 2010-й год показывает что их средняя пропускная способность находится в диапазоне 100-150 хитов в секунду.
Определенно у метрополитена более высокие требования к пропускной способности. Но значит ли это что Московский метрополитен можно считать более “высоконагруженным” чем StackOverflow? Вряд ли, в частности потому что эти две системы оперируют несравнимыми объемами информации, о чем мы поговорим позже.
Я намерено в обоих случаях привел оценку пропускной способности, так как она дает больше информации о нагрузке чем количество пользователей системы. Две разные системы могут подталкивать пользователей к разным паттернам их использования. Это может приводить к абсолютно разным требованиям по пропускной способности для этих систем. Пропускная способность точнее описывает количество работы которую должна уметь выполнять система в единицу времени.
Распределенная система
Высоконагруженные системы являются системами распределенными, то есть работают более чем на одном сервере. Зачастую это десятки и сотни серверов. Требование распределенности вытекает из следующих причин:
- необходимости обрабатывать возрастающие объемы данных;
- необходимости “живучести” системы в случаях отказа части серверов.
Но обо всем по порядку…
Я наверное не ошибусь если скажу что большинство высоконагруженных приложений являются Интернет-приложениями (позже мы еще вернемся к состоятельности этой гипотезы). А отличительной особенностью современного Интернета основанного на концепции Web 2.0 является тот факт, что сами пользователи генерируют данные, которые они сами же в итоге и потребляют. Это приводит к тому, что чем больше у вас пользователей, тем больше потенциальный объем хранимых данных.
Требование обработки больших объемов данных может существенно осложнить жизнь. Под “большим объемом” я подразумеваю такой объем информации, который не может эффективно обработать одна машина. В большинстве случаев, это объем превышающий объем доступной на сервере оперативной памяти. То есть приходится тем или иным образом распределять данные между несколькими машинами, каждая из которых обрабатывает свой небольшой кусочек данных, но делает это эффективно, без page fault’ов (не используя swap) и прочих неприятностей. Необходимость эффективной обработки данных диктуется другим очень важным качеством высоконагруженных систем, – интерактивностью, о котором будет сказано ниже.
Но большие объемы данных – это не все. Ко всему к этому хочется чтобы система работала в режиме 24x7 без остановок и перерывов. Но вот незадача, любое даже самое надежное оборудование иногда выходит из строя. Встает естественная задача обеспечения доступности системы в случаях отказа оборудования.
Тут мы вступаем в область знания распределенных систем, эксплуатация которых редко бывает безоблачной, даже когда вы используете готовые решения. Тем не менее распределенные системы, не смотря на сложность их разработки и поддержки, пожалуй единственный подход позволяющий обеспечить вышеизложенные требования в полной мере.
Позитивная динамика по пользователям и данным
Если ваше приложение представляет хоть какой-то интерес, то даже если ничего не делать, ваша аудитория будет расти просто с ростом аудитории Интернета. Поэтому характерной чертой высоконагруженных систем является не просто большое количество пользователей, но и позитивная динамика количества пользователей.
В контексте реалий Web 2.0 растущее количество пользователей может привести к тому, что такую же позитивную динамику вы будете иметь и по данным. Поэтому в контексте высоконагруженных систем корректней говорить не о большом, а о растущем количестве пользователей и данных.
Интерактивность
И тут мы подходим к своеобразной финальной ноте в аккорде качеств highload систем, если позволите так выразиться. Интерактивность – одно из основополагающих качеств высоконагруженной системы. Интерактивность подразумевает, что пользователь после того как послал запрос сидит и ждет ответа, а люди как известно ждать не любят. При этом большинство Интернет-приложений о которых мы говорим не являются критическими важными в жизни людей. Twitter, Flickr, Facebook и т.д. это все круто конечно, но если они будут отвечать непомерно долго, я найду чем занятся еще. Наша жизнь от них не зависит (прогрессирующие формы задротства у некоторых особо аддиктивных индивидов не в счет), а это значит что эти приложения должны занимать минимум нашего времени. То есть отвечать за приемлимое время. Это контрастирует, например, с системами пакетной обработки данных, в которых время ответа системы вторично.
Из этого правила безусловно есть исключения. Представьте что вы только что совершили покупку в Интернете сообщив свою платежную информацию третьим лицам. Скорее всего вы дождетесь ответа системы, даже если она будет отвечать дольше минуты. Но как известно исключение из правила лишний раз подверждает само правило.
Управление ресурсами
Качество интерактивности очень важно для понимания сути высоконагруженных приложений, потому что по вышеобозначенным причинам разрабатывая такую систему вы должны быть уверены вот в чем:
Каждый раз когда приложение получает очередной запрос, у него должно быть достаточно свободных ресурсов (CPU, память, диск, сеть) для обработки запроса за приемлимое время.
Возможно это и звучит тривиально, но именно данное требование приводит нас к основному посылу данной заметки:
Контроль за ресурсами является неотъемлимой частью работы с высоконагруженным проектом.
Необходимо также учитывать тот факт, что из-за позитивной динамики свободных ресурсов становится меньше с течением времени. В этом заключается “парадокс” высоконагруженных систем. Если вы возьмете высоконагруженный проект и заморозите его разработку (отправите всех разработчиков в бессрочный отпуск), то рано или поздно он перестанет работать.
Высоконагруженная система – это интерактивная распределенная система которая требует постоянного контроля за собственными ресурсами. В противном случае она перестает работать.
Это противоречит обывательскому опыту. Как может само по себе поломаться то, чего не меняли? У программного кода нет срока годности. Причина в том, что со временем системе просто перестанет хватать ресурсов. А нехватка ресурсов провоцируется факторами, часть из которых мы уже рассмотрели:
- рост аудитории;
- рост объема данных;
- изменения паттернов поведения аудитории, в том числе и сезонные.
Если посмотреть темы докладов на конференциях посвященных тематике разработки высоконагруженных систем (например, тот же highload), то большинство тем с которыми выступают докладчики можно свести к двум основополагающим направлениям:
- как решать существующие задачи используя меньше ресурсов (практически все NoSQL БД, неблокирующий I/O, оптимизация и тюнинг);
- как имея больше ресурсов решать пропорционально больший объем задач (message queueing, распределенные вычисления, распределенные БД, параллелизм).
Эту дихотомию я уже затрагивал ранее в заметке Performance vs. Scalability.
Фактически речь на таких конференциях идет в основном о различных способах адекватного управления аппаратными ресурсами в контексте баз данных, языков программирования, операционных систем, алгоритмов, моделей ввода/вывода, вычислительных парадигм и т.д.
Просто подумайте над этим. Большая часть пропогандируемых в настоящее время баз данных под эгидой NoSQL, не предоставляет качественно новых возможностей для разработчиков. Реляционная модель позволяет относительно легко все это реализовать не выходя за рамки одной парадигмы. В этом, я полагаю, и заключается одна из причин почему реляционки приобрели такую популярность и до сих остаются самым распространенным типом БД. Так называемые, пост-реляционные базы данных не являются инструментом для решения качественно новых задач, всего лишь инструментом для более эффективного решения существующих. Именно важность эффективной траты ресурсов стала катализатором популярности NoSQL тематики.
Высоконагруженный проект – это Интернет приложение
Если исходить из того что неотъемлемой частью высоконагруженного проекта является постоянный рост данных и аудитории, то становится понятно почему высоконагруженные проекты – это поголовно Интернет приложения.
В реальной жизни всегда есть некий предел, который не позволяет использовать систему все возрастающему количеству людей. В том же метрополитене человеку требуется некоторое время, чтобы пройти через турникет. Исходя из этого времени, а также общего количества турникетов можно достаточно точно расчитать верхний предел возможной нагрузки. Выглядит очень невероятным чтобы за секунду через один турникет могло пройти 10 человек.
В сфере High Performance Computations приложения могут выполнять просто гигансткое количество операций в единицу времени. Больше чем любое Интернет-приложение. Но это количество зависит только от объема входных данных, а также алгоритма обработки (например, от точности моделирования, если речь идет о моделировании динамических систем). Тяжело придумать причину почему нагрузка на такую систему может вырасти сама по себе без вмешательства персонала ее сопровождающего.
Похоже, что Интернет-приложения это единственная сфера в которой нагрузка есть переменная не имеющая верхнего предела.
Работая в Highload проекте
Но не стоит возводить данный тип проектов на какой-то особый пьедестал. Highload проект это в первую очередь такой же самый проект как и все остальные. Проект в котором бывают и гонки за фичами и deadline’ы и сложности межличностных отношений и все прочие “прелести” процесса разработки ПО. Но говоря о “высоконагруженной составляющей” проекта, работа над ней сводится к тому, что постоянно приходится искать ответы на ресурсно-ориентированные вопросы:
- какие bottleneck’и есть в системе по ресурсам и как их устранить?
- каков запас системы по ресурсам учитывая естественный рост аудитории/данных?
- что делать при нехватке того или иного ресурса? (например, можно за счет RAM сэкономить CPU).
Не удивительно что в такого рода проектах гигантское значение имеет система мониторинга. Она предоставляет массу информации для ответа на такого рода вопросы. Сопровождать и развивать высоконагруженный проект без мониторинга хотя бы основных метрик по всему серверному парку это безрассудство.
Это приводит нас к cписку первостепенных задач которые стоят перед разработчиками в контексте высокой нагрузки:
- создание инфраструктуры предоставляющей качественную обратную связь по утилизации аппаратных ресурсов системы (мониторинг, self-аудит приложений);
- определение звеньев системы нуждающихся в масштабировании в ближайшем времени и выбор стратегии масштабирования этих звеньев;
- определение toolbox’а позволяющего решать типовые задачи эффективно по аппаратным ресурсам (базы данных, очереди сообщений, языки программирования, библиотеки и т.д.).
В состоянии гонки
У вас как у инженера нет прямого влияния на количество пользователей и есть опосредованое влияние на объемы данных. В конце концов, чем больше данных и чем шире аудитория, тем лучше для бизнеса. Больше пользователей – больше возможностей для монетизации. Больше данных – больше конкурентное преимущество, а так же потенциально более высокие темпы роста проекта. Необходимо смирится с непрерывным ростом этих двух метрик.
Что же остается? Не так много на самом деле. Сделать так чтобы возможности системы были всегда на шаг впереди ее потребностей.
29 июня, 2011
Deploy и прочие неприятности
В продолжение предыдущего поста хочу немного рассказать о том как у нас происходит deploy. В прошлый раз мы закончили на том что артефакт доставлен на production и готов к развертыванию. Начинается самое интересное, процесс деплоя.
Но для начала надо немного описать платформу на которую мы деплоим наши приложения. В качестве servlet-container'а мы используем Jetty. Есть два основных типа приложений, которые мы пишем и сопровождаем: web-приложения, которые имеют некий web-интерфейс (это может быть как UI, так и REST интерфейс), а также background сервисы, которые как правило работают асинхронно (мы активно используем ActiveMQ). И здесь нужно подметить первую особенность, мы используем Jetty для обоих типов приложений. Это может показатся странным, так как для background-сервисов мы получаем явный overhead связанный с запуском и работой servlet-container'а. Но этот overhead просто мизерный по сравнению с тем какие плюсы мы получаем. А именно:
- единые утилиты управления lifecycle'ом приложения вне зависимости от его типа (старт, останов, перезапуск);
- стандартные способы задания конфигурации приложения вне зависимости от его типа;
- стандартные форматы распространения приложения вне зависимости от его типа (в нашем случае
.war).
Вобщем, стандартизация на лицо.
У нас своя сборка Jetty в которой кроме самого Jetty, есть еще несколько часто используемых нами библиотек (например: slf4j, logback, mysql драйвер, библиотеки connection pooling'а и некоторые другие). Сборка представлена в виде rpm пакета, что позволяет очень быстро подготавливать новую машину: yum install jdk jetty, готово.
Вас может удивило почему мы ставим на production машины JDK, а не JRE. Это связано с наличием в JDK очень полезных для диагностики утилит, таких как: jmap, jstack, jvisualvm и других. Это выгодно отличает платформу Java от множества других, -- масса утилит для диагностики, которые в большинстве случаев позволяют довольно быстро понять что не так с приложением.
Деплой приложения состоит из следующих шагов:
- распространение бинарника с приложением, а также конфигурации на целевые машины;
- перезапуск servlet container'а;
- deployment тестирование.
Естественно второй и третий шаг выполняются для всех машин по очереди.
Распространение артефакта и конфигурации
Тут все просто, scp доставляет файлы на каждую машину по очереди и ложит каждый в нужное место. Доставляются артефакты, а также конфигурационные файлы.
Перезапуск servlet container'а
Мы не используем hot redeploy, так как он вызывает ряд проблем. В частности, небезизвестную OutOfMemoryError: PermGen. Для перезапуска используются скрипты входящие в стандартную поставку Jetty с некоторыми несущественными дополнениями.
Deployment тестирование
Этот момент стоит описать поподробнее. После того как Jetty запустился было бы неплохо проверить что приложение способно выполнять основные функции. Тот кто деплоит приложение может опечататься в конфигурации, системные администраторы могут забыть "пропилить дырку" в firewall'е для необходимого приложению сервиса, мало ли что еще может произойти.
Задача deployment тестирования проверить что у приложения есть доступ ко всем необходимым сервисам для того чтобы оно выполняло свою работу. Это могут быть базы данных, очереди сообщений, сетевые файловые системы и другое middleware ПО. К счастью для нас, Spring Beans контейнер практически все делает за нас. Если во время инициализации контейнера (частью которого являются подключения к БД и т.д.) происходит ошибка, об этом легко узнать послав GET запрос на любой url, который обрабатывается непосредственно spring'ом. У нас есть соглашение что url /status не занимается приложением и служит для deployment тестирования. Этот url также сообщает какая версия приложения запушена в данный момент. Таким образом, deploy скрипт после того как перезапускает servlet container начинает опрашивать приложение. Если приложение возвращает 200-й статус код, то можно переходить к обновлению следующей машины. Если нет, то процесс деплоя считается неуспешным и прерывается. Тот кто делает deploy может инициировать процедуру отката. Откат мы делаем руками, так как процент "битых" релизов у нас не велик и смысла автоматизировать эту процедуру пока что смысла нет.
Rollout распределенных приложений
Когда приложение запущено в нескольких экземплярах (на нескольких машинах), то после запуска приложения на одном ноде имеет смысл немного подождать прежде чем переходить к следующей машине. Здесь играют свою роль как особенности приложения, так и особенности платформы. Java, как русские -- "долго запрягает, но быстро едет" (начальная загрузка байт кода, создание пула соединений, JIT компилятор etc). Нас такой tradeoff вполне устраивает, поэтому наш deploy скрипт может быть сконфигурирован таким образом, чтобы давать только что запущенному ноду "разогреться" перед тем как принять на себя нагрузку его еще не обновленных коллег. Как правило, 3-5 секунд достаточно.
Деплой на несколько машин (особенно учитывая их "разогрев") порождает интересную проблему. Во время обновления кластера, на нем работает две версии приложения. Некоторых людей этот факт ставит в ступор. Они не могут ужиться с тем, что у приложения в любой момент времени нет строго определенной версии.
Суровая реальность заключается в том, что требование работать одновременно в "нескольких версиях" порождается природой web-приложений. Мы стремимся к тому чтобы наше система всегда была online. Это имеет одно очень важное последствие для процесса deploy'я:
Мы всегда должны иметь возможность откатиться на предыдущую версию системы, так как пусть редко, но бывает что ошибка все же проходит сквозь все рубежи тестирования и попадает на production. Причем rollback должен осуществлятся не медленнее чем rollout.
Это приводит нас к следующему заключению: следующая версия системы всегда должна быть обратно-совместима с предыдущей. Это касается не только кода, но и схемы БД, данных в кеше и т.д.
Оказывается, что когда новая версия системы обратно совместима с текущей, нет ничего страшного в том чтобы некоторое время они поработали вместе.
На практике
Итак, как это все происходит на практике. Разработчик заходит через ssh на машину-координатор, куда автоматически доставляются артефакты приложений. У каждого приложения есть отдельная директория содержимое которой выглядит примерно следующим образом:
-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
Здесь есть несколько файлов о которых стоит рассказать поподробнее. Файл .config это обыкновенный ini-файл хранящий имена всех машин на которых установлено приложение, а также некоторые другие настройки развертывания. config.properties — это properties-файл, который содержит настройки приложения. .jettyrc содержит startup опции виртуальной машины на которой стартует Jetty.
За довольно длительное время мы перепробовали различные способы работы с конфигурационными файлами приложений. Мы хранили их в development системе контроля версий. Мы создавали для них отдельный репозиторий в зоне production. Текущая схема нам нравится больше всего. Она позволяет автоматически бекапить все конфигурационные файлы приложений, что безусловно необходимо, а также не связываться с излишей сложностью VCS систем для управления "двумя файлами из 10 строчек каждый".
Если запустить команду deploy без аргументов, то она выведет текущее состояние нодов:
$ 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
Благодаря maven во все наши сборки автоматически попадает информация о версии, а также номере билда.
Если же передать команде deploy имя артефакта, то начнется его развертывание на production машинах.
Дальнейшие соображения
Текущий процесс нас вполне устраивает, тем не менее у нас есть идеи как сделать его еще лучше.
Runtime обновление конфигурации логгирования
Мы используем logback в качестве библиотеки логгирования. Существенным ее плюсом является то что она позволяет менять конфигурацию логгирования на лету. Достаточно просто поменять XML файл с конфигурацией. Было бы неплохо иметь возможность распространять конфигурацию логгирования на машины без перезагрузки самого приложения.
Дифференцирование конфигурации различных нодов
В распределенной системе разные ноды могут иметь идентичную сборку, но разную конфигурацию, обуславливающую требуемую разность в их поведении. Сейчас у нас пока что нет возможности задать разную конфигурацию для разных нодов. Учитывая что конфигурация задается в виде properties файлов, сделать такого рода дифференциорание не сложно.
Partial deploy
Иногда бывает необходимо обновить не весь кластер, а только один нод из кластера. В будущем, я думаю мы сделаем такую возможность.
Вот пожалуй и все. А как деплоите приложения вы? ;)
17 апреля, 2011
Fair lock
Не так давно у нас на собеседовании был кандидат, который произвел довольно хорошее впечатление, поэтому было решено предложить ему более сложную задачу, которую обычно мы не спрашиваем. Вот ее немного видоизмененный вариант:
Переделайте следующий код оставив его многопоточным таким образом, чтобы лампочки зажигались и гасли строго по очереди и в любой момент времени должна быть включена только одна лампочка:
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");
}
}
}
Кандидат предложил использовать ReentrantLock в FairSync режиме. В первом приближении эта идея может показаться рабочей. Передать общий лок в оба оъекта типа Bulb и синхронизироватся там на нем. Тем не менее, этот подход не работает. Если мы заглянем в документацию к классу, то увидим следующее описание:
The constructor for this class accepts an optional fairness parameter. When set true, 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.
FairSync не гарантирует отсутствие race condition'а между потоками. Единственное что он гарантирует это то, что лок возьмет поток который ждал на локе дольше всего. Отсутствие контроля за CPU шедулером не дает нам гарантии что в момент когда поток отпускает лок его оппонент уже попытался сделать acquire на этом же локе (что необходимо для того чтобы сработал FairSync в этой задаче).
К сожалению у меня нет под рукой соответствующего железа, но я подозреваю что на однопроцессорной машине разницы между FairSync и NonfairSync вообще не будет, так как у параллельного потока не будет возможности поставить в очередь заявку на acquire, чтобы при следующем unlock'е его заявка была обслужена.
Правильное же решение задачи остается на совести читателя :)
09 апреля, 2011
Маленький Билд и его друзья
Помните фильм "Пятый элемент"? Там была сцена, когда Зорг опрокидывает стакан на пол и роботы тут же начинают уборку помещения. Всего лишь одно маленькое действие привело в жизнь десяток машин, которые сразу же начали подметать и мыть полы, а в конце еще и налили воды хозяину.
Что-то похожее происходит в коллективе с налаженным build процессом, когда разработчик коммитит изменения в систему контроля версий.
О пользе налаженного процесса билда известно много. Но "путь на production" не прост. После того как код написан и перед тем как он будет запущен на production'е было бы неплохо сделать следующие вещи:
- откомпилировать код если вы используете компилируемый язык (удивил, да?);
- прогнать модульные и интеграционные тесты если они у вас есть;
- выполнить статический анализ кода и/или другие проверки которые позволяет выполнить ваша платформа и которые имеют смысл для вашей команды (code style check, code coverage, цикломатическая сложность, dependency matrix и т.д.);
- собрать артефакт приложения (желательно в виде одного файла), содержащий в себе весь код и ресурсы необходимые для запуска приложения на целевой платформе;
- выполнить приемочное тестирование, если оно у вас есть;
- опубликовать артефакт в репозитории (предположительно локальном), чтобы другие члены комманды могли им воспользоватся в своих целях (особенно актуально, если вы разрабатываете билиотеку, а не приложение);
- в случае если вы разрабатываете приложение а не библиотеку, произвести deploy в окружение staging тестирования чтобы команда и менеджеры могли оценить текущее состояние проекта.
Этот список действий не догма и варьируется от потребностей проекта. Я же расскажу что и как делаем мы. Сразу отмечу что у нас в проекте используется две платформы: PHP и Java. Я преимущественно буду описывать как мы собираем Java проекты, так как за счет поддержки инструментария билд процесс там получается более целостный и вразумительный. Я думаю, этот опыт будет полезен читателям.
Итак код закоммичен в систему контроля версий. С этого момента начинается его маленькое путешествие на production. Но для начала давайте познакомимся с главными героями. Для того чтобы успешно проводить автоматизированною сборку проекта необходим определенный инструментарий.
В главных ролях
Система контроля версий (VCS)
Какую VCS систему использовать решать вам. Это может быть старый проверенный временем subversion, или модно-распределенный git/mercurial. Но она должна быть. Если на вашем календаре уже 2011 год и вы все еще не пользуетесь какой-либо системой контроля версий, то я настоятельно советую вам переоценить принципы согласно котрым вы принимаете решения.
В контексте обсуждения процесса сборки выбор системы контроля версий настолько неважен, что я даже не буду говорить какую используем мы :)
Система сборки проектов (build tool)
Основная задача системы сборки состоит в автоматизации задач связанных с созданием релиза из исходных кодов. Среди программистов работающих с интепретируемыми языками распространено мнение что исходный код и есть релиз. Связано это судя по всему с тем, что в таких языках нет выделенной фазы компиляции. Я считаю, что даже в этом случае разделение между исходниками и релизами нужно, потому что вне зависимости от того на каком языке программирования написано приложение, релиз должен обладать следующими свойствами, которыми не обладают исходные коды:
- любой релиз в отличии от исходных кодов должен работать. Подтверждатся это должно каким-либо видом тестирования (хотя бы ручным). Исходные коды же могут находится некоторое время в нерабочем состоянии (например, во время рефакторинга);
- релиз может содержать third party библиотеки и программное обеспечение, которое разрабатывается и поддерживается третьими лицами. Хранить все это в системе контроля версий может быть не самым удобным решением;
- платформа на которой работает приложение может иметь ограничения на формат релиза. Например, может быть строго определен формат архива с приложением (например, rpm или deb). Хранить исходные коды в VCS в том же формате может быть очень неудобно c точки зрения разработки.
Выражаясь математически: релиз = ƒ(исходники).
Для сборки мы используем Apache Maven, -- это довольно зрелая и продвинутая система сборки, которая впрочем не так проста в изучении как ее более "легковесные" братья вроде ant и gradle (хотя ant — это скорее бабушка, а gradle внучатый племянник). Gradle в последнее время получил довольно много положительных отзывов и продолжает набирать популярность, поэтому вам определенно стоит посмотреть на него, если вы определяетесь с вопросом выбора системы сборки Java-проекта.
Для того чтобы использовать maven более эффективно, у нас есть определенная экосистема для его поддержки.
Во-первых, это репозиторий артефактов о котором я расскажу ниже.
Во-вторых, у нас есть общийэ POM-дескриптор для всех проектов компании, который определяет настройки компилятора, сразу делает доступными библиотеки повсеместно используемые в нашей компании (TestNG, hamcrest, logback и т.д.), а также настраивает плагины для статического анализа кода и логгирования и т.д.
В третьих, у нас есть несколько прототипов проектов (archetype в терминологии maven), которые позволяют одной командой из консоли создать новый проект. Эдакий hello world с уже настроенным логированием, Jetty для тестирования, ActiveMQ и Spring Integration для обработки сообщений, Spring в качестве web-framework'а и много еще чем. Все это очень сильно упрощает старт, особенно людям не знакомым с премудростями настройки всего этого "зоопарка".
Репозиторий артефактов
Еще один вопрос связанный с системой билда — куда ложить его результат? После того как мы собрали приложение и протестировали его, нам надо опубликовать релиз. Необходимо общее централизованное место где бы хранились все артефакты, чтобы любой разработчик знал где искать последний релиз продукта. Позже вы можете достать его и использовать для deploy'я на production или в окружение staging тестирования. Если вы разрабатываете библиотеку то релиз нужен другим членам команды, для того чтобы использовать ее в своем приложении.
В простейшем случае роль репозитория может играть web-сервер. Его настройка для этих задач не займет у вас много времени. Либо это может быть FTP-сервер или сетевой диск доступный всем разработчикам. Так же эту роль может играть continious integration сервер, речь о котором пойдет ниже.
В более сложных случаях удачным решением может быть специализирванное ПО. Особенно это актуально если вы используете Maven, который специфицирует формат репозиториев и протокол работы с ними (поверх HTTP).
Мы используем Artifactory в качестве репозитория, который выполняет несколько ролей в нашей экосистеме:
- хранит артефакты делая их (и их исходники) доступными для использования любым программистом в любое время;
- кеширует third party библиотеки используемые программистами. Если вы начинаете новый проект и хотите использовать какую-то библиотеку, с высокой долей вероятности она уже есть в локальном репозитории и вы получите ее моментально не дожидаясь загрузки из интернета.
Continuous Integration сервер
Не смотря на грозное название, CI-сервера по своей сути — это триггеры билд процесса которые предоставляют разработчикам удобный способ контроллировать процесс и результат сборок. Они делают несколько вещей:
- позволяют конфигурировать политику запуска сборки (при коммите в VCS, через заданные интевалы времени, после успешной сборки зависимого проекта, вручную, и т.д.);
- предоставляют возможность следить за процессом сборки публикуя ее логи через web-интерфейс;
- предоставляют отчеты по результатам билда (проваленные тесты, предупреждения статического анализатора и т.д.);
- строят тренды на основании истории сборок (количество тестов, время затраченное на исправление билда, процент успешных сборок и т.д.);
- могут успешно играть роль репозитория артефактов.
Строго говоря использование CI-сервера не обязательно. Более того, на начальных этапах я бы рекомендовал делать сборки на машинах разработчиков. Это позволит сэкономить вам время в процессе допиливания build-процесса, которое на начальных стадиях неизбежно.
Мы до сих пор не используем CI-сервер для сборки билиотек. Библиотеки собираются и релизятся самим программистами. Несмотря на то что этот подход имеет ряд недостатков, он существенно проще.
В качестве CI-сервера мы используем TeamCity, бесплатной версией которой мы полностью довольны. Одна из привлекательных фич этого продукта состоит в том что он предоставляет неплохую статистику по билдам, включая success rate сборок, количество тестов и т.д.
Поезд отправляется
Итак как это все происходит на практике.
За системой контроля версий следит continuous integration сервер. Как только он замечает какие либо изменения, он делает checkout свежей версии и инициирует процесс сборки проекта.
TeamCity позволяет очень детально настроить с какими параметрами будет запущен build. Вы можете предопределить значения системных переменных нужных вашему процессу билда. Эти переменные могут использоваться например для того чтобы специфицировать на какой БД будет выполнятся тестирование.

Затем в работу вступает Maven. Он компилирует исходники, запускает модульные тесты. Если они провалились, то генерирует отчеты по проваленным тестам, которые будут затем показаны на персональной странице билда, а сам билд завершается и считается проваленным:
Если тесты проходят, то "состав движется дальше". Следующим шагом является сборка артефактов. В терминах continious integration артефакт это любой результат процесса сборки, главная черта которого — воспроизводимость. Для нас это означает что артефакт является процессом работы машины, а не ручных действий человека.
Обратите внимание, что в смежных областях знаний, например в configuration management, термин "артефакт" имеет немного отличные значения.
Воспроизводимость артефакта — очень важное его свойство. Оно позволяет в любой момент времени из любого среза VCS попытатся собрать приложение и посмотреть что из этого получится.
Воспроизводимость артефакта служит хорошей поддержкой для так популярных в наше время итеративных методов разработки, смысл которых состоит в идее "давайте начнем стрелять, а потом будем корректировать огонь". Когда патроны дешевле чем время, это хороший подход. Налаженный процесс автоматического производства артефактов в этом контексте можно сравнить с автоматическим станком по производству патронов. Чем лучше он у вас отточен, тем дешевле ваши патроны, и тем быстрее вы можете стрелять и получать фидбек критически важный для следующей итерации.
После того как артефакт собран Maven принимается за интеграционное и приемочное тестирование. Здесь необходимо отметить зачем разделение между модульными, интеграционными и приемочными тестами.
Модульные тесты тестируют классы в изоляции от других частей системы, а также в изоляции от внешних систем, таких как базы данных и веб-сервисы. Их задача состоит в том чтобы давать программисту быстрый фидбек относительно того, работает система или нет безотносительно того верно ли она интегрируется с внешними источниками данных. Фактически, любой программист должен иметь возможность сделать checkout исходников проекта, запустить модульные тесты на абсолютно не подготовленной машине (например, без базы данных) и тесты должны выполнится. Единственная причина почему они могут не выполнится, это если в коде приложения допущена ошибка.
Таким образом, у модульных тестов не должно быть зависимостей, отличных от тех которые тест может удовлетворить сам. По личному опыту могу сказать: тех кто не соблюдаете это правило ждет не очень приятное будущее.
В сухом остатке. Модульные тесты должны:
- выполнятся быстро. Потолок 15-20 секунд, затем их просто перестают использовать. Здесь нужно обратить внимание на то, что модульные тесты должны выполнятся именно на стороне программиста и как можно чаще. И конечно же обязательно перед коммитом в систему контроля версий. На CI-сервере они выполнятся лишь для полноты тестирования;
- быть изолированными от внешнего окружения и выполнятся даже на машине без сети.
В отличии от модульных, интеграционные тесты проверяют как приложение дружит с внешними системами, и как следствие, обладают следующим рядом особенностей:
- могут требовать довольно сложное окружение для своего выполнения (конкретную БД, возможно даже конкретной версии);
- могут быть довольно медленными, так как включают в себя взаимодействие по сети, зачастую с системами производительность которых находятся под контролем третьих лиц;
- являются гораздо более хрупкими чем модульные, потому что опираются на заранее установленное окружение, а также на программное обеспечение которое скорее всего меняется без оглядки на ваше конкретное приложение.
Тем не менее тестировать все равно надо, поэтому такие тесты выделяются в отдульную группу и запускаются на CI-сервере. Настроить и поддерживать в актуальном состоянии сложное окружение проще один раз на сервере, чем десять раз на машинах разработчиков. Разработчик может запустить интеграционные тесты у себя, но для этого он должен настроить хотя бы часть окружения на своей машине, что может быть довольно непросто. Частично эти проблему можно решить при помощи виртуализации и таких инструментов как puppet.
Обычно интеграционные тесты на стороне разработчика запускаются только в случае если менялся интеграционный код системы и как правило только тесты на изменившуюся часть системы. С точки зрения maven (а именно, maven-surefire-plugin) интеграционные тесты отличаются от модульных только постфиксом (*Test у модульных, *IT у интеграционных). После того как тесту задан соответствующий постфикс, он автоматически начинает запускатся на нужной фазе сборки проекта.
Если с интеграционными тестами все хорошо, то запускаются приемочные. Приемочные тесты проверяют систему по типу черного ящика. Если вы разрабатываете web-приложение, то оно запускается на web-сервере и тест под видом обыкновенного пользователя начинает ходить по нему и проверять его работоспособность. Пожалуй, это один из самых сложных видов тестирования. В нем довольно легко наделать ошибок, которые могут существенно увеличить стоимость поддержки тестовой инфраструктуры. Это в конце концов делает процесс тестирования менее эффективным. Впрочем, тема приемочного тестирования выходит далеко за рамки моего поста, поэтому я не буду заострять на этом сейчас внимание. Людям искушенным очень советую прочитать книгу Джеза Хамбла и Девида Ферли Continuous Delivery.
Ну а мы отправляемся дальше. Следуюшая остановка — статический анализ кода. Мы используем findbugs для анализа кода. Статические анализаторы действительно находят ошибки и потенциальные уязвимости, так почему бы не делать это при каждом изменении наших исходных файлов.
Можно указать TeamCity чтобы при превышении опредленного порога по количеству найденных проблемных ситуаций, билд считался проваленным.
Но, данный шаг можно порекомендовать не всем. Во-первых, для этого требуется существенная поддержка инструментов. Например, для php нет ничего подобного. Во-вторых, получаемая от статического анализатора информация должна оцениваться прагматически. Помните что анализатор показывает риски с формальной точки зрения. У каждого риска есть вероятность материализации и стоимость исправления. Вероятность материализации — это вероятность с которой риск превратится в проблему (то есть вы сможете наблюдать его воочию). Стоимость исправления — это сколько ресурсов (например, в виде человеко часов) нам надо будет потратить если риск все же материализуется в проблему. Перемножив эти два числа мы получим математическое ожидание проблемы. Может оказаться так, что математическое ожидание проблемы гораздо меньше чем стоимость внесения изменений в код здесь и сейчас. Некоторые проблемы дешевле исправлять пост фактум.
После того как статический анализ кода законечен, закончен и процесс непосредственно сборки. Теперь нам осталось опубликовать артефакт в общедоступный репозиторий и можно рапортовать об успешности билда.
Деплой у нас происходит в два этапа. Первый этап это деплой в локальный репозиторий артефактов. Второй этап включает в себя автоматическую доставку приложения на production сервера. Обратите внимание, доставку, но не deploy. Deploy осуществляется только по инициативе и под контролем разработчика. Таким образом, если разаботчик работает над проектом search-service версии 1.0 и на TeamCity успешно выполнился билд под номером 134, то на production кластере в папке проекта автоматически появляется файл search-service-1.0.134.war, который содержит в себе все необходимое для работы приложения и готов к развертыванию по команде разработчика. Это позволяет свести к минимуму участие человека в процессе билда. Человек привлекается только там где его внимание и возможность принимать решения не может заменить машина.
Вот пожалуй и все. Некоторые моменты остались за кадром. Например, как происходит запуск (deploy) приложения на production серверах. Но это тема совсем другого разговора.
Буду рад услышать отзывы и success stories читателей.
14 ноября, 2010
Как я собирал NAS
NAS у меня исполняет несколько обязанностей:
- файловое хранилище "жирного" контента (фильмы, etc);
- сервер для Apple TimeMachine (бекапы);
- качает и раздает торренты;
- "самопальный" мониторинг сетевой активности с использованием rrdtool;
- локальный git-репозиторий.
Мой опыт использования NAS начался с QNAP TS-109 PRO II.
Довольно удачная однодисковая железка, которая прослужила мне верой и правдой более года. У нее несколько минусов:
- собственно всего один диск;
- нестандартный встроенный linux;
- ARM-based процессор.
- установка компилятора и development библиотек тоже довольно нетривиальна (видимо проблема курицы и яйца);
- будучи программистом с серьезным отношением к тестированию я отношусь к той категории людей которые считают, что если ты сам компилируешь пакет, ты должен его сам и тестировать. А мне этого совсем не хотелось.
Таким, довольно эволюционным путем, примерно через полтора года я пришел к мысли что пора менять storage. На этот момент на рынке было несколько device'ов, которые привлекли мое внимание:
Моя вера в x86 на этом поприще росла с каждой новостью связанной с Intel Atom. Поэтому два первых девайса, опять таки построенные на базе ARM процессора, отсеялись сами собой. Два последних представляют уже больший интерес, но анализируя их спецификации я пришел к мысли что я бы мог сам собрать более подходящую мне железку.
Hardware
Я начал подискивать подходящий miniITX корпус. Мягко говоря это не просто, особенно во Владивостоке. Большинство корпусов в этом форм факторе нацелены на создание nettop'ов: имеют максимум 2 корзины под HDD, кучу лишних фентифлюшек вроде headphones output на фасаде и т.д. Мне же хотелось собрать железку не имеющую ничего лишнего и при этом "на вырост", чтобы через пару лет не пришлось менять снова.
В конце концов я нашел корпус, который меня полностью удовлетворил — Chenbro ES3469.
Этот корпус специально создавался под NAS, поэтому обладает довольно характерными особенностями:
- четыре корзины для 3.5" HDD с поддержкой горячей замены и индивидуальной индикацией питания и data activity;
- один внутренний слот для 2.5" HDD (очень удобно так как все четыре жестких можно отдать под хранение данных, а ОС поставить на отдельный пятый жесткий);
- внешний блок питания, что позволяет сэкономить место внутри и избавится от одного лишнего вентилятора (внешний БП без активного охлаждения);
- довольно "богатая" индикация на передней панели.
Сам корпус сделан довольно качественно, внутри разделен на два отсека. В одном отсеке находятся жесткие диски с двумя вентиляторами работающими на выдув, во втором материнская плата и вся коммутация.
Немного огорчило меня то, что одна из корзин сразу не заработала. Сначала я грешил на backplane, но слава богу дело было в неисправном SATA кабеле, который легко поддается замене. Замена правда осуществляется не самым простым образом, — для того чтобы добраться до отсека с корзинами и до backplane'ов, надо буквально говоря разобрать весь корпус, что не очень удобно. Что уж тут поделать, издержки форм фактора.
Следующим шагом стал выбор материнской платы. С ней тоже все оказалось не просто. Надо было найти Atom-based miniITX материнскую плату с полностью пассивным охлаждением и минимум 5 SATA портами! Немного странные требования для форм фактора miniTX, не находите? Такая все же нашлась — Supermicro X7SPA-H:
- двухядерник Intel Atom D510 1.6Ghz с поддержкой HT;
- 6 SATA портов;
- два SO-DIMM DDR2 667MHz модуля, максимум 4Gb.
- две гигабитные сетевые карты;
- полностью пассивное охлаждение;
- довольно простая видеокарта Intel GMA3150, что в моем случае является плюсом, будет меньше кушать и греться;
- поддержка power on after failure, что для девайсов такого класса очень полезно.
Вобщем, эта материнская плата подошла в моем случае просто идеально. Причем, материнская плата и корпус, что называется, соответствуют друг другу. Например, у обоих есть поддержка индикации power fail и chassis intrusion.
Правда, тут произошел казус. Сразу материнка не завелась: питание есть, вентилятор на корпусе вращается, а power LED не горит и стандартного писка о прохождении POST check'а нету. Отключил всю периферию, проверил что нигде ничего не замыкает, проверил память, — один черт, не заводится. Сбросил CMOS, — не заводится. Причем, если достать RAM, то матерится о ее отсутствии на всю квартиру. В конце концов, на третий день, когда вера в то что материнка жива окончательно иссякла, она при самых странных обстоятельствах (во время очередной операции "выткни-воткни оперативку") завелась. То ли чудо, то ли фокус... То ли я два или три десятка раз подряд неверно вставлял оперативную память.
Дальше стало проще. В качестве жестких я выбрал Western Digital линейки Caviar Green, так как они хорошо себя зарекомендовали еще со времен моего первого NAS'а — тихие и холодные. Сейчас у меня стоят два: WD10EADS и WD10EARS. Насколько я понимаю, отличаются они только объемом кеша 32/64Mb.
Так как я старался собрать максимально тихий device, один из вентиляторов в отсеке с жесткими я отключил, а на второй поставил zalman'овский регулятор скорости вращения. Эмпирическим путем была подобрана скорость, которая дает тихую работу. Температура при этом держится в районе 40-42 градусов, что меня вполне устраивает.
Software
Теперь немного об используемом программном обеспечении. Я "на короткой ноге" с Ubuntu, поэтому я выбрал именно этот дистрибутив. Так же, наслушавшись страшных историй о том как восстанавливают данные с RAID-зеркал, я сразу принял решение использовать файловую систему с поддержкой избыточности вместо аппаратного RAID'а.
Собственно, сейчас таких файловых системы две: BTRFS и ZFS. Первая находится на стадии беты и не предназначена для production использования. Вторая довольно стабильная и широко используемая, но в linux реализована в рамках проекта FUSE из-за лицензионных ограничений (изначально ее разработала Sun для ОС Solaris). По совокупности факторов я решил использовать ZFS.
ZFS вещь довольно интересная. Она позволяет собрать из списка блочных девайсов виртуальный том с заданными настройка избыточности. У нее есть ряд преимуществ перед аппаратным RAID'ом:
- в зеркале могут участвовать абсолютно разные жесткие (разного размера);
- выход из строя блочного девайса не запрещает клиентам читать и писать на том, до тех пор пока есть хотя бы одна живая реплика;
- rebuild тома (scrub в терминах ZFS) тоже не блокирует клиентов, они по прежнему могут читать и писать;
- настройки избыточности можно задавать отдельно для директорий. То есть вы можете хранить все в двух копиях, а особо важные для вас папки в трех (для этого том должен состоять как минимум из трех физических дисков).
Все фичи кроме последней я вынуждено "протестировал" в ходе эксплуатации. Так что, да, это действительно работает. Для проверки последней у меня просто не было мотивации, у меня нет настолько важных данных.
Создание ZFS тома делается очень просто:
zpool create tank raidz1 /dev/sda /dev/sdb
После создания, том будет автоматически примонтирован в /tank. Теперь можно заливать на него информацию. Просмотр статуса по виртуальным томам делается следующими командами:
zpool status
zpool iostat -v
Перебалансировка и починка тома осуществляется при помощи команды:
zpool scrub tank
В интернете есть множество информации о ZFS. Если хотите использовать ZFS, советую обратится обратится к официальному мануалу.
Надо понимать что такая гибкость не дается бесплатно. ZFS прожорлив по памяти, поэтому советую оснастить машину гигабайтом, а лучше двумя гигабайтами оперативной памяти. Я так и сделал, благо она дешевая.
С ZFS у меня произошел только один негатвиный инцедент. Во время начальной заливки данных на том, oomkiller'у не понравилось что драйвер ZFS ест много памяти, и он его убил :). Это издержки того что ZFS работает в user space. После reboot'а все стало нормально, ничего чинить не пришлось. Больше такого у меня не повторялось.
Так же, я использую NAS как сервер для Apple Time Machine, поэтому мне надо было настроить его так, чтобы мой mac mini воспринимал его как AFP сервер. В интернете есть полно мануалов, одним из которых я и пользовался.
Вот пожалуй и все что касается софта.
В итоге
Atom все же поменял мое представление о том, какой должен быть домашний файловый сервер. На базе атома вполне можно собрать тихий и холодный storage, и спихнуть на него "домашнюю работу". При этом это все это будет работать под управлением стандартного дистрибутива вашей любимой ОС (хоть windows). А сама железка мне обошлась даже немого дешевле чем Acer easyStore H340.



