Содержание
  1. Формат в памяти, библиотека PyArrow и IPC — разные уровни
  2. Array, ChunkedArray, Table и схема типов
  3. Полный разбор [10, null, 30] с validity bitmap 0x05
  4. Расчёт 12 байт слотов и 1 байта bitmap без padding и metadata
  5. Когда zero-copy возможен и когда нужна копия
  6. Pandas nullable-типы и Arrow-типы могут различаться
  7. Отличить Arrow в памяти от Parquet на диске
  8. Частые вопросы
Справочник

Что такое Apache Arrow: таблицы в памяти

13 ноября 2026 · 17 минут чтения

Apache Arrow — спецификация колоночного представления данных в оперативной памяти плюс набор библиотек, которые это представление читают и пишут. Что такое Apache Arrow: таблицы в памяти — это описание того, как значения лежат в буферах, как отдельно хранится признак пропуска и почему две программы могут обменяться таблицей без копирования. Формат задаёт раскладку байтов, библиотека PyArrow даёт к ней доступ из Python, а IPC переносит те же буферы между процессами.

Формат в памяти, библиотека PyArrow и IPC — разные уровни

Разговор про Arrow сбивается, когда три разных предмета называют одним словом. Спецификация формата описывает, как устроены буферы: где лежат значения, где битовая карта пропусков, в каком порядке байты. Библиотека — это код, который создаёт такие буферы и умеет их читать; в Python это PyArrow, в других языках свои реализации. Протокол обмена описывает, как те же буферы упаковать в поток или файл, чтобы передать другому процессу.

Документация формата на arrow.apache.org описывает Arrow как колоночный формат для аналитических задач в памяти. В том же документе сказано, что значения в массиве могут быть семантически пустыми независимо от того, примитивный это тип или вложенный. Это утверждение относится к уровню формата: признак пропуска существует для любого типа, и реализации обязаны его учитывать.

PyArrow — отдельный уровень. Это библиотека-обёртка, которая предоставляет объекты Array, ChunkedArray, Table, Schema и функции чтения-записи. Она реализует спецификацию, но сама спецификацией не является. Когда в тексте говорят «Arrow не копирует данные», речь идёт о свойстве формата: если две библиотеки договорились о раскладке, они могут смотреть в одни и те же байты. Конкретная библиотека может этого и не делать.

IPC — третий уровень. Документация формата указывает, что формат IPC по умолчанию использует little-endian. Это правило сериализации: при передаче между процессами и машинами байты укладываются в младшим-вперёд порядке. Это правило распространяется на сериализованные потоки и файлы IPC; метаданные схемы указывают порядок байтов их данных.

Parquet — колоночный формат файла для хранения таблиц. Arrow описывает представление данных в памяти. Читатель файла может декодировать Parquet в Arrow-таблицу; способ и объём загрузки зависят от выбранного инструмента и столбцов.

Практический вывод из разделения уровней: вопрос «поддерживает ли инструмент Arrow» надо уточнять. Инструмент может читать и писать IPC, может принимать Arrow-таблицу в памяти через API, может использовать Arrow внутри для обмена между движками. Три ответа означают три разные вещи.

Уровень Что описывает Где живёт Кто отвечает
Формат в памяти Раскладку буферов, bitmap, порядок байтов Оперативная память процесса Спецификация Arrow
Библиотека Объекты Array, Table, Schema, чтение и запись Код в процессе PyArrow и аналоги
IPC Упаковку буферов в поток или файл Канал, файл, сокет Спецификация IPC
Parquet Колоночный файл со сжатием и row groups Диск и объектное хранилище Спецификация Parquet

Разделение объясняет и типовой сценарий обмена. Движок читает Parquet с диска, собирает Arrow-таблицу в памяти, отдаёт её другой библиотеке через Arrow C Data Interface, та считает результат и возвращает новую таблицу. Диск, память и канал участвуют в одной цепочке, и на каждом переходе действуют свои правила.

Array, ChunkedArray, Table и схема типов

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

ChunkedArray — последовательность нескольких Array с одинаковым типом. Она появляется естественным образом: чтение большого файла идёт порциями, и каждая порция становится отдельным массивом. Логически ChunkedArray выглядит как один столбец, физически это список кусков. Различие важно для копирования: несколько кусков требуют непрерывного представления при передаче в библиотеку, которая такого требования придерживается.

Table — именованный набор столбцов с общей схемой. Каждый столбец таблицы представлен ChunkedArray. Схема описывает имена полей, их типы и признак допустимости пропуска. Схема — часть данных: две таблицы с одинаковыми значениями и разными типами столбцов дадут разные результаты при объединении или записи.

Типы в Arrow заданы явно и не совпадают один в один с типами языков. Целое число имеет ширину и знак: int8, int16, int32, int64 и беззнаковые варианты. Число с плавающей точкой имеет ширину. Текстовые значения представлены типом utf8, а произвольные последовательности байтов — типом binary. Отдельно существуют типы для дат, времени, интервалов, десятичных чисел, списков, структур и словарей. Схема фиксирует выбранный вариант, и при обмене обе стороны должны его понимать.

У примитивного массива с пропусками есть дополнительный буфер — битовая карта; у Union и run-end encoded представлений устройство отличается. Документация формата описывает нумерацию битов как least-significant bit, то есть бит с младшим значением идёт первым. Для массива без пропусков реализация может не выделять карту вовсе; в документации сказано, что представление такого отсутствующего буфера зависит от реализации, и в C++ он может быть обозначен нулевым указателем. Так реализация может сэкономить память, сохраняя смысл формата.

Схема типов решает и вопрос совместимости. Два инструмента договорятся об обмене, если их представления типов совпадают. Целое int32 и целое int64 — разные типы, и обмен между ними потребует преобразования. utf8 и binary — тоже разные типы. Проверка схемы перед обменом дешевле, чем отладка испорченных значений после него.

Объект Что содержит Число буферов Типичный источник
Array Значения одного типа, длина, карта пропусков Один или несколько Порция чтения, результат вычисления
ChunkedArray Список Array одного типа По числу кусков Столбец таблицы после чтения файла
Table Столбцы и схема По числу столбцов и кусков Результат чтения или вычисления
Schema Имена, типы, признак допустимости пропуска Метаданные Описание таблицы

Полный разбор [10, null, 30] с validity bitmap 0x05

Возьмём учебный пример: массив nullable int32 со значениями 10, пропуск, 30. Длина равна трём, число пропусков равно одному. Логические слоты значений выглядят как 10, 0, 30 — в слоте пропуска лежит ноль, и этот ноль не является значением. Битовая карта определяет, содержит ли слот действительное значение.

Битовая карта для трёх элементов занимает один байт. Биты идут от младшего к старшему, и для нашего массива они равны 1, 0, 1: первый элемент присутствует, второй пропущен, третий присутствует. В шестнадцатеричной записи первый байт карты равен 0x05. Проверка: младший бит даёт единицу, следующий бит даёт ноль, третий бит даёт единицу, сумма равна 1 + 4 = 5.

Буфер значений в little-endian для int32 содержит три четырёхбайтовых слота: 0a000000 для десяти, 00000000 для слота пропуска, 1e000000 для тридцати. Число 30 в шестнадцатеричной записи — 1e. Порядок байтов младшим-вперёд означает, что младший байт значения идёт первым.

Чтение массива идёт по двум буферам одновременно. Для позиции с индексом i проверяется бит i в карте. Если бит равен единице, значение берётся из буфера значений. Если бит равен нулю, результат чтения — пропуск, и содержимое слота значений игнорируется. Именно поэтому ноль в слоте пропуска не превращается в значение ноль.

Ошибка, которую этот пример показывает наглядно: если проигнорировать битовую карту, из массива 10, пропуск, 30 получится 10, 0, 30, и смысл пропуска изменится. Среднее по такому массиву посчитается по трём числам вместо двух. Фильтр «значение равно нулю» ошибочно выберет строку, в которой был пропуск. Поэтому чтение буфера значений нужно согласовывать с битовой картой.

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

ТЕКСТ15 строк
Массив: nullable int32, логические значения [10, null, 30]
length = 3
null_count = 1
validity bits = [1, 0, 1]
первый байт bitmap = 0x05

Буфер значений, little-endian, три слота по 4 байта:
  слот 0: 0a 00 00 00   -> 10
  слот 1: 00 00 00 00   -> пропуск, содержимое не читается
  слот 2: 1e 00 00 00   -> 30

Проверка чтения:
  индекс 0: бит 1 -> значение 10
  индекс 1: бит 0 -> пропуск
  индекс 2: бит 1 -> значение 30

Такой разбор полезен как эталон при отладке. Если библиотека возвращает 10, 0, 30, значит, битовая карта не была прочитана или была прочитана с неверным порядком битов. Если возвращается 10, пропуск, 30, но карта построена с обратным порядком битов, ошибка проявится на массивах, где пропуски стоят в разных позициях.

Расчёт 12 байт слотов и 1 байта bitmap без padding и metadata

Полезные слоты значений в нашем примере занимают 3 × 4 = 12 байт. Битовая карта для трёх бит занимает 1 байт. Эти два числа описывают только полезную нагрузку двух буферов и не описывают полный размер массива в памяти.

Что остаётся за рамками расчёта. Метаданные описывают тип, длину, число пропусков и ссылки на буферы. Выравнивание требует размещать буферы по адресам, кратным 8 или 64 байтам, как рекомендует документация формата. Padding дополняет буфер до длины, кратной 8 или 64 байтам. Для массива из трёх элементов накладные расходы могут превышать полезную нагрузку, и это нормально: формат рассчитан на большие массивы, где накладные расходы размываются.

Рекомендация выравнивания в документации сформулирована как совет реализациям выделять память по адресам, кратным 8 или 64 байтам, и дополнять буфер до длины, кратной 8 или 64 байтам. Причина практическая: выровненный доступ к памяти быстрее, а векторные инструкции работают с выровненными адресами эффективнее. Padding не несёт данных и не должен интерпретироваться как значения.

Отсюда следует правило оценки памяти. Для массива из n значений фиксированной ширины w полезная нагрузка значений равна n × w байт. Битовая карта занимает ceil(n / 8) байт, если она выделена. К этому добавляются метаданные и padding. Для больших n доля накладных расходов мала; для массивов из нескольких элементов она может быть больше самой нагрузки.

Компонент Формула для n = 3, w = 4 Значение Входит в полезную нагрузку
Слоты значений n × w 12 байт Да
Битовая карта ceil(n / 8) 1 байт Да
Метаданные Зависит от реализации Не фиксировано Нет
Padding До кратности 8 или 64 Не фиксировано Нет

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

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

Когда zero-copy возможен и когда нужна копия

Обмен без копирования возможен, когда две стороны смотрят в одни и те же байты и одинаково понимают их раскладку. Условий несколько, и все они должны выполняться одновременно.

Первое условие — совпадение раскладки. Обе стороны должны использовать один и тот же формат буферов, одну и ту же ширину типов, один и тот же порядок байтов. Если одна сторона ожидает int64, а данные лежат как int32, потребуется преобразование. Если одна сторона ожидает little-endian, а данные пришли в другом порядке, потребуется перестановка байтов.

Второе условие — непрерывность там, где её требует принимающая сторона. Документация интеграции Arrow с pandas прямо говорит, что несколько кусков всегда потребуют копирования из-за требования непрерывности в pandas. ChunkedArray из нескольких Array не станет одним непрерывным блоком без переупаковки. Однокусковый массив может быть передан без копирования, если остальные условия выполнены.

Третье условие — совместимость типов. Документация Arrow по интеграции с pandas указывает, что преобразования без копирования, при которых не требуется ни выделение памяти, ни вычисления, возможны только в ограниченном наборе случаев. Это честная формулировка: общий формат создаёт возможность, а конкретная пара типов и конкретная библиотека определяют, реализуется ли она.

Четвёртое условие — владение памятью. Буфер должен оставаться действительным, пока на него смотрят обе стороны. Если исходный объект освобождает память, вторая сторона получит висячую ссылку. Поэтому обмен обычно сопровождается соглашением о времени жизни: либо буфер удерживается ссылкой, либо копируется.

Ситуация Копирование Причина
Один Array, совпадающие типы, совпадающий порядок байтов Обычно возможно без копии Раскладка совпадает
ChunkedArray из нескольких кусков в pandas Требуется Непрерывность в pandas
Разные ширины целых типов Требуется Нужно преобразование значений
Разные представления nullable-типов Требуется Нужно согласование признака пропуска
Данные читаются из Parquet Требуется Формат файла преобразуется в формат памяти

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

Отдельный сценарий — обмен между движками через Arrow C Data Interface. Статья DuckDB о совместной работе с Arrow описывает интеграцию, при которой запрос выполняется в DuckDB, а результат передаётся в Arrow потоком без дополнительного копирования. Там же описан приём передачи данных между движками: если операция не поддерживается в одном движке, данные передаются в другой, и обратно, без затрат на повторную сериализацию. Это иллюстрация того, как общий формат памяти позволяет разделить работу между инструментами.

Pandas nullable-типы и Arrow-типы могут различаться

Pandas исторически использовал NumPy-массивы, и признак пропуска в них выражался по-разному для разных типов. Для чисел с плавающей точкой пропуск обозначался значением NaN. Для целых чисел собственного признака пропуска не было, и столбец с пропусками превращался в столбец с плавающей точкой. Для строк использовался объектный тип. В обычных примитивных массивах Arrow признак пропуска хранится в отдельном буфере, и это расхождение приходится согласовывать.

Современный pandas умеет хранить данные на Arrow-бэкенде. Документация pandas по PyArrow указывает, что читатели могут возвращать данные на PyArrow-бэкенде при указании параметра dtype_backend=»pyarrow». Там же сказано, что функции чтения предоставляют параметр engine, который может направить чтение в PyArrow для ускорения работы с источником ввода-вывода. Это два разных параметра с разным действием: один задаёт представление результата, другой выбирает движок чтения.

Тонкость, которую документация pandas подчёркивает отдельно: указание строкового типа через ArrowDtype и указание строкового типа через StringDtype с Arrow-бэкендом дают разные результаты. В документации сказано, что одно не эквивалентно другому. Практический смысл: два столбца, которые выглядят как строковые, могут иметь разные типы, и операция, работающая с одним, может повести себя иначе с другим.

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

Проверка типов перед обменом выглядит так: посмотрите на схему Arrow-таблицы и на типы столбцов pandas. Если столбец pandas имеет Arrow-бэкенд, его тип соответствует Arrow-типу, и обмен может пройти без преобразования. Если столбец использует NumPy-бэкенд, соответствие нужно проверять отдельно для каждого типа, и для целых чисел с пропусками преобразование почти наверняка потребуется.

Что сравниваем NumPy-бэкенд в pandas Arrow-бэкенд в pandas
Целое с пропуском Требует замены типа или отдельной маски Хранит пропуск в битовой карте
Строка Объектный тип или строковый тип pandas Arrow-тип utf8 или large_utf8
Признак пропуска Разный для разных типов Битовая карта у обычных примитивных массивов
Обмен с Arrow-таблицей Часто требует копирования Часто проходит без копирования

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

Отличить Arrow в памяти от Parquet на диске

Arrow и Parquet часто упоминают вместе, и это порождает представление, что это два режима одного формата. Это два разных формата с разными задачами. Arrow описывает данные в оперативной памяти, Parquet описывает файл на диске.

Parquet хранит данные колоночно, сжимает их и разбивает на row groups. Сжатие и разбиение нужны для экономии места и для чтения только нужных частей файла. Arrow хранит данные в памяти в том виде, в котором их удобно обрабатывать: без сжатия, с выровненными буферами, с прямым доступом к значениям по индексу.

Отсюда следуют практические различия. Файл Parquet можно читать частично: движок пропускает row groups, которые не нужны для запроса, и читает только нужные столбцы. Уже построенная Arrow-таблица предоставляет доступ к столбцам и срезам; возможность отбора при загрузке определяется источником и API чтения. Сжатие Parquet может уменьшать объём хранения. Представление Arrow даёт прямой доступ к значениям; итоговые размер и скорость зависят от данных, кодеков и операции.

Статья DuckDB о совместной работе с Arrow описывает, как эти свойства складываются в цепочку. Фильтры и проекции проталкиваются в сканирование, и читаются только нужные столбцы и row groups. В примере с набором данных о поездках такси из двадцати столбцов читаются четыре. Arrow-таблица при этом собирается уже из отобранных данных, и лишние байты в память не попадают.

Свойство Arrow в памяти Parquet на диске
Назначение Обработка в оперативной памяти Хранение и передача файлов
Сжатие Отсутствует Применяется
Частичное чтение Доступ к столбцам и срезам; загрузка зависит от API Через row groups и столбцы
Выравнивание буферов Рекомендуется Не является свойством формата
Пропуски Битовая карта в буфере Кодируются в структуре файла
Порядок байтов Задаётся форматом IPC Задаётся форматом файла

Практическое правило выбора: Parquet — для хранения и передачи между системами, Arrow — для обработки в памяти и для обмена между библиотеками внутри одного процесса или между процессами на одной машине. Типовая цепочка выглядит так: файл Parquet читается, преобразуется в Arrow-таблицу, обрабатывается одной или несколькими библиотеками, результат записывается обратно в Parquet.

Отдельно стоит помнить про порядок байтов. Документация формата указывает, что формат IPC по умолчанию использует little-endian. Это правило действует при передаче данных между процессами. Файл Parquet имеет собственные правила кодирования, и при чтении значения преобразуются в представление памяти.

Частые вопросы

Чем Arrow отличается от Parquet?

Это два формата с разными задачами. Arrow описывает данные в оперативной памяти: выровненные буферы, битовые карты пропусков, прямой доступ по индексу. Parquet описывает файл на диске: сжатие, row groups, статистика для пропуска ненужных частей. Типовая цепочка читает Parquet, преобразует в Arrow-таблицу, обрабатывает и записывает обратно.

Что такое PyArrow и нужен ли он отдельно?

PyArrow — библиотека, которая реализует спецификацию Arrow для Python и предоставляет объекты Array, ChunkedArray, Table, Schema. Она устанавливается отдельно от pandas и от других библиотек. Многие инструменты используют PyArrow внутри, поэтому он часто оказывается в зависимостях, даже если вы его не устанавливали явно.

Почему при преобразовании в pandas данные копируются?

Документация интеграции Arrow с pandas указывает, что несколько кусков всегда потребуют копирования из-за требования непрерывности в pandas. Однокусковый массив может быть передан без копирования, если совпадают типы и порядок байтов. Преобразования без копирования возможны только в ограниченном наборе случаев, и конкретную пару типов надо проверять.

Что означает bitmap 0x05 в примере с [10, null, 30]?

Битовая карта хранит по одному биту на элемент, от младшего к старшему. Для массива 10, пропуск, 30 биты равны 1, 0, 1. В шестнадцатеричной записи первый байт равен 0x05, потому что единицы стоят в позициях с весом 1 и 4. Если карту проигнорировать, массив прочитается как 10, 0, 30, и смысл пропуска изменится.

Как понять, что обмен прошёл без копирования?

Проверьте совместимость раскладки, типов и времени жизни буферов. Для обычного преобразования в NumPy-представление pandas важны также отсутствие пропусков и один кусок. Условия зависят от конкретного API; факт использования общих буферов проверяйте средствами выбранной библиотеки.

Читайте также

3 материала