Эта статья является первой частью серии из четырех о внутреннем устройстве виртуальной машины Envision: программного обеспечения, которое выполняет скрипты Envision. См. часть 2, часть 3 и часть 4. В этой серии не рассматривается компилятор Envision (возможно, в другой раз), поэтому давайте просто предположим, что скрипт каким-то образом был преобразован в байт-код, который принимает виртуальная машина Envision в качестве входных данных.

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

Beyond time-series

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

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

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

  • Управление памятью должно быть автоматическим. Это, конечно, включает в себя сборщик мусора, но также означает, что Envision должен прозрачно поддерживать обработку больших объемов данных. Создание массива размером в десять гигабайт - это минимальные требования: это даже не три миллиарда чисел! Ожидается возможность работать с набором данных, превышающим объем памяти одной машины. Фактически, Envision поддерживает наборы данных, которые превышают объем памяти всего кластера, благодаря умному использованию NVMe-накопителей.
  • Многопоточность и параллелизм на нескольких машинах должны быть автоматическими. Операции, которые можно выполнять параллельно, должны распределяться на все доступные ядра кластера без участия человека. Сценарий должен продолжать работу даже после сбоя одной машины в кластере, не требуя перезапуска. Сценарий должен успешно завершаться даже если кластер сократится до одной машины. И, конечно же, два запуска Envision должны выполняться одновременно на одном и том же кластере.
  • Мы ожидаем, что для работы Envision потребуется немного аппаратных ресурсов. Многие проблемы производительности могут быть решены путем потраты миллионов долларов на высококлассное оборудование и/или серверные лицензии, но мы предпочли бы избежать ситуации, когда нажатие кнопки “Запустить” сценария стоит сотни долларов.

Общеизвестные языки программирования не предоставляют эти возможности, и хотя их обычно можно комбинировать с фреймворками, которые это делают (Scala + Spark, Python + Dask и другие), это оставляет слишком много острых углов, доступных пользователю. В этом смысле Envision больше похож на SQL, работающий на BigQuery.

Окружение

Envision не может быть установлен или запущен локально. Вместо этого все пользователи подключаются к онлайн-платформе Lokad, которая предоставляет веб-интерфейс на основе браузера для редактирования и выполнения сценариев. Данные и панели управления также хранятся онлайн и доступны через веб-интерфейс, а также через SFTP и FTPS.

Когда пользователь запускает сценарий через веб-интерфейс, создается миссия, которая отправляется на выполнение в кластер Envision.

Envision работает в пакетном режиме: каждое выполнение читает все входные данные и производит полный вывод. Это может занять от 5 секунд для очень простого сценария, работающего с небольшим объемом данных, до 30-40 минут для большого сценария по увеличению данных и даже нескольких часов для некоторых задач машинного обучения.

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

Структура кластера

Начиная с 2021 года, весь Envision работает на .NET 5, размещенном на виртуальных машинах Ubuntu 20.04 в облаке Microsoft Azure. Кластер состоит из 2-6 экземпляров Standard_L32s_v2: 32 ядра AMD EPYC 7551, 256 ГиБ памяти и 4 NVMe-накопителя общим объемом 7,68 ТБ. Мы называем эти машины рабочими.

В кластере существует ассоциация M-to-N между рабочими и миссиями: одна машина может одновременно выполнять несколько сценариев Envision, и если одному сценарию назначено несколько машин, они будут сотрудничать для его более быстрого завершения.

У каждого кластера также есть планировщик, более маленький экземпляр Standard_B2ms с 2 ядрами и 8 ГиБ памяти. Планировщик предоставляет API-точки доступа для внешних приложений для отправки новых миссий и сбора результатов завершенных миссий. Он также отвечает за отправку миссий на одну или несколько машин в кластере. В зависимости от нагрузки и степени параллелизма, доступного каждому сценарию в любой момент времени, планировщик может добавлять или удалять рабочие машины из миссии.

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

Хранилище Blob

Lokad не использует SQL-базы данных для данных клиентов. Размещенные решения не могут легко содержать наборы данных наших крупных клиентов (они обычно достигают от 4 ТБ до 16 ТБ), а запуск собственных серверов потребовал бы усилий, которые мы предпочтем потратить в другом месте.

С другой стороны, Envision работает в пакетном режиме, что устраняет необходимость в запросах, более сложных, чем “Прочитать столбец X между строками L и M”: после загрузки входных данных рабочая машина сможет индексировать и повторно обрабатывать их при необходимости.

Именно поэтому мы используем Azure Blob Storage в качестве основного хранилища. Он позволяет нам хранить более петабайта по стоимости, не превышающей 1% от стоимости размещенных SQL-баз данных, и производительность чтения запросов надежно остается в диапазоне от 30 МБ/с до 60 МБ/с.

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

Для точности, Lokad построил хранилище с адресацией содержимого поверх Azure Blob Storage. Оно open source и доступно в виде пары пакетов NuGet Lokad.ContentAddr и Lokad.ContentAddr.Azure. Знание хеша отдельных файлов позволяет Envision определить, что некоторые из его входных данных не изменились, поэтому он может повторно использовать вычисленные значения, хранящиеся в кэше после предыдущего запуска.

Развертывание

Envision не использует контейнеризацию (например, Docker), потому что преимущества контейнеров не оправдывают дополнительную сложность, связанную с ними.

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

Во-вторых, для упаковки приложения вместе со всеми его зависимостями в платформенно-независимом виде мы нашли, что команда dotnet publish достаточна (и, фактически, быстрее, чем docker build). С .NET 5 Microsoft предоставляет выдающуюся кросс-платформенную поддержку, и достаточно просто скопировать результаты сборки с Windows-машин на хост Linux.

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

На следующей неделе мы рассмотрим модель выполнения Envision и то, как работа, которую нужно выполнить, представлена внутри кластера.

Бесстыдная реклама: мы нанимаем инженеров по программному обеспечению. Возможна удаленная работа.


  1. ничего не бывает даром, и мы платим за это, пожертвовав способностью Envision действовать как язык общего назначения. Например, автоматическая параллелизация означает, что мы никогда не будем поддерживать явное управление потоками в Envision; автоматическая обработка на нескольких машинах означает, что в Envision никогда не будет понятия “локальной машины”. ↩︎