Иэн Гортон
Основы масштабируемых систем

Foundations of
scalable systems
глава 2
Архитектура распределённых систем: введение
В этой главе я в общих чертах расскажу о некоторых фундаментальных подходах к масштабированию программных систем. Рассматривайте это как обзор с высоты 30 тысяч футов того, что будет обсуждаться в частях II, III и IV данной книги. Я проведу экскурс по основным архитектурным подходам, используемым для масштабирования системы, и дам ссылки на последующие главы, где эти вопросы будут рассмотрены более подробно. Вы можете воспринимать это как обзор того, почему нам нужны эти архитектурные тактики, а в остальной части книги будет рассказано, как это сделать.

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

Я начну с системы с простой конструкцией и покажу, как её можно масштабировать. При этом я введу несколько концепций, которые будут рассмотрены более подробно далее в этой книге. В этой главе я просто даю широкий обзор этих концепций и того, как они помогают в масштабировании — это действительно головокружительный тур!
Базовая архитектура системы
Практически все масштабные системы начинаются с малого и растут благодаря своей успешности. При этом популярно и разумно начинать с фреймворка разработки, такого как Ruby on Rails, Django или аналогичного, который способствует быстрой разработке и позволяет быстро запустить систему в работу. Типичная очень простая архитектура программного обеспечения для «стартовых» систем, которая очень похожа на ту, что получается при использовании фреймворков быстрой разработки, показана на рисунке 2-1. Она включает в себя клиентский уровень, уровень прикладных сервисов и уровень баз данных. Если вы используете Rails или его аналог, вы также получаете фреймворк, в котором реализован паттерн «модель-представление-контроллер» (MVC) для работы веб-приложений, а также объектно-реляционный маппер (ORM) для генерации SQL-запросов.
Рисунок 2-1. Базовая архитектура многоуровневых распределённых систем
При такой архитектуре пользователи отправляют запросы к приложению через мобильное приложение или веб-браузер. Магия интернет-сети (см. главу 3) доставляет эти запросы к сервису приложения, который работает на машине, размещённой в корпоративном или коммерческом облачном центре обработки данных. Для связи используется стандартный сетевой протокол прикладного уровня, как правило, HTTP.

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

Многие, если не большинство, из систем концептуально выглядят именно так. Код прикладного сервиса использует среду исполнения сервера, позволяющую одновременно обрабатывать несколько запросов от нескольких пользователей. Существует огромное количество таких технологий сервера приложений — например, Java EE и Spring Framework для Java, Flask для Python — которые широко используются в данном сценарии.

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

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

В этом случае первой стратегией масштабирования обычно является «наращивание» аппаратного обеспечения службы приложений. Например, если приложение работает на AWS, можно модернизировать сервер со скромного экземпляра t3.xlarge с четырьмя (виртуальными) процессорами и 16 Гбайт памяти до экземпляра t3.2xlarge, что удвоит количество процессоров и памяти, доступных для приложения.

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

Однако неизбежно, что для многих приложений нагрузка вырастет до уровня, который приведет к загрузке одного серверного узла, независимо от количества процессоров и объёма памяти. Тогда возникает необходимость в новой стратегии — горизонтальном масштабировании (scaling out), о котором я говорил в главе 1.
Горизонтальное масштабирование
Горизонтальное масштабирование опирается на возможность репликации сервиса в архитектуре и запуска нескольких копий на нескольких серверных узлах. Запросы от клиентов распределяются между копиями, так что теоретически, если у нас есть N копий и R запросов, то каждый серверный узел обрабатывает R/N запросов. Эта простая стратегия позволяет увеличить пропускную способность приложения и, следовательно, масштабируемость.

Для успешного горизонтального масштабирования приложения необходимо наличие двух основополагающих элементов в конструкции. Как показано на рисунке 2-2, это:

Балансировщик нагрузки
Все запросы пользователей направляются на балансировщик нагрузки, который выбирает целевую реплику сервиса для обработки запроса. Существуют различные стратегии выбора целевого сервиса, основная цель которых состоит в том, чтобы каждый ресурс был одинаково загружен. Балансировщик нагрузки также передает ответы от сервиса обратно клиенту. Большинство балансировщиков нагрузки относятся к классу интернет-компонентов, известных как обратные прокси-серверы (reverse proxies). Они контролируют доступ к ресурсам сервера для клиентских запросов. Являясь посредниками, обратные прокси добавляют дополнительный сетевой переход для запроса; чтобы минимизировать накладные расходы, они должны иметь очень низкую задержку. Существует множество готовых решений по балансировке нагрузки, а также решения, разработанные специально для облачных провайдеров, и их общие характеристики я более подробно рассмотрю в главе 5.

Службы без хранения состояния (stateless)
Для того чтобы балансировка нагрузки была эффективной и равномерно распределяла запросы, балансировщик нагрузки должен свободно отправлять последовательные запросы от одного и того же клиента на обработку разным экземплярам сервиса. Это означает, что реализация API в сервисах не должна хранить никаких знаний или состояний, связанных с отдельной сессией клиента. Когда пользователь обращается к приложению, сервис создает пользовательскую сессию, и уникальная сессия управляется внутри сервиса для идентификации последовательности взаимодействий пользователя и отслеживания состояния сессии. Классическим примером состояния сеанса является корзина покупок. Для эффективного использования балансировщика нагрузки данные, представляющие текущее содержимое корзины пользователя, должны храниться где-то — обычно в хранилище данных — таким образом, чтобы любая реплика сервиса могла получить доступ к этому состоянию при получении запроса как части пользовательского сеанса. На рисунке 2-2 это хранилище обозначено как «Session store».
Горизонтальное масштабирование привлекательно тем, что, теоретически, можно постоянно добавлять новое (виртуальное) оборудование и сервисы для обработки растущей нагрузки на запросы и поддержания постоянной и низкой задержки для запросов. Как только вы увидите, что задержки растут, вы развёртываете еще один экземпляр сервера. Это не требует изменения кода при использовании stateless-сервисов и, как следствие, относительно дешево — вы просто платите за развёрнутое оборудование.
Горизонтальное масштабирование имеет еще одну весьма привлекательную особенность. Если один из сервисов выйдет из строя, то обрабатываемые им запросы будут потеряны. Но поскольку отказавший сервис не управляет состоянием сеанса, эти запросы могут быть просто переотправлены клиентом и переданы на обработку другому экземпляру сервиса. Это означает, что приложение устойчиво к сбоям в программном и аппаратном обеспечении сервиса, что повышает его доступность.

К сожалению, как и любое инженерное решение, простое горизонтальное масштабирование имеет свои пределы. По мере добавления новых экземпляров сервисов мощность обработки запросов растет, прич ём потенциально бесконечно. Однако на определённом этапе реальность дает о себе знать, и возможности единой базы данных по обеспечению низкой задержки при обработке запросов снижаются. Медленные запросы будут означать увеличение времени отклика для клиентов. Если запросы будут поступать быстрее, чем они обрабатываются, некоторые компоненты системы будут перегружены и выйдут из строя из-за исчерпания ресурсов, а у клиентов появятся ошибки и таймауты запросов. По существу, ваша база данных становится узким местом, которое необходимо устранить для дальнейшего масштабирования приложения.
Рисунок 2-2. Масштабируемая архитектура
Масштабирование базы данных c помощью кэширования
Вертикальное масштабирование путём увеличения количества процессоров, памяти и дисков в сервере баз данных может значительно расширить возможности системы. Например, на момент написания статьи GCP может предоставить базу данных SQL на узле db-n1-highmem-96, который имеет 96 виртуальных процессоров (vCPU), 624 Гбайт памяти, 30 Тбайт дисков и поддерживает 4000 соединений. Это будет стоить от 6 до 16 тысяч долларов в год, что, на мой взгляд, очень выгодно! Вертикальное масштабирование — одна из распространённых стратегий масштабирования баз данных.

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

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

Для данных, которые часто читаются и редко изменяются, логика обработки может быть изменена таким образом, чтобы сначала проверялся распределённый кэш, например, Redis или memcached. Эти технологии кэширования, по сути, являются распределёнными хранилищами ключ-значение с очень простыми API. Эта схема показана на рисунке 2-3. Обратите внимание, что хранилище сеансов с рисунка 2-2 исчезло. Это объясняется тем, что для хранения идентификаторов сеансов вместе с данными приложения можно использовать распределённый кэш общего назначения.
Рисунок 2-3. Внедрение распределённого кэширования
Для доступа к кэшу требуется удалённый вызов вашего сервиса. Если нужные данные находятся в кэше, то в быстрой сети можно ожидать субмиллисекундного чтения кэша. Это гораздо менее затратно, чем обращение к экземпляру общей базы данных, а также не требует от запроса борьбы за обычно дефицитные соединения с базой данных.
Внедрение слоя кэширования также требует изменения логики обработки для проверки наличия кэшированных данных. Если нужные данные отсутствуют в кэше, то код всё равно должен запросить базу данных и загрузить результаты в кэш, а также вернуть их вызывающей стороне. Также необходимо решить, когда удалять или аннулировать кэшированные результаты — порядок действий зависит от характера данных (например, срок действия прогнозов погоды истекает естественным образом) и толерантности приложения к предоставлению клиентам неактуальных — или устаревших (stale) — результатов.

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

Тем не менее, многие системы нуждаются в постоянном доступе к терабайтным и более хранилищам данных, что делает эффективным использование одной базы данных. В таких системах необходима распределённая база данных.
Распределение базы данных
В 2022 году существует больше технологий распределённых баз данных, чем вы, вероятно, готовы себе представить. Это сложная область, которую я подробно рассмотрю в части III. В самом общем виде можно выделить две основные категории:

Распределённые хранилища SQL
Они позволяют организациям относительно легко масштабировать базу данных SQL за счет хранения данных на нескольких дисках, к которым обращаются несколько реплик движков базы данных. Эти несколько механизмов логически представляются приложению как единая база данных, что сводит к минимуму изменения в коде. Существует также класс «изначально распределённых» баз данных SQL, которые принято называть хранилищами NewSQL и которые относятся к этой категории.
Распределённые, так называемые «NoSQL» хранилища (от целого ряда производителей)
Эти продукты используют различные модели данных и языки запросов для распределения данных между несколькими узлами, на которых работает механизм базы данных, каждый из которых имеет собственное локально подключённое хранилище. При этом местоположение данных прозрачно для приложения и обычно контролируется структурой модели данных с помощью хэш-функций для ключей базы данных. Ведущими продуктами в этой категории являются Cassandra, MongoDB и Neo4j.

На рисунке 2-4 показано, как в нашей архитектуре реализована распределённая база данных. По мере роста объёмов данных распределённая база данных может увеличивать количество узлов хранения. При добавлении (или удалении) узлов происходит перебалансировка данных, управляемых всеми узлами, с целью обеспечения равномерного использования вычислительных мощностей и возможностей хранения для каждого узла.
Рисунок 2-4. Масштабирование уровня данных с использованием распределённой базы данных
Распределённые базы данных также способствуют повышению доступности. Они поддерживают репликацию на каждом узле хранения данных, поэтому в случае выхода из строя одного узла или невозможности доступа к нему из-за проблем в сети можно получить другую копию данных. Модели, используемые для репликации, и компромиссы, которые они требуют (спойлер: согласованность), рассматриваются в последующих главах.

Если вы пользуетесь услугами крупного облачного провайдера, у вас также есть два варианта развёртывания слоя данных. Можно развернуть собственные виртуальные ресурсы, создать, настроить и администрировать собственные распределённые серверы баз данных. В качестве альтернативы можно использовать базы данных, размещённые в «облаке». В последнем случае упрощается процесс администрирования, связанный с управлением, мониторингом и масштабированием базы данных, поскольку многие из этих задач, по сути, возлагаются на выбранного облачного провайдера. Как обычно, здесь действует принцип «бесплатного обеда». За всё приходится платить, какой бы подход вы ни выбрали.
Несколько уровней обработки
Любая реалистичная система, которую необходимо масштабировать, будет содержать множество различных сервисов, взаимодействующих при обработке запроса. Например, для получения доступа к веб-странице на сайте Amazon.com может потребоваться вызов более 100 различных служб, прежде чем ответ будет возвращён пользователю.

Прелесть архитектуры без сохранения состояния (stateless), с балансировщиком нагрузки (load-balanced), с кешированием (cached), о которой я рассказываю в этой главе, заключается в том, что можно расширить основные принципы проектирования и построить многоуровневое приложение. Выполняя запрос, сервис может вызывать один или несколько зависимых сервисов, которые, в свою очередь, реплицируются и балансируются по нагрузке. Простой пример показан на рисунке 2-5. Существует множество нюансов взаимодействия сервисов и того, как приложения обеспечивают быстрый отклик зависимых сервисов. Подробнее об этом будет рассказано в последующих главах.
Рисунок 2-5. Масштабирование вычислительной мощности с помощью нескольких уровней
Такая конструкция также способствует наличию различных сбалансированных по нагрузке сервисов на каждом уровне архитектуры. Например, на рисунке 2-6 показаны два реплицированных интернет-сервиса, которые используют основной сервис, обеспечивающий доступ к базе данных. Каждый сервис сбалансирован по нагрузке и использует кэширование для обеспечения высокой производительности и доступности. Такая конструкция часто используется для предоставления сервиса для веб-клиентов и сервиса для мобильных устройств клиентов, каждый из которых может масштабироваться независимо в зависимости от нагрузки. Это так называемый паттерн Backend for Frontend (BFF).
Рисунок 2-6. Масштабируемая архитектура с несколькими сервисами
Кроме того, разбив приложение на несколько независимых сервисов, можно масштабировать каждый из них в зависимости от спроса на услуги. Например, если количество запросов от мобильных пользователей растёт, а от веб-пользователей снижается, то для удовлетворения спроса можно выделить разное количество экземпляров для каждого сервиса. В этом заключается основное преимущество рефакторинга монолитных приложений в несколько независимых сервисов, которые можно отдельно создавать, тестировать, развёртывать и масштабировать. В главе 9 я рассмотрю некоторые из основных проблем проектирования систем на основе таких сервисов, называемых микросервисами.
Повышение оперативности
Большинство запросов клиентских приложений ожидают ответа. Пользователь может захотеть просмотреть все аукционные товары для определённой категории товаров или увидеть недвижимость, выставленную на продажу в определённом месте. В этих примерах клиент отправляет запрос и ожидает получения ответа. Этот промежуток времени между отправкой запроса и получением результата и есть время отклика запроса. Время отклика можно уменьшить за счёт использования кэширования и заранее рассчитанных ответов, однако многие запросы все равно будут приводить к обращению к базе данных.
Аналогичный сценарий существует и для запросов, обновляющих данные в приложении. Если пользователь обновляет свой адрес доставки непосредственно перед оформлением заказа, то новый адрес доставки должен быть сохранён, чтобы пользователь мог подтвердить его перед нажатием кнопки «купить». Время ответа в этом случае включает время записи в базу данных, что подтверждается полученным пользователем ответом.

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

Когда человек ожидает посадки на подъёмник, сканирующее устройство проверяет подлинность пропуска с помощью считывателя RFID (радиочастотной идентификации) чипов. Затем информация о пассажире, подъёмнике и времени отправляется через Интернет в службу сбора данных, работающую на горнолыжном курорте. Катающемуся на подъёмнике не нужно ждать, пока это произойдет, так как время отклика может замедлить процесс загрузки подъёмника. Кроме того, пассажир подъёмника не ожидает, что он сможет мгновенно воспользоваться своим приложением, чтобы убедиться в том, что эти данные были получены. Они просто заходят в подъемник, общаются с друзьями и планируют следующий заезд.

Реализации сервисов могут использовать такой сценарий для повышения скорости реагирования. Данные о событии отправляются в сервис, который подтверждает их получение и одновременно сохраняет их в удалённой очереди для последующей записи в базу данных. Для надёжной передачи данных от одного сервиса к другому могут использоваться платформы распределённых очередей, которые обычно, но не всегда, работают по принципу «первый пришел — первый ушел» (FIFO - first in first out).

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

Базовая архитектура для реализации этого подхода показана на рисунке 2-7.
Рисунок 2-7. Повышение скорости отклика с помощью очередей
Когда результаты операции записи не требуются немедленно, приложение может использовать этот подход для повышения скорости отклика и, как следствие, масштабируемости. Существует множество технологий постановки в очередь, которые могут использоваться приложениями, и я рассмотрю их работу в главе 7. Все эти платформы очередей обеспечивают асинхронное взаимодействие. Сервис-производитель (producer) записывает сообщения в очередь, которая выступает в качестве временного хранилища, а сервис-потребитель (consumer) удаляет сообщения из очереди и вносит необходимые обновления в базу данных, в нашем примере хранящую данные о поездках лыжников на подъёмнике.

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

Однако есть случаи, когда увеличение количества процессорных ядер и доступной памяти не приведёт к повышению масштабируемости. Например, если код однопоточный, то его выполнение на узле с большим количеством ядер не приведёт к повышению производительности. В любой момент времени будет задействовано только одно ядро. Остальные просто не будут задействованы. Если многопоточный код содержит много сериализованных секций, то только однопоточное ядро может одновременно выполнять работу, чтобы гарантировать правильность результатов. Это явление описывается законом Амдала. Это даёт нам возможность рассчитать теоретическое ускорение кода при добавлении дополнительных ядер процессора, исходя из количества кода, выполняемого последовательно.

Две точки данных из закона Амдала таковы:

  • Если только 5% кода выполняется последовательно, а остальное — параллельно, то добавление более 2 048 ядер практически не даёт эффекта.
  • Если 50% кода выполняется последовательно, а остальное — параллельно, то добавление более 8 ядер практически не даёт эффекта.

Это показывает, почему эффективный многопоточный код является важнейшим условием достижения масштабируемости. Если ваш код не работает как высоконезависимые задачи, реализованные в виде потоков, то даже деньги не помогут вам добиться масштабируемости. Именно поэтому я посвятил главу 4 теме многопоточности — это основной компонент знаний для построения масштабируемых распределённых систем.

На рисунке 2-8 показано, как повышается пропускная способность эталонной системы по мере развёртывания базы данных на более мощном (и дорогом) оборудовании. В эталонной системе используется Java-сервис, который принимает запросы от клиента, генерирующего нагрузку, запрашивает базу данных и возвращает результаты клиенту. Клиент, сервис и база данных работают на разных аппаратных ресурсах, размещённых в одних и тех же регионах облака AWS.
Рисунок 2-8. Пример масштабирования сервера баз данных
В тестах количество одновременных запросов увеличивается от 32 до 256 (ось x), а каждая линия представляет собой пропускную способность системы (ось y) для различных аппаратных конфигураций службы реляционных баз данных (RDS) AWS EC2. Различные конфигурации перечислены в нижней части графика: слева — наименее мощные, справа — наиболее мощные. Каждый клиент отправляет фиксированное количество запросов синхронно по протоколу HTTP, без паузы между получением результатов одного запроса и отправкой следующего. Это приводит к высокой нагрузке на сервер.

Из этого графика можно сделать несколько простых наблюдений:
  • В общем случае, чем мощнее аппаратное обеспечение, выбранное для базы данных, тем выше пропускная способность. Это хорошо.
  • Разница между экземплярами db.t2.xlarge и db.t2.2xlarge по пропускной способности минимальна. Это может быть связано с тем, что сервисный уровень становится узким местом, либо наша модель базы данных и запросы не используют дополнительные ресурсы экземпляра db.t2.2xlarge RDS. В общем, больше денег - меньше пользы.
  • Два наименее мощных экземпляра работают достаточно хорошо до тех пор, пока нагрузка на запрос не увеличится до 256 одновременных клиентов. Падение пропускной способности этих двух экземпляров свидетельствует о том, что они перегружены, и при увеличении нагрузки на них ситуация будет только ухудшаться.

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

Еще одна область, которую мы обошли в этой главе, — это архитектура программного обеспечения. Я использовал термин «сервисы» для обозначения распределённых компонентов архитектуры, реализующих бизнес-логику приложения и доступ к базе данных. Эти сервисы представляют собой независимо развёртываемые процессы, взаимодействующие между собой с помощью механизмов удалённого обмена данными, таких как HTTP. С архитектурной точки зрения эти сервисы наиболее близки к сервисам сервис-ориентированной архитектуры (SOA — service-oriented architecture) — устоявшегося архитектурного подхода к построению распределённых систем. Более современное развитие этого подхода связано с микросервисами. Это, как правило, более целостные, инкапсулированные сервисы, способствующие непрерывной разработке и развёртыванию.

Если вы хотите получить более глубокое обсуждение этих вопросов и концепций архитектуры программного обеспечения в целом, то книга Марка Ричардса и Нила Форда «Основы архитектуры программного обеспечения: An Engineering Approach» (O'Reilly, 2020) - отличное начало.

Наконец, существует класс программных архитектур больших данных, которые решают некоторые проблемы, возникающие при работе с очень большими массивами данных. Одной из наиболее заметных является повторная обработка данных. Это происходит, когда уже сохранённые и проанализированные данные необходимо проанализировать заново в связи с изменением кода или бизнес-правил. Такая обработка может быть вызвана исправлениями в программном обеспечении или внедрением новых алгоритмов, позволяющих получить больше информации из исходных данных. В 2014 году в блоге O'Reilly Radar Джей Креппс в статье «Подвергая сомнению архитектуру Lambda» хорошо обсудил архитектуры Lambda и Kappa, которые занимают видное место в этой области.