Последние несколько месяцев я работал над приложением, отвечающим за обработку данных в памяти. Этот проект интересен мне по двум причинам. Во-первых, проект полностью написан на Rust, а во-вторых, это возможность узнать о новых темах и библиотеках. В этой статье исследуется то, что я узнал во время работы с Apache Arrow.

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

Что такое Apache Arrow

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

Формат памяти

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

Библиотеки

Другие библиотеки предоставляются в качестве дополнения к Apache Arrow. Они предоставляют общие функции, которые вы бы не хотели реализовывать самостоятельно. Мне показались полезными две специфичные для Rust библиотеки: DataFusion и Arrow Flight. DataFusion - это механизм запросов, основанный на Apache Arrow, который предоставляет фрейм данных и API-интерфейсы SQL-запросов. Arrow Flight - это библиотека сериализации, которая используется для передачи данных по сети. Я расскажу об этих библиотеках более подробно в следующих статьях.

Экосистема разработчиков

Одним из важных факторов при выборе Apache Arrow была экосистема разработчика. Apache Arrow поддерживается Apache Software Foundation, которая обеспечивает руководящий орган и процесс принятия решений. Фонд также работает над поддержанием открытого для всех сообщества разработчиков ПО с открытым исходным кодом.

Реализация Rust Arrow

Существует несколько реализаций Apache Arrow, но я остановлюсь на версии Rust. Этот раздел будет разбит на четыре части: низкоуровневые массивы, высокоуровневые конструкции, средства чтения данных и вычислительные ядра. Не все аспекты библиотеки будут рассмотрены, но достаточно, чтобы получить хорошее представление о том, как ее можно использовать.

Массивы нижнего уровня

Важнейшим компонентом реализации Rust является трейт Array. Он представляет собой универсальный неизменяемый потокобезопасный массив фиксированных размеров элементов, допускающих значение NULL. Существует два способа создания массива: метод преобразователя и метод построения.

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

В этом фрагменте Int32Array - это Array, представляющий массив необязательных 32-разрядных целых чисел со знаком. Метод from принимает вектор из Option<i32>values ​​для построения массива. Существует несколько реализаций признака Array, поэтому обязательно проверьте документацию на правильный тип Array.

Метод построителя - это способ создания массива путем добавления к нему значений. Это полезно для динамического построения массивов из значений. Вот пример использования метода построителя:

В приведенном выше примере Int32Builder используется для инициализации экземпляра построителя, имеющего емкость 100 элементов. Для первых 50 элементов сгенерированное значение добавляется к массиву, если оно четное, в противном случае добавляется ноль - это не фактическое null значение. Для последних 50 элементов сгенерированные значения собираются в вектор, который затем добавляется к массиву в виде среза. Метод finish преобразует экземпляр построителя в экземпляр массива.

Конструкции высокого уровня

Массивы - невероятно мощная абстракция для работы с данными. Однако они не обеспечивают простой способ работы с табличными наборами данных. Вот где на сцену выходят эти высокоуровневые конструкции. Три структуры данных работают вместе с низкоуровневыми массивами для представления набора данных:

  • Field - определяет метаданные одного столбца данных.
  • Schema - определяет метаданные набора данных, содержит вектор Field
  • RecordBatch - определяет двумерный набор данных, который содержит низкоуровневые массивы одинаковой длины и Schema, который соответствует типам массивов.

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

В приведенном выше примере тип RecordBatch становится компонентом верхнего уровня, который охватывает схему и массивы примитивов. Следует отметить, что RecordBatch состоит из указателей с атомарным подсчетом ссылок на схему и отдельные массивы примитивов. Использование этого типа ссылки - важная причина, по которой Apache Arrow может обеспечивать чтение с нулевым копированием.

Считыватели данных

Возможность создания набора данных из основных компонентов Apache Arrow имеет ограниченные варианты использования. Существует определенная потребность в считывании данных в формат памяти Apache Arrow без особых усилий. В реализации Apache Arrow на Rust одним из предоставляемых средств чтения данных является средство чтения CSV.

Средство чтения CSV считывает записи из файла CSV, а затем преобразует столбцы в соответствующий тип примитивного массива на основе схемы. Читатель CSV может быть использован напрямую или сгенерирован из объекта построителя с пользовательскими настройками. Использование CSV-ридера напрямую может выглядеть так:

В этом примере определена схема, представляющая данные в файле CSV. Средство чтения CSV создается с использованием открытого файла, ссылки на схему и некоторых деталей конфигурации, например наличия у файла заголовков и размера пакета. Размер пакета - интересный параметр, поскольку он определяет, сколько пакетов записей будет создано. Например, если у вас есть CSV-файл, содержащий 10 000 записей, а размер пакета равен 1000, то программа чтения в конечном итоге вернет коллекцию из 10 RecordBatch экземпляров. Еще одна интересная особенность заключается в том, что программа для чтения CSV является итератором. Это означает, что средство чтения может использоваться в цикле for или любых других итерационных методах в Rust.

Читатель CSV также может быть сгенерирован из объекта построителя, что обеспечивает немного большую гибкость. Использование метода построителя может выглядеть так:

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

Вычислить ядра

Вычислительные ядра - это рабочие лошадки Apache Arrow. Предоставляется много функций ядра, но в этом разделе подробно рассматриваются только функции фильтрации и сортировки.

Функция фильтра принимает ссылку на массив данных и массив логических значений, которые указывают, следует ли фильтровать элементы в этой позиции. Чтобы отфильтровать массивы в объекте RecordBatch, функция фильтрации должна применяться ко всем массивам с использованием одного и того же логического массива. Например, фильтрация данных CSV по столбцу group может выглядеть примерно так:

В этом примере массив фильтров создается путем сравнения каждого элемента groupcolumn со значением параметра group. Затем массив фильтров используется для фильтрации каждого столбца в пакете. Новый экземпляр RecordBatch создается и возвращается с использованием исходной схемы и отфильтрованных массивов.

Apache Arrow предоставляет несколько функций, связанных с сортировкой. К этому примеру относится функция sort_to_indices, которая принимает ссылку на массив данных и необязательный объект SortOptions и возвращает массив целых чисел без знака, которые представляют новые позиции элементов в массиве. Вот пример:

В этом примере создается массив индексов путем вызова функции sort_to_indices со ссылкой на столбец group и без параметров сортировки. Затем функция thetake отображается на все столбцы для изменения порядка массивов на основе отсортированных индексов. Новый объект RecordBatch создается и возвращается с использованием той же схемы и отсортированных массивов.

Библиотека Apache Arrow ориентирована на низкоуровневые операции. Это означает, что некоторые общие операции агрегирования данных не являются частью API. Например, нет группы по функциям. Однако функциональность группировки по функциям может быть реализована с помощью фильтров, сортировок и выбора агрегатных функций. Вот пример:

В этом примере сначала выполняется поиск уникальных значений столбца group путем сортировки столбца group, сбора значений в вектор и удаления повторяющихся значений. Вектор массивов построителя инициализируется для хранения среднего балла для каждого столбца баллов. Для каждой уникальной группы функция filter_by_group используется для поиска всех соответствующих значений баллов. Среднее значение рассчитывается для каждого столбца в этой группе и добавляется к соответствующему построителю. Затем собираются результаты построителей, создается новая схема, представляющая новую структуру, и возвращается экземпляр RecordBatch.

Последние мысли

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

Спасибо

Спасибо за прочтение! Если вы хотите подключиться или оставить отзыв, свяжитесь со мной в LinkedIn.

Статьи по Теме



Ресурсы