Назад к списку

Распределенные вычисления. Введение

Постановка задачи

Есть некоторое оборудование, которое каждый час снимает значения показателей (x_i). Оборудование объединяется в группы, образуя дерево объектов. 

Есть расчетные показатели (формулы), которые являются результатом вычисления функции f(x_i). Необходимо рассчитать и отобразить для пользователя рассчитанные значения за выбранный период. Здесь и далее будут употребляться обезличенные объекты и показатели. Допустим, вы владелец масштабной компании, предоставляющей услуги ЖКХ, у вас есть приборы учета, которые ежечасно присылают значение потребленного ресурса (воды, электричества, тепла и т.п.). Эти приборы учета объединяются в квартиру, квартиры в подъезд, подъезды в дом, ЖК, территориальные, региональные и всероссийские отделения. Будем считать это деревом объектов. Значения, которые присылают эти приборы учета, – фактическими данными. Вы хотите знать какие-то величины, которые являются вычисляемыми от фактических. Например, стоимость потребленных ресурсов как произведение потребленного объёма (фактические данные) умноженного на тариф (функция от времени). Довольно просто.
Теперь о размере задачи. В нашем дереве больше 12000 объектов, заказчик говорит, что хочет видеть почасовку в пределах года, а количество расчетных показателей больше 100. Итого 24*365*12000*100 = 10 512 000 000 расчетных показателей. Такое желание может возникнуть у каждого ответственного пользователя в регионе, то есть подобный запрос может прийти от нескольких десятков пользователей. И тут нельзя оперировать тем, что человек не может одномоментно оценить 10 миллиардов значений, т.к. смотреть начинают, как правило, с рутовых узлов, а в рутовых узлах объекты и показатели агрегируются.
Существует приложение на основе клиент-серверной архитектуры, которое начало захлёбываться от количества вычислений ещё до тотального роста количества пользователей.


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

Чего хотим добиться?

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

Направления изменений

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


В каждом из звеньев поставим свои proxy-модули, у которых будет довольно широкий спектр задач:
  • FE PROXY. Будет выполнять роль шлюза (получать и отправлять данные), управлять клиентской памятью, следить за подключением к клиентской базе данных, управлять WASM-модулями расчета, управлять web worker-ами.
  • BE PROXY. Будет управлять кэшами (данных и расчетных показателей), разбивать макрозадачу на микрозадачи, управлять очередью, в которую будут помещаться микрозадачи, распределять микрозадачи на расчетные модули. Впоследствии вытеснит или поглотит изначальную серверную часть.
Описание общего подхода будет разделено на следующие статьи:
  • Подготовка бэкенда. Чистая архитектура. Обсудим начало работ с бэ и принципы чистой архитектуры. Технологии: .net core / C#.
  • Перенос квазипостоянных данных на сторону клиента. Статья о бд оперативного доступа на клиентской стороне. Попытка снизить количество запросов на бэ/серверную бд. Технологии: IndexedDB / TypeScript.
  • Перенос расчета на сторону клиента. Статья о расчетном модуле на клиентской стороне. Разделение микрозадач между серверной и клиентской частями. Технологии: WebAssembly / Rust + JavaScript.
  • Перенос расчета на серверный GPU. Задача выглядит как огромное количество одинаковых расчетов (в первую очередь на листьях дерева), что подталкивает попробовать их выполнять на большом количестве нитей графического процессора. Технологии: CUDA / C.
  • Создание и распределение микрозадач. Продолжение первой статьи. Будет рассмотрен процесс создания микрозадач, добавление микрозадач в очередь, организация кэшей (данных и вычисляемых показателей), распределение микрозадач между расчетными модулями. Технологии: .net core / C#.
  • Бенчмарки, цифры, выводы. Основная статья. Попытка оценить производительность, затраченные ресурсы и сделать выводы о целесообразности такого подхода.
В рамках нашего гайда будем перечислять недостатки и сложности, с которыми придётся столкнуться. Первые видны уже сразу:
  • Количество поддерживаемых языков и технологий. Придётся писать на C#, C, Rust, TypeScript (JavaScript скорее всего будет только автосгенерированным js-клеем для работы с WebAssembly).
  • Синхронизация расчетов в разных модулях. Допустим, с течением времени какая-то формула изменилась, а у нас как в анекдоте два тоннеля (даже не два, а три). Изменения в них будут не одномоментными и надо следить за тем, чтобы не было разного результата в зависимости от того, на какой расчетный модуль распределилась микрозадача.
  • Динамическая компиляция формул будет только на изначальном расчетном модуле в серверной части.
Список будет пополняться по мере выхода статей. Stay tuned!
Сергей Коновалов, главный разработчик ПО

Поиск по сайту