Проблемы масштабирования динамических систем

Размышления об оптимизации компилятора

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

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

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

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

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

Перед тем, как мы попытались реализовать описанную выше концепцию, возникло несколько вопросов, а именно:

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

Это некоторые моменты, которыми мы займемся в этой статье.

Что происходит при загрузке страницы?

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

Имея это в виду, вот диаграмма того, что происходит, когда пользователь открывает страницу:

  1. Конкретная перестановка метаданных создается на основе пользовательской конфигурации.
  2. Механизм, управляемый метаданными (например, DSL-Engine *), преобразует метаданные в более подходящий формат.
  3. Теперь действие на уровне приложения может быть выполнено на основе окончательного формата метаданных (т. Е. Рендеринг компонента React.js или выполнение запроса SQL).
  4. Пользователь авторизуется в зависимости от уровня доступа.
  5. Браузер отображает страницу.

* DSL-Engine - это система, интерпретирующая метаданные и выполняющая действия на уровне приложения на основе их содержимого, например отображение веб-страницы или выполнение запроса SQL.

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

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

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

Каждый раз, когда конфигурация клиента изменяется, мы можем:

  1. Создайте все необходимые метаданные для этой конфигурации.
  2. Преобразуйте метаданные в формат, более подходящий для DSL Engine.
  3. Преобразуйте полученные метаданные в код, готовый к запуску.

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

  1. Получить предварительно скомпилированный код.
  2. Обработка приложения в соответствии с конфигурацией на уровне пользователя.
  3. Авторизуйте пользователя.
  4. Визуализируйте страницу.

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

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

Проблемы реализации

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

  1. Сколько данных нашей системе необходимо предварительно сгенерировать для каждого клиента?
  2. Как эти данные со временем растут?
  3. Можно ли оптимизировать хранение данных?

Данные, генерируемые для каждого клиента, зависят от количества приобретенных приложений. Приложение - это небольшая единица метаданных, которая представляет функциональные возможности HCM Application Suite. У каждого клиента своя конфигурация этого приложения в зависимости от приобретенных функций.

Мы проанализировали данные десяти клиентов, чтобы понять их различия, и вот что мы обнаружили:

У клиентов имеется значительный объем общих функций.

Существует особый тип метаданных, который отвечает за различные перестановки, которые может иметь клиент, в зависимости от его локализации и конфигурации приложения. Мы называем этот тип метаданных «переопределением». На приведенном ниже графике показано, какая часть метаданных, используемых этими клиентами, состоит из переопределений:

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

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

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

Хранилище с адресом местоположения, хранилище с адресацией по содержанию и деревья Меркла

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

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

Вот разница в росте данных при использовании оптимизированного подхода:

Рост данных пошел от линейного O (n) до почти логарифмического O (log (n)). Скорость также была значительно оптимизирована. Теперь нам нужно только вычислить хеш-значение вместо полной повторяющейся повторной компиляции.

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

Заключение

Подход платформы разработки Lifion на основе метаданных дает ADP гибкость для быстрой разработки глобальных приложений HCM для своих клиентов. Такая гибкость достигается за счет высоких требований к обработке для создания настраиваемых версий одного и того же пакета приложений HCM для каждого нового клиента. Если наши системы всегда должны создавать приложение во время выполнения, будет сложно масштабировать платформу для поддержки миллионов пользователей.

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

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

Признанные члены команды

  1. Срджан Стрбанович (старший архитектор платформы)
  2. Игорь Курочкин (технический менеджер)
  3. Джуд Мерфи (архитектор решений)
  4. Али Юсуф (инженер-программист)
  5. Alex Vũ Nguyễn (инженер-программист)