Архитектура и производительность
На этой странице объясняется, как indexed-parquet-dataset обеспечивает случайный доступ O(1) и эффективно работает с терабайтами данных.
Стратегия индексации
В отличие от стандартных библиотек чтения Parquet, которые часто требуют сканирования всех групп строк (Row Groups) для поиска конкретной записи, наша библиотека строит глобальную карту индексов при инициализации.
1. Сканирование (Initialization)
Когда вы вызываете from_folder, библиотека:
1. Обходит все файлы Parquet по указанному пути.
2. Читает только метаданные (не сами данные) каждого файла.
3. Извлекает количество строк в каждой группе строк (Row Group) каждого файла.
4. Вычисляет кумулятивные оффсеты (префиксные суммы).
Результат: Легковесная карта в оперативной памяти, которая говорит: "строка #1,500,000 находится в файле X, в группе строк Y, на позиции Z".
2. Мгновенный поиск (Lookup)
При запросе dataset[i]:
1. Используется бинарный поиск (numpy.searchsorted) по оффсетам для поиска нужного файла за O(log N_files).
2. Вычисляется локальный индекс внутри файла.
3. Файл открывается (или берется из кэша), и считывается только одна нужная Row Group.
Управление ресурсами
Lazy Handle Loading (LRU Cache)
Открытие тысяч файлов одновременно приведет к превышению лимита файловых дескрипторов ОС. Чтобы этого не произошло, библиотека использует LRU (Least Recently Used) кэш для открытых файлов.
По умолчанию удерживается до 128 открытых файлов. Когда требуется 129-й, самый "старый" файл закрывается автоматически. Вы можете управлять этим через параметр max_open_files.
Экономия I/O
При чтении строки библиотека запрашивает у PyArrow только те колонки, которые необходимы для финального результата. Если вы использовали select_columns, данные остальных колонок даже не будут считаны с диска.
Работа в многопроцессорных режимах (PyTorch)
Для эффективного обучения моделей в PyTorch используется DataLoader с num_workers > 0. Это означает, что данные читаются параллельно в нескольких процессах.
indexed-parquet-dataset спроектирован так, чтобы быть полностью Pickle-совместимым:
1. Сам индекс (BaseIndex) легко сериализуется.
2. Открытые дескрипторы файлов не сериализуются. Вместо этого каждый воркер (процесс) при первом обращении к данным открывает свои собственные файлы.
3. Это гарантирует отсутствие конфликтов доступа к файлам между процессами.
Схема прохождения данных (Data Flow)
graph LR
Disk[(Parquet Files)] -- "1. Read Metadata" --> Indexer
Indexer -- "2. Build Presums" --> Index
User[dataset[idx]] --> Index
Index -- "3. File + RowGroup" --> LRUCache
LRUCache -- "4. Open/Reuse" --> PF[PyArrow File]
PF -- "5. Read RowGroup" --> Mapper
Mapper -- "6. Rename/Cast/Map" --> Output[Row Dict]
Когда O(1) может замедлиться?
Хотя поиск индекса всегда мгновенный, время выдачи строки может расти, если:
- Медленное хранилище: HDD или сетевые диски с высокими задержками (latency).
- Слишком много трансформаций: Если у вас длинная цепочка из .map() и .alias(lambda), Python тратит время на их выполнение.
- Очень маленькие Row Groups: Если файл разбит на тысячи крошечных групп, накладные расходы на чтение метаданных группы могут стать заметными.
В таких случаях рекомендуется использовать материализацию через .clone().