Разработка информационных систем: фактор Масштабируемости

Как обеспечить масштабируемость разрабатываемой информационной системыДаже если на сегодняшний момент система работает надежно, нет гарантий, что она будет так же работать в будущем. Одна из частых причин снижения эффектив­ности — рост нагрузки: например, система выросла с 10 тыс. работающих одновре­менно пользователей до 100 тыс. или с 1 до 10 млн. Это может быть и обработка значительно бОльших объемов данных, чем ранее.

Масштабируемость (scalability) — термин, который я буду использовать для описания способности системы справляться с возросшей нагрузкой. Отмечу, одна­ко, что это не одномерный ярлык, который можно «навесить» на систему: фразы «X — масштабируемая» или «Y — немасштабируемая» бессмысленны. Скорее, об­суждение масштабирования означает рассмотрение следующих вопросов: «Какими будут наши варианты решения проблемы, если система вырастет определенным образом?» и «Каким образом мы можем расширить вычислительные ресурсы в целях учета дополнительной нагрузки?».


Оглавление статьи[Показать]


Описание нагрузки

Во-первых, нужно сжато описать текущую нагрузку на систему: только тогда мы сможем обсуждать вопросы ее роста («Что произойдет, если удвоить нагрузку?»). Нагрузку можно описать с помощью нескольких чисел, которые мы будем на­зывать параметрами нагрузки. Оптимальный выбор таких параметров зависит от архитектуры системы. Это может быть количество запросов к веб-серверу в секун­ду, отношение количества операций чтения к количеству операций записи в базе данных, количество активных одновременно пользователей в комнате чата, частота успешных обращений в кэш или что-то еще. Возможно, для вас будет важно среднее значение, а может, узкое место в вашей ситуации будет определяться небольшим количеством предельных случаев.

Чтобы прояснить эту идею, рассмотрим в качестве примера социальную сеть Twitter, задействуя данные, опубликованные в ноябре 2012 года. Две основные операции сети Twitter таковы:

  • публикация твита — пользователь может опубликовать новое сообщение для своих подписчиков (в среднем 4600 з/с, пиковое значение — более 12 000 з/с);
  • Домашняя лента — пользователь может просматривать твиты, опубликованные теми, на кого он подписан (300 000 з/с).

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

1.    Публикация твита просто приводит к вставке нового твита в общий набор записей. Когда пользователь отправляет запрос к своей домашней ленте, выполняется поиск всех людей, на которых он подписан, поиск всех твитов для каждого из них и их слияние (с сортировкой по времени). В реляционной базе данных, такой как на рис. 1, это можно выполнить путем следующего запроса:

SELECT tweets.*, users.* 
   FROM tweets JOIN users ON tweets.sender_id = users.id 
               JOIN follows ON follows.followee_id = users.id 
WHERE follows.follower_id = current_user

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

Первая версия социальной сети Twitter использовала подход 1, но система еле справлялась с нагрузкой от запросов домашних лент, вследствие чего компания переключилась на подход 2. Этот вариант работал лучше, поскольку средняя часто­та публикуемых твитов почти на два порядка ниже, чем частота чтения домашних лент, так что в данном случае предпочтительнее выполнять больше операций во время записи, а не во время чтения.

Однако недостатком подхода 2 является необходимость в значительном количе­стве дополнительных действий для публикации твита. В среднем твит выдается 75 подписчикам, поэтому 4,6 тысяч твитов в секунду означает 345 тысяч записей в секунду в кэши домашних лент. Но приведенное здесь среднее значение маски­рует тот факт, что количество подписчиков на пользователя сильно варьируется, и у некоторых пользователей насчитывается более 30 миллионов подписчиков. То есть один твит может привести к более чем 30 миллионов записей в домашние ленты! Выполнить их своевременно — Twitter пытается выдавать твиты подписчикам в течение пяти секунд — непростая задача.

Простая реляционная схема реализации домашней ленты Twitter 

 Рис. 1. Простая реляционная схема реализации домашней ленты Twitter

Конвейер данных Twitter для выдачи твитов подписчикам с параметрами нагрузки по состоянию на ноябрь 2012 года

Рис. 2. Конвейер данных Twitter для выдачи твитов подписчикам с параметрами нагрузки по состоянию на ноябрь 2012 года

В примере с сетью Twitter распределение подписчиков на одного пользователя (возможно, взвешенное с учетом частоты твитов этих пользователей) — ключевой параметр нагрузки для анализа масштабирования, ведь именно он определяет на­грузку разветвления по выходу. Характеристики ваших приложений могут очень сильно отличаться, но для рассуждений относительно их нагрузки все равно можно применять аналогичные принципы.

И последний поворот истории Twitter: реализовав ошибкоустойчивый подход 2, сеть понемногу начинает двигаться в сторону объединения обоих подходов. Большинство записей пользователей по-прежнему распространяются по домашним лентам в момент их публикации, но некое количество пользователей с очень большим количеством подписчиков (то есть знаменитости) исключены из этого процесса. Твиты от знаменитостей, на которых подписан пользователь, выбираются отдельно и сливаются с его домашней лентой при ее чтении, подобно подходу 1. Такая гибридность обеспечивает одинаково хорошую производительность во всех случаях.

 

Описание производительности

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

  • Как изменится производительность системы, если увеличить параметр нагруз­ки при неизменных ресурсах системы (CPU, оперативная память, пропускная способность сети и т. д.)?
  • Насколько нужно увеличить ресурсы при увеличении параметра нагрузки, чтобы производительность системы не изменилась?

Для ответа на оба вопроса понадобятся характеристики производительности, так что рассмотрим вкратце описание производительности системы.

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

 

Время ожидания и время отклика

Термины «время ожидания» (latency) и «время отклика» (response time) часто используются как синонимы, хотя это не одно и то же. Время отклика — то, что видит клиент: помимо фактического времени обработки запроса (время обслуживания, service time), оно включает задержки при передаче
информации по сети и задержки сообщений в очереди. Время ожидания — длительность ожидания запросом обработки, то есть время, на протяжении которого он ожидает обслуживания.

 

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

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

Довольно часто смотрят именно на среднее время отклика. (Строго говоря, термин «среднее» не подразумевает значения, вычисленного по какой-либо конкретной формуле, но на практике под ним обычно понимается арифметическое среднее: при n значениях сложить их все и разделить на n.) Однако среднее значение дале­ко не лучшая метрика для случаев, когда нужно знать «типичное» время отклика, поскольку оно ничего не говорит о том, у какого количества пользователей фак­тически была такая задержка.

Обычно удобнее применять процентили. Если отсортировать список времен от­клика по возрастанию, то медиана — средняя точка: например, медианное время отклика, равное 200 мс, означает, что ответы на половину запросов возвраща­ются менее чем через 200 мс, а половина запросов занимает более длительное время.

среднее значение и процентили: время отклика для выборки из 100 запросов к сервису

Рис. 3. Иллюстрирует среднее значение и процентили: время отклика для выборки из 100 запросов к сервису

Это делает медиану отличной метрикой, когда нужно узнать, сколько пользовате­лям обычно приходится ждать: половина запросов пользователя обслуживается за время отклика меньше медианного, а оставшиеся обслуживаются более длительное время. Медиана также называется 50-м процентилем, который иногда обозначают p50. Обратите внимание: медиана относится к отдельному запросу; если пользо­ватель выполняет несколько запросов (за время сеанса или потому, что в одну страницу включено несколько запросов), вероятность выполнения хотя бы одного из них медленнее медианы значительно превышает 50 %.

Чтобы выяснить, насколько плохи аномальные значения, можно обратить внимание на более высокие процентили: часто применяются 95-й, 99-й и 99.9-й (сокращенно обозначаемые p95, p99 и p999). Это пороговые значения времени отклика, для кото­рых 95 %, 99 % или 99,9 % запросов выполняются быстрее соответствующего по­рогового значения времени. Например, то, что время отклика для 95-го процентиля равно 1,5 с, означает следующее: 95 из 100 запросов занимают менее 1,5 с, а 5 из 100 занимают 1,5 с либо дольше. Данная ситуация проиллюстрирована на рис. 3.

Верхние процентили времени отклика, известные также под названием «хвосто­вых» времен ожидания, важны потому, что непосредственно оказывают влияние на опыт взаимодействия пользователя с сервисом. Например, Amazon описывает требования к времени отклика для внутренних сервисов в терминах 99.9х про- центилей, хотя это касается лишь 1 из 1000 запросов. Дело в том, что клиенты с самыми медленными запросами зачастую именно те, у кого больше всего данных в учетных записях, поскольку они сделали много покупок, — то есть они являются самыми ценными клиентами. Важно, чтобы эти клиенты оставались довольны; необходимо обеспечить быструю работу сайта именно для них: компания Amazon обнаружила, что рост времени отклика на 100 мс снижает продажи на 1 %, а по другим сообщениям, замедление на 1 с снижает удовлетворенность пользователей на 16 %.

С другой стороны, оптимизация по 99.99-му процентилю (самому медленному из 10 000 запросов) считается слишком дорогостоящей и не приносящей достаточ­но выгоды с точки зрения целей Amazon. Снижать время отклика на очень высоких процентилях — непростая задача, поскольку на них могут оказывать влияние по независящим от вас причинам различные случайные события, а выгоды от этого минимальны.

Например, процентили часто используются в требованиях к уровню предоставле­ния сервиса (service level objectives, SLO) и соглашениях об уровне предоставления сервиса (service level agreements, SLA) — контрактах, описывающих ожидаемые производительность и доступность сервиса. В SLA, например, может быть указано: сервис рассматривается как функционирующий нормально, если его медианное время отклика менее 200 мс, а 99-й процентиль меньше 1 с (когда время отклика больше, это равносильно неработающему сервису), причем в требованиях может быть указано, что сервис должен работать нормально не менее 99,9 % времени. Благодаря этим метрикам клиентские приложения знают, чего ожидать от сервиса, и обеспечивают пользователям возможность потребовать возмещения в случае несоблюдения SLA.

За значительную часть времени отклика на верхних процентилях часто несут от­ветственность задержки сообщений в очереди. Так как сервер может обрабатывать параллельно лишь небольшое количество заданий (ограниченное, например, количе­ством ядер процессора), даже небольшого количества медленных запросов достаточ­но для задержки последующих запросов — явление, иногда называемое блокировкой головы очереди. Даже если последующие запросы обрабатываются сервером быстро, клиентское приложение все равно будет наблюдать низкое общее время отклика из-за времени ожидания завершения предыдущего запроса. Принимая во внимание это явление, важно измерять время отклика на стороне клиента.

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

 

Процентили на практике

Верхние процентили приобретают особое значение в прикладных сервисах, вызы­ваемых неоднократно при обслуживании одного запроса конечного пользователя. Даже при выполнении вызовов параллельно запросу конечного пользователя все равно приходится ожидать завершения самого медленного из параллельных вы­зовов. Достаточно одного медленного вызова, чтобы замедлить весь запрос конеч­ного пользователя, как показано на рис. 3. Даже если лишь небольшой процент прикладных вызовов медленные, шансы попасть на медленный вызов возрастают, если запрос конечного пользователя нуждается в том, чтобы их было много, так что больший процент запросов конечных пользователей оказывается медленными (это явление известно под названием «усиление “хвостового” времени ожидания»). Если нужно добавить в мониторинговые инструментальные панели ваших сервисов процентили времени отклика, необходимо эффективно организовать их регулярное вычисление. Например, можно использовать скользящее окно времени отклика за­просов за последние 10 минут с вычислением каждую минуту медианы и различных процентилей по значениям из данного окна с построением графика этих метрик.

В качестве «наивной» реализации можно предложить список времен отклика для всех запросов во временном окне с сортировкой этого списка ежеминутно. Если такая операция представляется вам недостаточно эффективной, существуют алго­ритмы приближенного вычисления процентилей при минимальных затратах про­цессорного времени и оперативной памяти, например forward decay, t-digest и HdrHistogram. Учтите, что усреднение процентилей с целью понижения вре­менного разрешения или объединения данных с нескольких машин математически бессмысленно — правильнее будет агрегировать данные о времени отклика путем сложения гистограмм.

один-единственный медленный прикладной запрос может затормозить работу всего

Рис. 3. Если для обслуживания запроса необходимо несколько обращений к прикладным сервисам, то один единственный медленный
прикладной запрос может затормозить работу всего запроса конечного пользователя

 

Как справиться с нагрузкой

Обсудив параметры для описания нагрузки и метрики для оценки производи­тельности, можно всерьез перейти к изучению вопроса масштабируемости: как сохранить хорошую производительность даже в случае определенного увеличения параметров нагрузки?

Подходящая для определенного уровня нагрузки архитектура, вероятно, не сможет справиться с десятикратным ее увеличением. Если вы имеете дело с быстрорасту­щим сервисом, то, вероятно, вам придется пересматривать архитектуру при каждом возрастании нагрузки на порядок или даже еще чаще.

Зачастую проводят дифференциацию между вертикальным масштабированием — переходом на более мощную машину — и горизонтальным масштабировани­ем — распределением нагрузки по нескольким меньшим машинам. Распределение нагрузки по нескольким машинам известно также под названием архитектуры, не предусматривающей разделения ресурсов. Системы, которые способны работать на отдельной машине, обычно проще, а высококлассные машины могут оказаться весьма недешевыми, так что при высокой рабочей нагрузке часто нельзя избежать горизонтального масштабирования. На практике хорошая архитектура обычно представляет собой прагматичную смесь этих подходов: например, может оказаться проще и дешевле использовать несколько весьма мощных компьютеров, чем много маленьких виртуальных машин.

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

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

По мере усовершенствования инструментов и абстракций для распределенных систем это мнение подвергается изменениям, по крайней мере для отдельных видов приложений. Вполне возможно, что распределенные информационные системы в будущем станут «золотым стандартом» даже для сценариев применения, в кото­рых не идет речь об обработке больших объемов данных или трафика.

Архитектура крупномасштабных систем обычно очень сильно зависит от при­ложения — не существует такой вещи, как одна масштабируемая архитектура на все случаи жизни (на сленге именуемая волшебным масштабирующим соусом). Проблема может заключаться в количестве чтений, количестве записей, объеме хранимых данных, их сложности, требованиях к времени отклика, паттернах до­ступа или (зачастую) какой-либо смеси всего перечисленного, а также во многом другом.

Например, система, рассчитанная на обработку 100 000 з/с по 1 Кбайт каждый, вы­глядит совсем не так, как система, рассчитанная на обработку 3 з/мин. по 2 Гбайт каждый — хотя пропускная способность обеих систем в смысле объема данных одинакова.

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

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

 

Вас заинтересует / Intresting for you:

Подходы к разработке современн...
Подходы к разработке современн... 687 просмотров Doctor Sun, 20 Jan 2019, 11:07:45
Разработка информационных сист...
Разработка информационных сист... 910 просмотров Александров Попков Sun, 20 Jan 2019, 10:58:09
Разработка архитектуры информа...
Разработка архитектуры информа... 1663 просмотров Administrator SU Thu, 19 Jul 2018, 15:36:26
Средства моделирования в компь...
Средства моделирования в компь... 563 просмотров Александров Попков Sun, 17 Mar 2019, 15:50:15
Войдите чтобы комментировать

Fortan аватар
Fortan ответил в теме #9357 22 фев 2019 07:59
"Прочность" информационной системы, конечно, нужно закладывать на этапе проектирования / разработки. В этом плане фактор масштабирования нужно учитывать в одну из первых очередей. Планировать, так сказать, приложение "на вырост".