Показаны сообщения с ярлыком scalability. Показать все сообщения
Показаны сообщения с ярлыком scalability. Показать все сообщения

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’а позволяющего решать типовые задачи эффективно по аппаратным ресурсам (базы данных, очереди сообщений, языки программирования, библиотеки и т.д.).

В состоянии гонки

У вас как у инженера нет прямого влияния на количество пользователей и есть опосредованое влияние на объемы данных. В конце концов, чем больше данных и чем шире аудитория, тем лучше для бизнеса. Больше пользователей – больше возможностей для монетизации. Больше данных – больше конкурентное преимущество, а так же потенциально более высокие темпы роста проекта. Необходимо смирится с непрерывным ростом этих двух метрик.

Что же остается? Не так много на самом деле. Сделать так чтобы возможности системы были всегда на шаг впереди ее потребностей.

15 февраля, 2010

Superlinear scalability

Как вы думаете во сколько раз может быть быстрее ваша программа, если вам дадут в два раза более "крутое" железо: в два раза более быстрый процессор, в два раза больше памяти и т.д. Интуитивный ответ — в два раза. Раньше я уже писал о том, что не все так безоблачно. Взвесив все аргументы вы можете сказать: "окей, максимум в два раза". Но что если бы я вам сказал, что при увеличении вычислительной мощности в два раза вы можете получить ускорение больше чем в два раза?

Давайте представим себе обратный случай: у вас есть алгоритм с асимптотической оценкой по времени в O(n). И вы решаете задачу эталонного размера на эталонном компьютере за эталонное время. Увеличим размер задачи в два раза. Скажем вместо 5 миллионов записей надо отсортировать 10 миллионов (например, за O(n) можно сделать топологическую сортировку). Как изменится эталонное время? Увеличится в двое... как минимум. Увеличение времени вдвое нам гарантировано, так как мы выполним в два раза больше операций. Но существуют причины для дополнительного замедления программы. Например, если удвоенный dataset не помещается в оперативную память, то вы получите большое количество page fault'ов и операционной системе прийдется одни страницы выгружать из оперативной памяти на жесткий диск, другие загружать с диска в оперативную память. В общем случае, вы можете получить замедление более чем в два раза.

Но что если теперь мы начнем решать эту "удвоенную задачу" на "удвоенном компьютере"? Во-первых, мы получим двукратный прирост производительности связанный с удвоенной вычислительной способностью нового компьютера. Но также мы избавимся от лишних page in/page out'ов, что безусловно еще более убыстрит нашу программу, так как теперь не надо тратить лишнее время на IO. А это значит что в сумме мы получим более чем двукратный прирост производительности.

Утверждение "при удвоении вычислительных ресурсов можно получить максимум двукратный прирост производительности" имеет силу только в условиях когда задача решается эффективно (то есть не делается лишней работы).


16 января, 2010

KV-хранилища

В последнее время в web-программировании появился очередной тренд — Key-Value базы данных. Существует просто великое множество KV-решений, — одно лучше другого. Но так ли они важны и какая от них польза? Разрешите мне немного порассуждать о происхождении KV-хранилищ.

Нет дыма без огня

Недовольство реляционными базами данных начало появляться давно. Оказалось что на некоторых use case'ах они не такие быстрые как хотелось бы. Они слишком сложные для того чтобы большинство программистов понимало как они работают, а следственно и то, как их использовать. Идея изоляции программиста от физических деталей хранения данных с треском провалилась. Если вы не знаете нюансов вашей РСУБД, то эффективно использовать ее на большом dataset'е или при высокой конкурентной нагрузке у вас не получится.

Помните чему вас учили в университете? Первая нормальная форма, вторая, тертья, Бойса-Кодда и т.д. Бурный рост интернет-аудитории с одной стороны и функциональности web-приложений с другой привел к тому, что пришлось пересмотреть паттерны использования реляционных СУБД. Сейчас, для того чтобы получить от реляционной БД приемлемый уровень пропускной способности, необходимо денормализовывать данные, отказываться от распределенных транзакций и проверки целостности по внешним ключам.

Но не стоит спихивать эти проблемы на не дальновидность или не профессионализм разработчиков реляционных СУБД. Насколько хорошо вы себе представляете как работает реляционная СУБД? Давайте проведем quick test. Ответьте на следующие вопросы:

  • вы знаете что такое план выполнения запроса и как БД его строит?
  • вы знаете как БД использует несколько индексов для фильтрации в случае, если нет index'а покрывающего все условия фильтрации?
  • вы знаете как БД использует индексы для сортировки, группировки и join'ов?
  • вы знаете сильные и слабые стороны hash и btree индексов?
  • вы знаете разницу между nested loop join, merge join и hash join?

Если вы утвердительно ответили хотя бы на половину вопросов, то вы знаете, что какая бы продвинутая БД у вас не была, сложные join'ы, сложные критерии фильтрации и группировки требуют много времени, какие бы индексы вы ни создавали. Сделать эффективный индекс (индекс позволяющий не делать лишней работы и определить результат только по этому индексу) можно только если у вас невысокая вариативность выборок и вы знаете все виды возможных запросов.

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

KV-storage

Однажды приведя свою систему к тому состоянию, когда большинство выборок в ней производится по первичному ключу, вы можете задаться вопросом: а на кой черт мне здесь реляционная СУБД? Действительно, все эти статистические выкладки по cardinality индексов, эвристики заменяющие множество lookup'ов на один sequential read нужны были только тогда когда мы не знали какой запрос прийдет от клиента. Теперь мы знаем — это lookup по id. А раз мы знаем, то мы можем написать хранилище не делающее ничего лишнего — KV-storage.

Вот так они и появились. KV-хранилища — это не "серебрянная пуля" и не "RDBMS killer". Это следствие эволюции взглядов на паттерны доступа к данным. KV-хранилища быстрые лишь потому, что они предоставляют только один способ доступа к данным — lookup по id. Они быстрые потому, что не обременены, как РСУБД, необходимостью тратить лишнее время на определение оптимального плана выполнения запроса, чтобы сэкономить гораздо больше времени во время выполнения этого запроса.

Фронт NoSQL

Но современные постреляционные базы данных (если позволите так их назвать) ушли гораздо дальше чем просто lookup по id. Сейчас начинают набирать популярность документо-ориентированные базы данных (CouchDB, MongoDB), которые предоставляют более сложные способы извлечения и модификации данных. Некоторые KV-хранилища умеют нативно работать с коллекциями. Но эти возможности все равно меньше чем у реляционных баз данных.

Весь фронт NoSQL держится на том, что большинству web-приложений не нужны все возможности реляционной БД. Ну или скажем так, они могут обойтись без всех возможностей реляционной БД в угоду производительности.

Масштабируемость KV-решений

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

Задумайтесь, что дает вам, например, memcachedb для того чтобы легко масштабироваться? Кто-то из вас может сказать: "Легко. Берем остаток от деления хеша первичного ключа на количество серверов и...". Ну ладно ладно, я понял. Кто-то может вспомнить про consistent hashing. Отлично. Но дело в том, что это не хранилище дает вам эту возможность partitioning'а, а тот паттерн доступа к данным которым вы пользуетесь. С таким же успехом, я могу легко вместо memcachedb использовать MySQL и утверждать что "MySQL легко масштабируется".

Справедливости ради, надо сказать что некоторые решения (Cassandra, Project Voldemort, Scalaris) сами по себе предоставляют решения для автоматического partitioning'а ключей по нодам кластера. В отношении этих решений утверждение о масштабируемости все же верно.

Выводы

Суровая реальность научила нас тому, что для того чтобы быстро получать доступ к данным их необходимо хранить в удобном для оперирования над ними виде. Данные и их структура первичны в случае если вы хотите получить максимум производительности. Этому существует немало подтверждений (впрочем, пока эти подтверждения находятся в областях не связанных напрямую с web-программированием, поэтому они могут показаться вам безосновательными).

Мой совет web-программистам заключается в том, чтобы они пересматривали паттерны доступа к данным в своих приложениях, и по возможности сводили их к более простым. Это может вам обеспечить необходимый уровень производительности, а позже и масштабируемости.

К KV-хранилищам я бы советовал относится более осторожно. 100K запросов в секунду выглядит конечно заманчиво, но помните, — любая реляционка на подобных запросах ведет себя довольно шустро. Внедрение же еще одного продукта в проект увеличивает его себестоимость владения.


06 декабря, 2009

Гарантия доставки сообщений и ее последствия

В прошлой заметке, я затронул тему порядка доставки сообщений MQ-системами. Отсутствие гарантий в отношении этого порядка вызвало некоторое возмущение со стороны читателей, поэтому я решил раскрыть эту тему более полно.

Почему же многие очереди сообщений не гарантирую порядок? И так ли он вообще важен — этот порядок доставки?

Почему многие системы очередей не гарантируют порядок доставки?

Здесь потребуется договорится об обозначениях. Для любых двух событий A и B запись “A→B” означает, что событие A происходит перед событием B. Людям знакомым с Java Memory Model эта запись должна быть известна как отношение happens-before.

Давайте представим себе очередь сообщений, которая гарантирует вышеупомянутый порядок и двух подписчиков читающих сообщения из этой очереди.

image #1

Исходя из этого мы можем определить порядок доставки сообщений следующим образом. Для двух любых сообщений m1 и m2 для которых выполняется условие receive(m1)→receive(m2) (то есть брокер получил сначала сообщение m1 потом m2), если гарантируется что dispatch(m1)→dispatch(m2), значит очередь сообщений гарантирует порядок доставки.

Тем не менее гарантия в отношении порядка доставки мало что дает. Даже если сообщения доставлены в порядке (in-order), consumer’ы могут обработать сообщения в порядке отличном от порядка доставки (out-of-order).

image #2

Как любит говорить один мой коллега: “Тому есть тысяча причин”. Машины на которых работают consumer’ы могут быть разной конфигурации: у них могут быть разные процессоры, разный объем памяти, разная по производительности подсистема I/O. Но даже если они идентичны, у вас нет контроля над детерминизмом cpu и I/O scheduler’ов. Любая машина может отказать или начать медленно работать из-за большого количества pagefault’ов и т.д. Все это говорит о том, что порядок доставки сообщений не имеет ничего общего с порядком их обработки. А ведь именно порядок обработки, а не доставки должен интересовать нас в первую очередь.

Порядок обработки сообщений мы можем определить следующим образом. Для двух любых сообщений m1 и m2 для которых выполняется условие receive(m1)→receive(m2), если гарантируется что ack(m1)→ack(m2), значит очередь сообщений гарантирует порядок обработки.

Но соблюсти это правило очередь сообщений может только одним способом. Путем форсирования порядка ack(m1)→dispatch(m2). Другими словами, брокер не должен отправлять следующее сообщение пока предыдущее не будет обработано. Это подразумевает следующую картину.

image #3

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

Получается что порядок доставки сам по себе ничего не значит, — важен порядок обработки, предоставление гарантий в отношении которого убивает на корню пропускную способность.

А важен ли порядок обработки?

Иногда да. Есть ситуации когда порядок обработки очень важен. Предыдущая заметка иллюстрирует один из таких случаев. Но вы должны понимать, что любой порядок в web-приложении является имитацией. В любом web-приложении клиенты посылают запросы параллельно. Эти запросы параллельно идут по сети, параллельно обрабатываются frontend’ами, параллельно идут на backend’ы и так же параллельно ответы идут обратно к пользователям.

Место где порядок обработки начинает проявляться — это реляционная база данных. Если два потока в транзакции модифицируют одни и те же кортежи, база данных останавливает выполнение одного из потоков до тех пор пока другой не завершит свою работу. База данных линеаризует выполнение нескольких потоков таким образом, что в системе появляется порядок обработки. Но вместе с появлением порядка обработки испаряется пропускная способность. К тому же тот порядок который навязывает база данных может не совпадать с порядком поступления запросов на frontend. Для двух любых запросов req1 и req2 связанных порядком receive(req1)→receive(req2) соблюдение правила transaction(req1)→transaction(req2) не гарантируется.

В свою очередь, это означает что сложность, которая присуща решению продемонстрированному в предыдущей заметке, не является сложностью присущей асинхронным системам обработки сообщений. Это сложность присущая web-приложениям в целом. И даже если бы вы исключили MQ-систему из транка обработки и, скажем, посылали бы сообщения через SOAP или напрямую писали в базу данных, вам все равно пришлось бы реализовывать те же механизмы, чтобы обеспечить сохранность данных.

Любое web-приложение — это конкурентная система в природе которой порядок обработки запросов отсутствует.

Некоторых людей такая ситуация не устраивает. Отчасти являясь идеалистами (что особенно характерно для программистов) они не могут ужиться с мыслью, что те или иные процессы протекают стохастично и не имеют порядка в своей природе. Попытки навязать этот порядок обрекают web-приложение на деградацию пропускной способности. Вы можете наблюдать этот синдром повсюду — начиная от многопоточного программировния и использования mutex’ов до баз данных и протоколов XA-транзакций. В свое время Dan Pritchett написал об этом отличное эссе — “Chaotic Perspectives”.

Если вы web-программист, то я вас поздравляю. Судьба сделала вам подарок. В других отраслях программистам приходится прилагать титанические усилия для распараллеливания задач. У вас же большая часть процессов протекает и так параллельно. Все что вам надо, — смирится с мыслью, что порядок обработки запросов в системе не детерминистичен. С этим бессмысленно бороться, этим надо уметь пользоваться.

18 августа, 2009

О проблемах растущего размера

Все таки интернет индустрия развивается. Теперь на каждом углу высоконагруженные проекты, миллионы пользователей, требования к high-availability, масштабиремости. Причем вот ведь странно. К масштабиремости всегда относятся как-то однобоко. Иногда ее путают с производительностью. А иногда не понимают что масштабируют.

Dan Pritchett, один из архитекторов eBay (теперь уже бывший), еще в 2006 году написал отличное эссе "You scaled your what?" на тему векторов масштабирования (scaling vectors). Действительно, существует масса направлений которые требуют внимания при интенсивном росте проекта, а следственно и сложности решаемых в проекте проблем. А это значит, что употребляя слово "масштабируемость", мы должны отдавать себе отчет в том, в каком контексте мы употребляем это слово.

Transactional scalability

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

Data scalability

Тоже довольно популярный вектор. Насколько хорошо система справляется с ростом dataset'а. Бывает так, что система хорошо приспособлена к росту пользовательской аудитории, но плохо переносит возрастающий набор данных. Причиной может быть природа реляционных баз данных, которые плохо приспособлены к высокой конкурентной нагрузке.

Важно понимать, что цена хранения одного мегабайта данных зависит от цены потери одного мегабайта данных. При росте dataset'а, использовать один и тот же подход для хранения данных разной важности крайне неэффективно. Как и в случае с transactional scalability типичным ответом на data scalability является горизонтальное масштабирование.

Operational scalability

Очень важный вектор, который до поры до времени игнорируется многими командами. Программное обеспечение нуждается в поддержке. Его необходимо контролировать, мониторить, тестировать и выявлять ошибки (желательно раньше чем это сделают ваши пользователи), определять потенциально узкие места с точки зрения производительности (до того, как они вас "задушат"). Сложность всех этих процессов растет вместе со сложностью системы.

Представьте себе простое web-приложение работающее на одном сервере и использующее в качестве базы данных простые текстовые файлы. Такое приложение может представлять из себя буквально пару десятков скриптов. Вряд ли бы у вас возникли проблемы с организацией процесса разработки и тестирования такого приложения. А что если это система распределена по 15 серверам, написана на разных языках и использует самое различное middleware обеспечение? Как бы вы вообще запустили бы такую систему на машине разработчика? И стали бы вы вообще это делать?

Если не уделять внимания этому вектору, то при росте инфраструктурной сложности он вам нанесет "ответный удар", не сомневайтесь.

Feature time-to-market scalability

Ничто так не омрачает большой проект, как большое количество программистов. Большое количество программистов = большое количество проблем. Но тем не менее, бизнес не стоит на месте — он растет. Много идей, кто-то должен их реализовывать. Для этого нужны новые люди. Но новые люди не всегда увеличивают производительность команды. Иногда все происходит совсем наоборот.

Умение при помощи растущего коллектива решать пропорционально растущий объем задач — это искусство.

Я назвал всего четыре вектора. Есть и другие, такие как: power scalability, deployability и другие. Более того внутри компании могут быть свои scaling vectors, которые обусловлены спецификой ее функционирования.

Конечно же (и это уже классика жанра), вы не сможете добиться максимума по всем направлениям — необходимо выбирать. Надо понимать, какие направления для вас являются приоритетными в краткосрочной перспективе, и что может изменится в долгосрочной. Поэтому, когда вы в следующий раз будете употреблять слово "масштабируемость", задумайтесь — масштабируемость чего?


15 июля, 2009

Fail fast

$data = file_get_contents($location);

Что не так с этим кодом? На первый взгяд все хорошо. Вряд ли он может нам чем-нибудь навредить.

Хорошо, предположим, что у вас есть 3 web frontend'а, каждый из которых может обслуживать 60 параллельных запросов (итого 180 конкурентных запросов). На минутку предположим, что вышеприведенный код — это единственный код, который выполняется при обработке запроса. Если location указывает на локальный ресурс (на локальной файловой системе), то вряд ли что-то может пойти не так. Latency чтения с локальной файловой системы достаточно низкий для того, чтобы не беспокоится о возможных проблемах (если файлы достаточно маленькие).

А что если location указывает на удаленный ресурс? Например, на какой-нибудь ресурс доступный по протоколу http. Предположим, что ресурс недоступен. Как поведет себя ваше приложение?

В лучшем случае, вы моментально получите управление назад, сможете определить ошибочную ситуацию и каким-либо образом восстановить поток исполнения. Но так бывает далеко не всегда. В случае если недоступна физический машина (а не приложение на этой машине), или если на сервере некорректно настроен firewall, то вы не получите управление назад. Клиент отсылает TCP SYN запрос и... тишина. Некому вам сказать, что ресурс недоступен, и все что вам остается — это ждать.

Возможно у вас возник вопрос: "А как это меня касается? Я же не могу оживить удаленный ресурс". Два момента.

Во-первых, пользователи не любят ждать. Особенно они не любят когда после ожидания им говорят: "извините, мы не смогли обработать ваш запрос". Зачем было ждать ЭТО? Во-вторых, путем довольно нехитрых расчетов можно вычислить, что если скорость поступления запросов равна 30 запросам в секунду, а запрос обрабатывается дольше 6 секунд, то при 180 обработчиках у вас переполнится очередь входящий запросов и вы перестанете отвечать на эти самые запросы. А пользователи, как известно, не любят ждать. Числа, может быть и ничего не говорящие, но смысл в том, что они конечны, а следственно конечен и тот timeout превышение которого для вас может закончится отказом в обслуживании для всех новых запросов.

В современном мире распределенных приложений, в котором даже яичницу приготовить сложнее, чем отправить SOAP запрос, — подобная ситуация с зависимостью между различными приложениями не редка.

Поэтому реализуя системы которые имеют синхронные зависимости от 3rd party ресурсов необходимо соблюдать ряд правил:

  • если обращение к внешней системе никак не влияет на user experience пользователя и выполняет чисто служебные функции (например, логгирование статистики), то отличным вариантом будет замена синхронного вызова, на асинхронный (например, при помощи ActiveMQ и STOMP);
  • если асинхронная обработка невозможна, то необходимо корректным образом настроить connection и read timeout'ы. Это позволит ограничить то время, которое клиент будет ждать ответа от сервера и исключить возможность отказа в обслуживании;
  • если сервер на котором располагается ресурс находится под вашим управлением, необходимо проверить, что в iptables нет правил, которые осуществляют DROP пакетов. DROP правила, в отличии от REJECT, откидывают входящий пакет, без соответствующего ICMP-ответа клиенту о недоступности ресурса, что и провоцирует такие ситуации;
Помните, для того чтобы исправить ошибку, надо сначала обнаружить ошибку.

28 июня, 2009

Perfomance vs. scalability

Иногда встречаюсь c непониманием того, что такое производительность, а что такое масштабируемость. Почему-то некоторые люди считают, что это одно и то же.

С производительностью все более менее понятно. Это мера скорости работы системы. Запрос поступает в систему и через некоторое время система генерирует ответ. Это время (которое иногда называют latency) и является мерой производительности системы. Для интернет-приложений время получения ответа клиентом может быть значительно больше, чем время генерации ответа на стороне сервера (потери в канале, необходимость загрузки дополнительных статических ресурсов и т.д). Обычно эти издержки не учитываются при оценке производительности, хотя надо признать, они могут очень сильно влиять на user experience работы с приложением.

С масштабируемостью все немного сложнее . Если производительность это ответ на вопрос "как быстро?", то масштабирумость — на вопрос "как много?". Как много пользователей одновременно могут пользоваться ресурсом? Как много данных может быть в базе данных прежде чем она перестанет справляется с нагрузкой?

Иначе говоря, оптимизация под производительность подразумевает делать ту же работу используя меньше ресурсов. Под масштабируемость — используя больше ресурсов, делать пропорционально больший объем работы.

Это может показаться странным, но оптимизация под масштабируемость нередко приводит к худшей производительности. С точки зрения производительности наверное нет ничего лучше чем sql-вызовы прямо в шаблоне (зачем тратить лишнее время на вызовы полиморфных методов, да?). Но для того чтобы хорошо масштабироваться по объему данных в БД, необходимо уметь "пилить" нагрузку между серверами. А для этого требуется гибкий механизм диспетчеризации обращений к базе данных (ORM, ActiveRecord, DataMapper, DAO или что там у вас). Нам нужен некий промежуточный слой, который бы абстрагировал клиента от знания где именно (на каком сервере) лежат данные, которые он запрашивает. В зависимости от специфики реализации такие слои могут добавлять существенный overhead к latency ответа. И все это делается только ради того чтобы быть больше, а не быстрее (на самом деле делается гораздо больше, это всего лишь частный пример), потому что в большинстве случаев быть большим выгоднее, чем быть быстрым.

Только не поймите меня неправильно, я не хочу сказать, что быть быстрым не надо. Пользователи не будут ходить на ваш ресурс если страницы будут открываться по 10 секунд. Что я хочу сказать, так это то, что ваши пользователи не будут платить вам больше, если страницы вашего ресурса будут открываться не 0.2 секунды, а 0.1. Но если у вас будет в два раза больше пользователей...

13 января, 2009

Конец эры закона Мура

Начиная с 70-х годов прошлого века производители микропроцессоров осуществляли экспоненциальный рост производительности, описанный законом Мура. Но достигнув физических ограничений, связанных с резким скачком тока утечки транзисторов и, как следствие, рассеивания тепла, стало понятно — дальнейшее увеличение производительности процессоров существующими техниками невозможно. Еще в 2003 году Intel обещала предоставить 4 ГГц модель. И вроде бы годом позже intel'овцы приблизились к реализации задуманного, — появился процессор с тактовой частотой 3.8 ГГц. Но 4 гигагерцовым "мечтам" так и не суждено было сбыться. А все современные модели процессоров работают на тактовой частоте до 3 ГГц. Индустрия, как мы все теперь знаем, пошла другим путем. Выходом стало увеличение количества процессоров (ядер).

Причем, мне кажется, что Intel давно предвидела что дни гигагерцовых гонок сочтены. Hyper-Threading тому доказательство. Эта технология изначально появилась в архитектуре Xeon, но позже была реализована и в Pentium 4. Это был первый сигнал нам разработчикам, — учитесь распараллеливать. Затем появились многоядерные процессоры. А теперь, Anwar Ghuloum, ведущий инженер Intel, открыто просит разработчиков учитывать особенности SMP архитектуры.
developers should start thinking about tens, hundreds, and thousands of cores now in their algorithmic development and deployment pipeline

Халява кончилась. Дальнейший рост производительности возможен, только путем изменения существующих алгоритмов и их распараллеливания. И именно нам разработчикам придется поставить все эти ядра на службу человечеству. Это и имеют в виду, когда говорят, что наступил конец эры закона Мура.

Справедливости ради надо отметить, что технически закон Мура все же будет выполняться, так как он предсказывает экспоненциальный рост числа транзисторов в интегральный микросхемах. Однако экспоненциального роста производительности при этом наблюдаться не будет. Как такое может быть? На этот вопрос отвечает преемник закона Мура, который и будет вершить бал с этих пор, — закон Амдаля.

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

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

Не знаю во сколько вы оценили свой фактор сериализации, но у меня для вас плохие новости. Даже если вы его оценили в 0.1 (10%) — это означает, что вы перестаете эффективно масштабироваться примерно на 10 процессорах (прирост производительности от дальнейшего добавления исполнителей составляет меньше 50% от теоретически возможного). При факторе 0.5 (половина вычислений) добавление пятого процессора в систему ускорит ее максимум на 6% по-сравнению с четырех-процессорной. Говорить об эффективном использовании 8-процессорной машины не приходится. А ведь попадаются очень выдающиеся сервера.

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

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

Но нет худа добра. Я верю, что мы со временем создадим эффективные и что еще более важно, удобные механизмы для распараллеливания задач. Они уже начинают появляться. У бизнес сектора начинает просыпаться интерес к кластерным вычислениям, что подтверждается появлением таких платформ как Amazon EC2/S3 и Java Stax. Появляются новые языки и инструменты, такие как Scala и Kilim, которые позволяют эффективно работать с concurrency. Получают более широкое распространение уже знакомые методики функционального программирования, которые очень помогают в данном случае. А это все говорит о том, что у нас с вами есть огромное поле для развития и самосовершенствования, чего я вам и желаю.

09 января, 2009

8 на 8

Это скриншот с Windows 2008 R2 с запущенным на нем SQLServer.

Примечательна сама загрузка. Загрузить 256 ядер почти на полную одним приложением не так-то просто. У MySQL, например, проблемы начинаются уже с 8 ядрами. У нас в проекте мы это испытали на собственной шкуре.

Как заметил Doug Holland, для того чтобы наблюдать такие полотна графиков, надо иметь по меньшей мере 30" монитор. Task manager плохо масштабируется по колличеству ядер, если вам угодно :).

Так или иначе тенденция налицо. Да здравствует век параллельных вычислений.

16 июня, 2008

MySQL Queue

Случайно нашел интерестное программное решение. Q4M - иммитация сервера очереди сообщений посредством MySQL сервера (начиная с версии 5.1). Написано что fast, robust и flexible (интерестно, бывает что-нибудь одно?). Однако, дозволяются условные выборки из очереди, несмотря на то, что в limitations указано, - индексы при этом не используются. Вообще, довольно трудно представить себе где бы могла мне пригодится такая очередь сообщений. Cуществует куча бесплатных специализированных решений, которые создавались специально для обработки очередей сообщений. Взять хотя бы ActiveMQ или JBossMQ. Поддержка транзакционности, оптимизирована для быстрой вставки (в принципе, как и любая другая MQ система), поддержка практически всех популярных скриптовых языков посредством STOMP, встроенный message router, поддержка репликации, поддержка "forward on demand bridge" и куча еще чего. Учитывая то, что MQ системы обычно применяются либо при интеграции приложений либо в целях масштабируемости приложений (что говорит о больших обьемах данных), непонятно зачем нужно решение подобное Q4M. Рано или поздно вы упретесь в пропускную способность sql-сервера по чтению, добавите slave серверов, а затем упретесь в ту же пропускную способность, но уже по записи. И тут уже никакая репликация не спасет. Справедливости ради надо отметить, что Q4M предоставлят возможности, которых в других mq системах нет. Например, conditional consuming и join'ы очередей с обыкновенными таблицами! В прицнипе, кому-то может пригодится. Ebay, если верить некоторым источникам, использует самописный messaging стек, который поддерживает conditional consuming. Это упрощает использование очереди, да и EIP была бы раза в два тоньше. Вот только за все приходится платить, и за удобство использования тоже. Особенно, когда за очередью прячется реляционная база данных. В данном случае платить придется производительностью, failover'ом и масштабирумостью.