Малые данные в Elixir

Первая статья в цикле статей о малых данных в Elixir. Начнём с определения «малые данные», поговорим о том, почему они важны, вкратце рассмотрим инструмент Flow и пробежимся по содержанию следующих статей цикла.

Насколько малы малые данные?

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

Группа учёных из Калифорнийского университета проанализировали различные профили нагрузки MapReduce и пришли к следующему выводу:

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

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

Система MapReduce с использованием библиотеки Metis, запущенная на одной машине, показывает наивысшую производительность при работе с малыми данными (≤ 0,5 ГБ). Это действительно важно, ведь на практике более распространенными являются именно малые объёмы входных данных: 40-80% MapReduce-заданий клиентов Cloudera и 70% заданий Facebook имеют не более 1 ГБ входных данных.

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

И наконец, обратим внимание на публикацию «Масштабируемость, но какой ценой?». «Цена» конкретной платформы для решения конкретной задачи — это необходимость конфигурации платформы для превосхождения производительности однопоточной реализации.

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

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

Что из себя представляют малые данные в том или ином случае зависит от конкретной задачи, объёма данных (или частоты их поступления) и ожидаемого времени их обработки. В данном цикле статей рассматриваются решения различных задач с помощью библиотеки Flow. Flow обеспечивает конкурентность в одномашинных системах, а также подходит для работы с «малыми» задачами, тем самым избавляя разработчиков от необходимости прибегать к готовым программным решениям обработки больших данных.

GenStage и Flow

В прошлом году был представлен GenStage — абстракция, предназначенная для обмена данными между процессами Elixir. GenStage разработан с учётом возможности использования данных из внешних систем (таких как Apache Kafka, RabbitMQ, баз данных, файлов и т.п.) без перегрузки системы, обрабатывающей эти данные.

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

Однако с таким неконтролируемым подходом схемы работы приложений, скорее всего, будут далеки от оптимальных. Именно поэтому был разработан инструмент Flow, построенный на основе GenStage. Flow позволяет разработчикам производить обработку данных с помощью функциональных операций map, reduce, filter и т.п. Flow также предоставляет комфортные средства для секционирования данных и управления окнами. После задания всех необходимых параметров Flow создаёт из подключенных этапов сеть, через которую затем будут проходить данные. Ниже приведён простой пример использования Flow для подсчёта количества слов в файле:

File.stream!("path/to/file")
|> Flow.from_enumerable()
|> Flow.flat_map(&String.split/1)
|> Flow.partition()
|> Flow.reduce(fn -> %{} end, fn word, map ->
     Map.update(map, word, 1, & &1 + 1)
   end)
|> Enum.into(%{})

Приведённый выше пример будет подробнее рассмотрен в следующих статьях.

Что дальше

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

© 2020 / Россия Любые мысли и вопросы пишите на elixir@wunsh.ru.