Инфраструктура для Data-Engineer форматы файлов

4e93c7b0ea714848c53d17d026a52fd7.png

Введение

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

Я подготовил примеры кода, которые помогут нам на практике разобраться с каждым форматом. Весь код доступен в моём репозитории.

Тестовые данные

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

Если вы хотите использовать другие данные или использовать настоящие данные, то можете обратиться к моей статье 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

Также если вам необходима консультация/менторство/мок-собеседование и другие вопросы по дата-инженерии, то вы можете обращаться ко мне. Все контакты указаны по ссылке.

© Habrahabr.ru