Инфраструктура для Data-Engineer форматы файлов
Введение
В современной дата-инженерии работа с данными неразрывно связана с различными форматами файлов. Каждый формат имеет свои особенности, преимущества и области применения. В этой статье мы рассмотрим наиболее популярные форматы, научимся с ними работать и поймем, когда какой формат лучше использовать.
Я подготовил примеры кода, которые помогут нам на практике разобраться с каждым форматом. Весь код доступен в моём репозитории.
Тестовые данные
Мы сейчас с вами создадим функцию, которая позволит генерировать пользователей для дальнейшей работы.
Если вы хотите использовать другие данные или использовать настоящие данные, то можете обратиться к моей статье Pet-проекты и данные для Data-Engineer
Для сравнения форматов создадим функцию-генератор данных:
import datetime
import uuid
import pandas as pd
from faker import Faker
def generate_users(size_of_generate: int = 1000) -> pd.DataFrame:
"""
Функция-генератор.
Позволяет создать случайных пользователей для работы с ними.
:param size_of_generate: Количество пользователей для генерации.
:return: pd.DataFrame с данными.
"""
fake = Faker(locale="ru_RU")
list_of_dict = []
for _ in range(size_of_generate):
dict_ = {
"id": str(uuid.uuid4()),
"created_at": fake.date_time_ad(
start_datetime=datetime.date(year=2024, month=1, day=1),
end_datetime=datetime.date(year=2025, month=1, day=1),
),
"updated_at": fake.date_time_ad(
start_datetime=datetime.date(year=2024, month=1, day=1),
end_datetime=datetime.date(year=2025, month=1, day=1),
),
"first_name": fake.first_name(),
"last_name": fake.last_name(),
"middle_name": fake.middle_name(),
"birthday": fake.date_time_ad(
start_datetime=datetime.date(year=1980, month=1, day=1),
end_datetime=datetime.date(year=2005, month=1, day=1),
),
"email": fake.email(),
"city": fake.city(),
}
list_of_dict.append(dict_)
return pd.DataFrame(data=list_of_dict)
Parquet (.parquet)
Apache Parquet — колоночный формат хранения данных, оптимизированный для работы с большими наборами данных.
Рекомендую ознакомиться с видео Parquet File Format — Explained to a 5 Year Old! в нём хорошо рассказано про .parquet
.
Преимущества
Является »стандартом» в дата-инженерии
Эффективное сжатие данных
Быстрое чтение отдельных колонок
Поддержка вложенных структур данных
Хорошая интеграция со многими инструментами в дата-инженерии и не только
Недостатки
Бинарный формат (нельзя прочитать в текстовом редакторе)
Может быть избыточным для маленьких наборов данных
Пример записи
from generate_data import generate_users
df = generate_users()
df.to_parquet(path="../data/data.parquet")
Пример чтения
Мы можем прочитать весь файл:
import pandas as pd
df = pd.read_parquet(path="../data/data.parquet")
print(df)
Или прочитать только отдельные колонки:
import pandas as pd
df = pd.read_parquet(path="../data/data.parquet", columns=["id", "city"])
print(df)
CSV (.csv)
Comma-Separated Values — текстовый формат для представления табличных данных.
Преимущества
Простой и понятный формат
Человекочитаемый
Поддерживается практически везде
Легко создавать и редактировать
Недостатки
Неэффективное использование места
Медленное чтение больших файлов
Проблемы с типами данных
Сложности с обработкой специальных символов
Пример записи
Без сжатия:
from generate_data import generate_users
df = generate_users()
df.to_csv(
path_or_buf='../data/data.csv',
index=False,
)
С сжатием:
from generate_data import generate_users
df = generate_users()
df.to_csv(
path_or_buf="../data/data.csv.gz",
compression="gzip",
index=False,
)
Пример чтения
Без сжатия:
import pandas as pd
df = pd.read_csv('../data/data.csv')
print(df)
С сжатием:
import pandas as pd
df = pd.read_csv('../data/data.csv.gz')
print(df)
Также мы сразу при чтении можем привести к нужным нам типам данных через конструкцию dtype
(необходимо использовать типы NumPy).
Изначально в .csv
всё хранится с типом object
:
id object
created_at object
updated_at object
first_name object
last_name object
middle_name object
birthday object
email object
city object
Но мы при чтении к примеру можем распарсить даты:
import pandas as pd
df = pd.read_csv(
filepath_or_buffer="../data/data.csv",
parse_dates=["created_at"],
)
print(df.dtypes)
И получим следующий результат после запуска:
id object
created_at datetime64[ns]
updated_at object
first_name object
last_name object
middle_name object
birthday object
email object
city object
JSON (.json)
JavaScript Object Notation — текстовый формат для хранения структурированных данных.
Преимущества
Человекочитаемый формат
Поддержка сложных структур данных
Широкая поддержка во всех языках
Удобен для API и веб-сервисов
Недостатки
Избыточность из-за повторения ключей
Большой размер файлов
Медленнее бинарных форматов
Пример записи
from generate_data import generate_users
df = generate_users()
df.to_json(path_or_buf="../data/data.json")
Пример чтения
import pandas as pd
df = pd.read_json("../data/data.json")
print(df)
Avro (.avro)
Apache Avro — компактный бинарный формат с поддержкой схем данных.
Преимущества
Компактное хранение
Встроенная поддержка схем
Хорошая совместимость со схемами
Популярен в Apache Kafka
Недостатки
Сложнее в использовании
Требует определения схемы
Меньше инструментов поддержки
Пример записи
import json
import avro.schema
from avro.datafile import DataFileWriter
from avro.io import DatumWriter
from generate_data import generate_users
df = generate_users(size_of_generate=1)
# Определение схемы
schema = {
"type": "record",
"name": "user",
"fields": [
{"name": "id", "type": "string"},
{"name": "created_at", "type": "string"},
{"name": "updated_at", "type": "string"},
{"name": "first_name", "type": "string"},
{"name": "middle_name", "type": "string"},
{"name": "birthday", "type": "string"},
{"name": "email", "type": "string"},
{"name": "city", "type": "string"},
],
}
# Преобразуем datetime колонки в строки
datetime_columns = ["created_at", "updated_at", "birthday"]
for col in datetime_columns:
df[col] = df[col].astype(str)
# Запись в Avro
with DataFileWriter(
open("../data/data.avro", "wb"),
DatumWriter(),
avro.schema.parse(json.dumps(schema)),
) as writer:
for _, row in df[[
"id",
"created_at",
"updated_at",
"first_name",
"last_name",
"middle_name",
"birthday",
"email",
"city"]].iterrows():
writer.append({
"id": row["id"],
"created_at": row["created_at"],
"updated_at": row["updated_at"],
"first_name": row["first_name"],
"middle_name": row["middle_name"],
"birthday": row["birthday"],
"email": row["email"],
"city": row["city"],
})
Пример чтения
from avro.datafile import DataFileReader
from avro.io import DatumReader
with DataFileReader(
open("../data/data.avro", "rb"),
DatumReader(),
) as reader:
for record in reader:
print(record)
Важно: в примерах выше я записал и прочитал одну строку, потому что формат .avro
не используется для пакетной передачи данных. Он чаще всего встречается при передачи сообщений в Kafka или других брокерах сообщений.
Если вы не знаете что такое Kafka, но хотели бы с ней познакомиться, то можете изучить мою статью Инфраструктура для data engineer Kafka.
Сравнение форматов
Давайте сравним размеры файлов:
import os
import pandas as pd
def measure_formats() -> pd.DataFrame:
"""
Функция, которая позволяет посчитать объём файлов в разных форматах.
:return: pd.DataFrame c данными.
"""
results = []
# Размеры файлов
files = {
"parquet": "../data/data.parquet",
"csv": "../data/data.csv",
"csv.gz": "../data/data.csv.gz",
"json": "../data/data.json",
}
for format_name, filename in files.items():
if os.path.exists(filename):
size = os.path.getsize(filename)
results.append({
"format": format_name,
"size_mb": size / (1024 * 1024),
})
return pd.DataFrame(results)
print(measure_formats())
Для 1 строки в форматах:
format size_mb
0 parquet 0.006000
1 csv 0.000274
2 csv.gz 0.000257
3 json 0.000444
4 avro 0.000598
Для 1'000 строк в форматах:
format size_mb
0 parquet 0.103770
1 csv 0.205385
2 csv.gz 0.085176
3 json 0.356647
Для 1'000'000 строк в форматах
format size_mb
0 parquet 73.052988
1 csv 205.540927
2 csv.gz 58.519802
3 json 382.720285
Заключение
В этой статье мы рассмотрели основные форматы файлов, используемые в дата-инженерии. Каждый формат имеет свои сильные и слабые стороны:
.parquet
отлично подходит для аналитических запросов и работы с большими данными..csv
остается универсальным форматом для обмена данными..json
незаменим в веб-разработке и API..avro
прекрасно работает в распределенных системах. При выборе формата стоит учитывать:Объем данных.
Требования к производительности.
Необходимость человекочитаемости.
Интеграцию с другими системами.
Требования к схеме данных.
В реальных проектах часто используется комбинация форматов — например, .json
для API, .parquet
для хранения исторических данных, и .csv
для отчетов. Главное — выбирать формат исходя из конкретных требований задачи.
Стоит также упомянуть, что я перечислил только популярные форматы, с которыми чаще всего встречается дата-инженер.
Существуют ещё следующие форматы данных:
.log
— представляют собой текстовые файлы, которые используются для записи логов (журналов) различных приложений, систем и серверов..orc
(Optimized Row Columnar) — оптимизированный колоночный формат, изначально разработанный для Hive..xml
(eXtensible Markup Language) — разметка, которая описывает данные с помощью тегов. Хотя менее популярен из-за своей громоздкости по сравнению с.json
,.xml
часто используется в системах, требующих строгого описания структуры данных.etc
Также если вам необходима консультация/менторство/мок-собеседование и другие вопросы по дата-инженерии, то вы можете обращаться ко мне. Все контакты указаны по ссылке.