Хранение данных
"Data is a precious thing and will last longer than the systems themselves." — Tim Berners-Lee
Данные — это топливо любой системы. Но данные бывают радикально разными: SQL-таблица с транзакциями банка, терабайтный дамп логов, видеофайл на 4K, обученная ML-модель весом в десятки гигабайт, петабайт сырых данных IoT-сенсоров. Хранить всё это в одной реляционной базе — абсурд. Разные типы данных требуют разных типов хранилищ, каждое из которых оптимизировано под свои паттерны доступа, объёмы и требования к надёжности.
В этой главе мы разберём три фундаментальных типа хранилищ (block, object, file), детально изучим распределённые файловые системы (HDFS, GlusterFS), объектное хранилище (S3, MinIO, GCS), сравним Data Lake и Data Warehouse, а также рассмотрим стратегии партиционирования данных — ключевой механизм масштабирования любого хранилища.
1 Три типа хранилищ: Block, File, Object
Все системы хранения данных можно разделить на три фундаментальные категории. Каждая работает на своём уровне абстракции и решает свой класс задач.
Block Storage (Блочное хранилище)
Блочное хранилище работает на самом низком уровне — оно делит данные на блоки фиксированного размера (обычно 512 байт — 4 КБ) и адресует каждый блок по его физическому или логическому номеру. Файловая система, базы данных и операционная система работают поверх блочного хранилища.
Block Storage:
┌────────────────────────────────────────────┐
│ Disk / SAN / EBS Volume │
│ │
│ [Block 0][Block 1][Block 2]...[Block N] │
│ 512B 512B 512B 512B │
│ │
│ Нет метаданных, нет файлов, нет папок │
│ Только блоки с адресами │
└────────────────────────────────────────────┘
Характеристики:
- Минимальная латенси (прямой доступ к блоку по адресу)
- Поддерживает произвольный доступ (random I/O)
- Идеально для баз данных, которые сами управляют размещением данных на диске
- Не содержит метаданных о файлах — это ответственность файловой системы поверх
Примеры:
- Локальные диски (SSD, HDD)
- SAN (Storage Area Network) — общие блочные устройства в дата-центре
- AWS EBS (Elastic Block Store) — виртуальные диски для EC2
- GCP Persistent Disk, Azure Managed Disks
Когда использовать: Базы данных (PostgreSQL, MySQL, MongoDB), виртуальные машины (загрузочные тома), приложения, требующие минимальной латенси I/O.
File Storage (Файловое хранилище)
Файловое хранилище добавляет уровень абстракции поверх блоков — иерархическую структуру каталогов и файлов с метаданными (имя, размер, дата создания, права доступа). Это то, с чем работает обычный пользователь и большинство приложений.
File Storage:
┌──────────────────────────────────────┐
│ / │
│ ├── home/ │
│ │ ├── user1/ │
│ │ │ ├── report.pdf (2.1 MB) │
│ │ │ └── photo.jpg (3.5 MB) │
│ │ └── user2/ │
│ │ └── data.csv (150 KB) │
│ └── var/ │
│ └── log/ │
│ └── app.log (50 MB) │
│ │
│ Иерархия + метаданные + права │
└──────────────────────────────────────┘
Характеристики:
- Иерархическая структура (каталоги, подкаталоги)
- Поддерживает POSIX-операции: open, read, write, seek, close
- Поддерживает блокировки файлов (file locking)
- Может быть общим (shared) — несколько серверов монтируют одну файловую систему
Примеры:
- Локальные ФС: ext4, XFS, NTFS
- Сетевые ФС: NFS (Network File System), SMB/CIFS
- Облачные: AWS EFS (Elastic File System), Azure Files, GCP Filestore
- Распределённые: GlusterFS, CephFS
Когда использовать: Общие данные между серверами (конфигурации, медиа для обработки), CMS, legacy-приложения, требующие POSIX-совместимости, home-директории пользователей.
Object Storage (Объектное хранилище)
Объектное хранилище — принципиально другая модель. Вместо иерархии каталогов — плоское пространство имён. Каждый объект состоит из данных (blob), метаданных (произвольные key-value пары) и уникального идентификатора. Объекты не редактируются частично — только полная перезапись.
Object Storage:
┌──────────────────────────────────────────────────────────┐
│ Bucket: "my-app-media" │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Key: "users/123/avatar.jpg" │ │
│ │ Data: <binary blob, 45 KB> │ │
│ │ Metadata: │ │
│ │ Content-Type: image/jpeg │ │
│ │ Upload-Date: 2025-01-15T10:30:00Z │ │
│ │ x-amz-meta-user-id: 123 │ │
│ │ x-amz-meta-original-name: "photo.jpg" │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Key: "videos/raw/abc123.mp4" │ │
│ │ Data: <binary blob, 2.3 GB> │ │
│ │ Metadata: │ │
│ │ Content-Type: video/mp4 │ │
│ │ x-amz-meta-duration: 3600 │ │
│ │ x-amz-meta-resolution: 4K │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ Плоское пространство имён (нет реальных директорий) │
│ Префикс "users/123/" — это просто часть строки ключа │
└──────────────────────────────────────────────────────────┘
Характеристики:
- Плоское пространство имён (хотя «/» в ключах имитирует каталоги)
- Каждый объект неизменяем (immutable) — обновление = полная перезапись
- Нет частичного чтения/записи (нельзя дописать в конец файла)
- Масштабируется практически бесконечно (петабайты и более)
- Встроенная репликация и долговечность (durability 99.999999999% у S3)
- HTTP API (REST) вместо POSIX
Примеры:
- AWS S3 (Simple Storage Service)
- Google Cloud Storage (GCS)
- Azure Blob Storage
- MinIO (self-hosted S3-совместимое хранилище)
- Ceph RADOS Gateway
Когда использовать: Статические файлы (изображения, видео, документы), бэкапы, логи, Data Lake, ML-датасеты, артефакты CI/CD, хостинг статических сайтов.
Сравнительная таблица
| Характеристика | Block Storage | File Storage | Object Storage |
|---|---|---|---|
| Абстракция | Блоки по адресу | Файлы в каталогах | Объекты по ключу |
| Протокол | SCSI, iSCSI, NVMe | NFS, SMB, POSIX | HTTP REST API |
| Латенси | <1 ms | 1–10 ms | 10–100 ms |
| Произвольный доступ | Да (random I/O) | Да (seek) | Нет (целиком) |
| Частичное обновление | Да (запись блока) | Да (write at offset) | Нет (перезапись) |
| Масштабируемость | Ограничена (ТБ) | Средняя (десятки ТБ) | Практически безлимитная (ПБ) |
| Стоимость ($/ГБ/мес) | ~$0.10 | ~$0.30 | ~$0.023 |
| Durability | Зависит от RAID/репликации | Зависит от реализации | 99.999999999% (11 девяток) |
| Метаданные | Нет | Системные (POSIX) | Произвольные (key-value) |
| Типичное использование | БД, VM | Shared files, CMS | Медиа, бэкапы, Data Lake |
Ключевой принцип выбора: чем ниже уровень абстракции — тем ниже латенси, но тем сложнее управление и ниже масштабируемость. Чем выше уровень абстракции — тем проще масштабировать, но выше латенси и меньше гибкости в доступе к данным.
2 Распределённые файловые системы
Когда объём данных превышает возможности одного сервера (десятки терабайт и более), а для обработки нужна высокая пропускная способность, на сцену выходят распределённые файловые системы. Они разбивают данные на фрагменты, распределяют их по кластеру машин и предоставляют единый интерфейс доступа.
2.1 HDFS (Hadoop Distributed File System)
HDFS — распределённая файловая система, разработанная для хранения и обработки петабайтных объёмов данных на кластерах из commodity-серверов. Это основа экосистемы Apache Hadoop и де-факто стандарт для batch-обработки больших данных.
Архитектура HDFS:
HDFS построена по модели master-worker с разделением метаданных и данных.
┌─────────────────────┐
│ NameNode │
│ (Master) │
│ │
│ Метаданные: │
│ - Дерево файлов │
│ - Маппинг файл → │
│ блоки │
│ - Маппинг блок → │
│ DataNodes │
│ - Права доступа │
└──────────┬──────────┘
│
Heartbeat + Block Report
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ DataNode 1 │ │ DataNode 2 │ │ DataNode 3 │
│ │ │ │ │ │
│ [B1] [B4] │ │ [B1] [B2] │ │ [B2] [B4] │
│ [B3] [B5] │ │ [B3] [B6] │ │ [B5] [B6] │
└─────────────┘ └─────────────┘ └─────────────┘
B1–B6 — блоки данных (по умолчанию 128 МБ каждый)
Каждый блок реплицирован (replication factor = 3 по умолчанию)
NameNode (Master):
- Хранит все метаданные файловой системы в оперативной памяти
- Знает дерево каталогов, маппинг файлов на блоки, маппинг блоков на DataNodes
- Не хранит данные — только метаданные
- Single point of failure → требует High Availability (см. ниже)
DataNode (Worker):
- Хранит блоки данных на локальных дисках
- Периодически отправляет heartbeat и block report на NameNode
- Если heartbeat не получен N секунд — NameNode считает DataNode мёртвым и инициирует ре-репликацию блоков
Процесс записи файла:
Клиент хочет записать файл "data.csv" (400 МБ):
1. Клиент → NameNode: "Хочу создать /data/data.csv"
2. NameNode: Файл разбивается на 4 блока (по 128 МБ):
Block1 → [DN1, DN3, DN5] (три реплики)
Block2 → [DN2, DN4, DN6]
Block3 → [DN1, DN2, DN4]
Block4 → [DN3, DN5, DN6]
3. NameNode → Клиент: "Пиши Block1 на DN1, DN3, DN5"
4. Клиент → DN1: данные Block1
DN1 → DN3 → DN5 (pipeline репликация)
5. Повторяем для Block2, Block3, Block4
6. Клиент → NameNode: "Запись завершена"
Pipeline-репликация: клиент отправляет данные только на первый DataNode, а тот передаёт следующему в цепочке. Это позволяет утилизировать сетевую полосу каждого узла, а не перегружать клиент.
Процесс чтения файла:
Клиент хочет прочитать "/data/data.csv":
1. Клиент → NameNode: "Где блоки /data/data.csv?"
2. NameNode → Клиент:
Block1 → [DN1 (rack1), DN3 (rack2), DN5 (rack3)]
Block2 → [DN2 (rack1), DN4 (rack2), DN6 (rack3)]
...
(DataNodes отсортированы по «близости» к клиенту)
3. Клиент → DN1: "Дай Block1" (ближайший DataNode)
4. Клиент → DN2: "Дай Block2"
...
Клиент всегда читает с ближайшего DataNode (rack-aware placement). Если DN недоступен — переключается на другую реплику.
Rack-aware placement:
HDFS размещает реплики с учётом стоек (racks), чтобы обеспечить доступность при отказе целой стойки:
Replication Factor = 3:
Реплика 1: Локальный DataNode (тот же rack, где клиент)
Реплика 2: DataNode в другом rack
Реплика 3: Другой DataNode в том же rack, что и Реплика 2
Логика:
- Если упадёт один DataNode → 2 реплики остаются
- Если упадёт целый rack → 1 реплика в другом rack остаётся
- Компромисс: запись быстрее (Реплики 2 и 3 в одном rack = быстрая репликация),
но доступность обеспечена (Реплика 1 в другом rack)
High Availability NameNode:
NameNode — single point of failure. HDFS решает это через Active/Standby конфигурацию:
┌──────────────┐
│ ZooKeeper │ ← Выбор Active NameNode
│ Ensemble │
└──────┬───────┘
│
┌────────────┼───────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ NameNode │ │ NameNode │
│ (Active) │ ──WAL──▶ │ (Standby) │
│ │ │ │
└─────────────┘ └─────────────┘
│ │
Shared Storage (JournalNodes)
или NFS для EditLog
- Active NameNode обрабатывает все запросы
- Standby NameNode постоянно реплицирует метаданные через JournalNodes (кворум из 3+ узлов, хранящих EditLog)
- При отказе Active — ZooKeeper переключает Standby в Active (failover за секунды)
Ограничения HDFS:
- Оптимизирована для больших файлов и последовательного чтения (sequential read). Плохо работает с миллионами мелких файлов (small files problem — каждый файл занимает ~150 байт метаданных в NameNode).
- Нет поддержки произвольной записи (random write) — файл записывается один раз, дополнение (append) ограничено.
- Не подходит для low-latency доступа (HDFS оптимизирована для throughput, не для латенси).
2.2 GlusterFS
GlusterFS — распределённая файловая система без центрального метаданного сервера (peer-to-peer архитектура). В отличие от HDFS, GlusterFS не имеет master-узла — все узлы равноправны.
GlusterFS: peer-to-peer архитектура
Client (FUSE / NFS / SMB)
│
▼
┌────────────────────────────────────┐
│ GlusterFS Translator Stack │
│ (клиентская библиотека) │
│ │
│ DHT (Distributed Hash Table) │ ← Определяет, на каком brick хранить файл │ │
│ AFR (Automatic File Replication) │ ← Обеспечивает репликацию
│ EC (Erasure Coding) │ ← Альтернатива репликации
└──────────────┬─────────────────────┘
│
┌──────────┼──────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│Brick 1 │ │Brick 2 │ │Brick 3 │ ← Bricks = каталоги на серверах
│(srv1: │ │(srv2: │ │(srv3: │
│ /data) │ │ /data) │ │ /data) │
└────────┘ └────────┘ └────────┘
Ключевые концепции:
- Brick — каталог на сервере, экспортированный в GlusterFS. Это атомарная единица хранения.
- Volume — логический том, объединяющий несколько bricks. Тип volume определяет стратегию хранения.
- Translator — модули, формирующие pipeline обработки запросов на стороне клиента.
Типы Volume:
| Тип | Описание | Пример |
|---|---|---|
| Distributed | Файлы распределяются по bricks (DHT). Нет репликации. | 3 bricks × 1 ТБ = 3 ТБ |
| Replicated | Каждый файл реплицируется на N bricks. | 3 bricks × 1 ТБ = 1 ТБ (replica 3) |
| Distributed-Replicated | Комбинация: файлы распределяются, каждый реплицируется. | 6 bricks, replica 2 = 3 ТБ |
| Dispersed (Erasure Coding) | Файлы разбиваются на фрагменты + фрагменты чётности. Экономичнее, чем репликация. | 6 bricks, redundancy 2 = 4 ТБ |
Преимущества GlusterFS:
- Нет single point of failure (нет master-узла)
- POSIX-совместимость (приложения работают без модификации)
- Простое масштабирование (добавляем bricks → автоматическая ребалансировка)
- Поддержка нескольких протоколов (FUSE, NFS, SMB, libgfapi)
Недостатки:
- Производительность на мелких файлах ниже, чем у локальной ФС
- Метаданные распределены через DHT — операции вроде
ls -Rна каталоге с миллионами файлов медленные - Нет оптимизации для batch-обработки (в отличие от HDFS)
HDFS vs GlusterFS: когда что выбирать
| Критерий | HDFS | GlusterFS |
|---|---|---|
| Паттерн нагрузки | Batch-обработка (MapReduce, Spark) | Shared storage (NFS-замена) |
| Размер файлов | Большие (> 128 МБ) | Любые |
| Доступ | Последовательное чтение/запись | POSIX (random read/write) |
| Архитектура | Master-worker (NameNode + DataNodes) | Peer-to-peer (все узлы равны) |
| Интеграция | Hadoop, Spark, Hive, Presto | Любое приложение через FUSE/NFS |
| Типичный кластер | Сотни — тысячи узлов | Единицы — десятки узлов |
Правило: Если нужно хранить и обрабатывать терабайты данных с помощью Hadoop/Spark — HDFS. Если нужна распределённая файловая система, совместимая с POSIX и заменяющая NFS — GlusterFS.
3 Object Storage: S3, MinIO, GCS
Объектное хранилище стало де-факто стандартом для хранения неструктурированных данных в облаке. Amazon S3, запущенный в 2006 году, определил паттерн, которому следуют все остальные: плоское пространство имён, HTTP API, неограниченная масштабируемость и durability «11 девяток».
3.1 Amazon S3: архитектура и модель данных
Ключевые абстракции:
- Bucket — контейнер для объектов. Имя глобально уникально в рамках всего AWS. Привязан к региону.
- Object — единица хранения. Состоит из ключа (строка до 1024 байт), данных (до 5 ТБ), метаданных и версии.
- Key — уникальный идентификатор объекта внутри bucket. Например:
videos/2025/01/intro.mp4.
S3 модель данных:
Bucket: "my-app-prod"
│
├── users/avatars/user-123.jpg ← Object (key + data + metadata)
├── users/avatars/user-456.png
├── videos/raw/vid-abc.mp4
├── videos/processed/vid-abc-720p.mp4
├── backups/2025-01-15/db-dump.sql.gz
└── ml/models/v3/model.bin
"users/avatars/" — это НЕ каталог, это просто префикс ключа.
S3 List с prefix="users/avatars/" и delimiter="/" имитирует листинг каталога.
Классы хранения (Storage Classes):
S3 предлагает несколько уровней хранения с разным балансом стоимости и доступности:
| Класс | Стоимость ($/ГБ/мес) | Латенси доступа | Типичное использование |
|---|---|---|---|
| S3 Standard | ~$0.023 | мс | Активно используемые данные |
| S3 Intelligent-Tiering | ~$0.023 + мониторинг | мс | Данные с непредсказуемым паттерном |
| S3 Standard-IA | ~$0.0125 | мс | Редко запрашиваемые, но нужны быстро |
| S3 One Zone-IA | ~$0.01 | мс | Нерепликабельные между AZ (дешевле) |
| S3 Glacier Instant | ~$0.004 | мс | Архив с мгновенным доступом |
| S3 Glacier Flexible | ~$0.0036 | минуты — часы | Долгосрочный архив |
| S3 Glacier Deep Archive | ~$0.00099 | 12–48 часов | Compliance, архив на годы |
Lifecycle policies позволяют автоматически перемещать объекты между классами:
# Пример lifecycle policy (boto3): s3_client.put_bucket_lifecycle_configuration( Bucket='my-app-prod', LifecycleConfiguration={ 'Rules': [ { 'ID': 'archive-old-logs', 'Filter': {'Prefix': 'logs/'}, 'Status': 'Enabled', 'Transitions': [ { 'Days': 30, 'StorageClass': 'STANDARD_IA' # Через 30 дней }, { 'Days': 90, 'StorageClass': 'GLACIER' # Через 90 дней }, ], 'Expiration': { 'Days': 365 # Удалить через год } } ] } )
Consistency Model:
С декабря 2020 года S3 предоставляет strong read-after-write consistency для всех операций:
- PUT нового объекта → немедленно доступен для чтения
- Overwrite существующего объекта → следующий GET вернёт новую версию
- DELETE → следующий GET вернёт 404
До 2020 года S3 предоставлял only eventual consistency для overwrites и deletes, что вызывало множество проблем.
Versioning:
Bucket: "my-docs" (versioning enabled)
Key: "report.pdf"
├── Version ID: "v3" (current) ← GET возвращает эту версию
├── Version ID: "v2"
└── Version ID: "v1"
DELETE "report.pdf" → добавляет Delete Marker (данные не удаляются)
GET "report.pdf" → 404 (Delete Marker)
GET "report.pdf?versionId=v2" → возвращает v2
Versioning защищает от случайного удаления и перезаписи. Часто используется в связке с MFA Delete (требует MFA-токен для удаления версий) для критических данных.
3.2 Multipart Upload
Объекты размером более 100 МБ рекомендуется загружать через multipart upload — файл разбивается на части (5 МБ — 5 ГБ каждая), части загружаются параллельно, при ошибке перезагружается только проблемная часть.
Multipart Upload: файл 10 ГБ
1. Initiate Multipart Upload → получаем Upload ID
2. Параллельная загрузка частей:
Part 1 (100 MB) ──────────► S3 ✓ ETag: "abc123"
Part 2 (100 MB) ──────────► S3 ✓ ETag: "def456"
Part 3 (100 MB) ──────────► S3 ✗ ошибка → retry ✓ ETag: "ghi789"
...
Part 100 (100 MB) ────────► S3 ✓ ETag: "xyz999"
3. Complete Multipart Upload (Upload ID, Parts + ETags)
→ S3 собирает объект из частей
4. Объект доступен для чтения
import boto3 from boto3.s3.transfer import TransferConfig s3 = boto3.client('s3') # Настройка multipart upload config = TransferConfig( multipart_threshold=100 * 1024 * 1024, # Файлы > 100 МБ → multipart multipart_chunksize=100 * 1024 * 1024, # Размер части: 100 МБ max_concurrency=10, # Параллельных загрузок: 10 use_threads=True, ) # Загрузка файла (boto3 автоматически использует multipart) s3.upload_file( 'large_video.mp4', 'my-bucket', 'videos/large_video.mp4', Config=config, )
3.3 MinIO: self-hosted S3
MinIO — высокопроизводительное S3-совместимое объектное хранилище с открытым исходным кодом. Полностью совместимо с S3 API, что позволяет использовать все существующие S3-инструменты и SDK без модификации.
Когда MinIO вместо S3:
- On-premise инфраструктура (нет облака или регуляторные требования)
- Контроль над данными и инфраструктурой
- Избежание vendor lock-in
- Среды разработки и тестирования (локальный S3)
- Edge-развёртывания
Архитектура MinIO:
MinIO: Distributed Mode
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ MinIO Node │ │ MinIO Node │ │ MinIO Node │ │ MinIO Node │
│ #1 │ │ #2 │ │ #3 │ │ #4 │
│ │ │ │ │ │ │ │
│ /disk1 │ │ /disk1 │ │ /disk1 │ │ /disk1 │
│ /disk2 │ │ /disk2 │ │ /disk2 │ │ /disk2 │
│ /disk3 │ │ /disk3 │ │ /disk3 │ │ /disk3 │
│ /disk4 │ │ /disk4 │ │ /disk4 │ │ /disk4 │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
Erasure Coding: данные разбиваются на data + parity shards
При 16 дисках (4 узла × 4 диска): 8 data + 8 parity
→ Выдерживает потерю до 8 дисков (50%) без потери данных
MinIO использует erasure coding вместо простой репликации — это экономичнее по дисковому пространству при той же отказоустойчивости.
# Запуск MinIO в distributed mode minio server http://node{1...4}/data/disk{1...4} # Использование с boto3 (тот же S3 API) s3 = boto3.client( 's3', endpoint_url='http://minio.internal:9000', aws_access_key_id='minioadmin', aws_secret_access_key='minioadmin', ) # Все операции идентичны S3 s3.put_object(Bucket='my-bucket', Key='data.json', Body=json.dumps(data))
3.4 Google Cloud Storage (GCS)
GCS предоставляет аналогичную S3 модель с несколькими отличиями:
| Характеристика | S3 | GCS |
|---|---|---|
| Контейнер | Bucket | Bucket |
| Consistency | Strong (с 2020) | Strong (изначально) |
| Классы хранения | 7 уровней | 4 уровня (Standard, Nearline, Coldline, Archive) |
| Минимальный срок хранения | Нет (Standard) | 30 дней (Nearline), 90 дней (Coldline), 365 дней (Archive) |
| IAM | IAM + Bucket Policy + ACL | Uniform IAM (рекомендуется) |
| Совместимость | S3 API | JSON/XML API + S3-compatible |
| Интеграция | Весь AWS-экосистема | BigQuery, Dataflow, AI Platform |
Autoclass — уникальная функция GCS, автоматически перемещающая объекты между классами на основе реального паттерна доступа без необходимости писать lifecycle rules.
4 Data Lake vs Data Warehouse
По мере роста объёмов данных организации сталкиваются с вопросом: как хранить и анализировать данные из десятков источников? Два фундаментально разных подхода — Data Warehouse и Data Lake — предлагают разные ответы на этот вопрос.
4.1 Data Warehouse
Data Warehouse — централизованное хранилище структурированных данных, оптимизированное для аналитических запросов (OLAP). Данные перед загрузкой проходят через ETL-процесс: очищаются, трансформируются и приводятся к единой схеме.
Data Warehouse: Schema-on-Write
Источники данных ETL Pipeline Data Warehouse
(структурированные данные)
┌──────────┐ ┌──────────────────────┐
│ CRM │──┐ │ Факты (Facts): │
└──────────┘ │ ┌───────────────┐ │ ┌────────────────┐ │
┌──────────┐ │──────│ Extract │ │ │ orders │ │
│ ERP │──┤ │ Transform │────────────▶│ │ transactions │ │
└──────────┘ │ │ Load (ETL) │ │ │ page_views │ │
┌──────────┐ │ └───────────────┘ │ └────────────────┘ │
│ Logs │──┘ │ Измерения (Dims): │
└──────────┘ Очистка, нормализация, │ ┌────────────────┐ │
валидация, агрегация │ │ customers │ │
│ │ products │ │
│ │ dates │ │
│ └────────────────┘ │
└──────────────────────┘
Schema-on-Write: данные приводятся к схеме при загрузке. Если данные не соответствуют схеме — они отклоняются или трансформируются. Это гарантирует, что все данные в warehouse чистые и согласованные.
Характеристики:
- Структурированные данные (таблицы, колонки, типы)
- SQL-доступ
- Columnar storage (оптимизировано для аналитических запросов:
SELECT AVG(amount) FROM orders WHERE year = 2025) - Высокая стоимость хранения, но быстрые запросы
- Примеры: Snowflake, Amazon Redshift, Google BigQuery, Azure Synapse, ClickHouse
4.2 Data Lake
Data Lake — хранилище для данных любого формата (structured, semi-structured, unstructured) в «сыром» виде. Данные загружаются как есть, а структура применяется при чтении.
Data Lake: Schema-on-Read
Источники данных Ingestion Data Lake
(любой формат) (сырые данные)
┌──────────┐ │ s3://data-lake/ │ │ │
│ CRM │──┐ │ ├── raw/ │ │ │ │ │
└──────────┘ │ ┌──────────────────┐ │ │ ├── crm/events.json │ │ │ │ │ │
┌──────────┐ │────│ Minimal ETL │──────────▶│ │ ├── logs/2025- │01/*.gz │ │ │ │ │ │ │ │
│ IoT │──┤ │ (или просто │ │ │ ├── iot/sensor_*.avro │ │ │ │ │ │ │ │
└──────────┘ │ │ копирование) │ │ │ └── images/*.jpg │ │ │ │ │
┌──────────┐ │ └──────────────────┘ │ ├── processed/ │ │ │ │ │
│ Logs │──┤ │ │ ├── events_cleaned/ │ │ │ │ │ │
└──────────┘ │ │ │ └── features/ │ │ │
┌──────────┐ │ │ └── curated/ │ │ │ │ │
│ Images │──┘ │ ├── dim_customers/
│ │ │ │
└──────────┘ │ └── fact_orders/
Зоны:
- Raw: данные как есть (без изменений)
- Processed: очищенные, обогащённые данные
- Curated: данные, готовые для аналитики (по сути — мини-warehouse)
Schema-on-Read: данные хранятся без схемы. Схема применяется при чтении — каждый потребитель интерпретирует данные так, как ему нужно.
Характеристики:
- Любой формат: JSON, CSV, Parquet, Avro, ORC, изображения, видео, бинарные файлы
- Масштабируется дёшево (обычно на object storage — S3, GCS)
- Поддерживает batch и real-time ingestion
- Требует governance, иначе превращается в «data swamp»
- Примеры: S3 + Athena, Databricks, Azure Data Lake Storage
4.3 Сравнение
| Характеристика | Data Warehouse | Data Lake |
|---|---|---|
| Тип данных | Только структурированные | Любые (structured, semi, unstructured) |
| Схема | Schema-on-Write | Schema-on-Read |
| Формат | Проприетарный (columnar) | Открытый (Parquet, ORC, JSON, CSV) |
| Пользователи | Бизнес-аналитики, BI-инструменты | Data Scientists, ML-инженеры |
| Стоимость хранения | Высокая ($5–25/ТБ/мес) | Низкая ($23/ТБ/мес на S3) |
| Скорость запросов | Быстрая (оптимизировано) | Зависит от формата и движка |
| Governance | Встроена (типы, ограничения) | Требуется отдельно |
| Подготовка данных | Перед загрузкой (ETL) | При чтении (ELT) |
4.4 Data Lakehouse
Современный тренд — Data Lakehouse, объединяющий достоинства обоих подходов: дешёвое хранение Data Lake с транзакционной семантикой и производительностью Data Warehouse.
Data Lakehouse:
┌───────────────────────────────────────────────┐
│ Query Engine (Spark, Presto, Trino) │
├───────────────────────────────────────────────┤
│ Table Format Layer │
│ (Delta Lake / Apache Iceberg / Apache Hudi) │
│ │
│ - ACID транзакции поверх файлов │
│ - Schema enforcement & evolution │
│ - Time travel (версионирование) │
│ - Partition pruning, Z-ordering │
│ - Merge/Update/Delete на файлах │
├───────────────────────────────────────────────┤
│ Object Storage (S3 / GCS / ADLS) │
│ Формат: Parquet / ORC │
└───────────────────────────────────────────────┘
Delta Lake, Apache Iceberg, Apache Hudi — open table formats, которые добавляют ACID-транзакции, schema enforcement и time travel поверх обычных файлов в object storage. Это позволяет использовать S3 как «Data Warehouse» с SQL-доступом, не платя за отдельное DWH-решение.
# Пример: Delta Lake (PySpark) from delta.tables import DeltaTable # Запись данных с ACID-гарантиями df.write.format("delta").mode("overwrite").save("s3://lake/orders") # Time travel: чтение данных на определённый момент df_yesterday = ( spark.read.format("delta") .option("timestampAsOf", "2025-01-14") .load("s3://lake/orders") ) # MERGE (upsert) — обновление существующих + вставка новых delta_table = DeltaTable.forPath(spark, "s3://lake/orders") delta_table.alias("target").merge( new_data.alias("source"), "target.order_id = source.order_id" ).whenMatchedUpdateAll().whenNotMatchedInsertAll().execute()
Когда что выбирать:
- Data Warehouse — если нужен быстрый SQL-доступ для BI-аналитиков, данные структурированы, объём умеренный (до десятков ТБ).
- Data Lake — если данные разнообразного формата, объёмы большие (петабайты), основные потребители — Data Science / ML.
- Data Lakehouse — если нужны преимущества обоих подходов: дешёвое хранение + ACID + SQL + ML. Современный стандарт.
5 Стратегии партиционирования данных
Партиционирование (partitioning) — разделение большого набора данных на меньшие части (partitions) по определённому правилу. Это фундаментальный механизм масштабирования любого хранилища — от баз данных до Data Lake.
Партиционирование отличается от шардирования (sharding) контекстом: шардирование — это распределение данных между разными серверами, а партиционирование — логическое разделение данных внутри одной системы. На практике эти термины часто используются взаимозаменяемо.
5.1 Горизонтальное vs Вертикальное партиционирование
Горизонтальное партиционирование (по строкам):
Строки таблицы делятся на группы по значению одного или нескольких столбцов.
Таблица orders (10 млрд строк):
Горизонтальное партиционирование по дате:
Partition 2025-01: orders WHERE created_at BETWEEN '2025-01-01' AND '2025-01-31'
Partition 2025-02: orders WHERE created_at BETWEEN '2025-02-01' AND '2025-02-28'
Partition 2025-03: orders WHERE created_at BETWEEN '2025-03-01' AND '2025-03-31'
...
Запрос: SELECT * FROM orders WHERE created_at = '2025-03-15'
→ Сканируется ТОЛЬКО Partition 2025-03 (partition pruning)
→ Вместо 10 млрд строк — ~300 млн (ускорение в 30+ раз)
Вертикальное партиционирование (по столбцам):
Столбцы таблицы разделяются на группы. Часто используемые столбцы хранятся отдельно от редко используемых.
Таблица users:
┌────┬──────┬───────┬────────────────────┬──────────────┐
│ id │ name │ email │ bio (TEXT, ~5 KB) │ avatar (URL) │
└────┴──────┴───────┴────────────────────┴──────────────┘
Вертикальное разделение:
Partition A (горячие данные): Partition B (холодные данные):
┌────┬──────┬───────┐ ┌────┬────────────────────┬──────────────┐
│ id │ name │ email │ │ id │ bio (TEXT, ~5 KB) │ avatar (URL) │
└────┴──────┴───────┘ └────┴────────────────────┴──────────────┘
Размер строки: ~100 байт Размер строки: ~5 КБ
Запрос: SELECT name, email FROM users WHERE id = 123
→ Читает ТОЛЬКО Partition A (в 50 раз меньше данных per row)
В контексте columnar storage (Parquet, ORC) вертикальное партиционирование происходит автоматически — каждый столбец хранится отдельно, и при чтении загружаются только запрошенные столбцы.
5.2 Стратегии горизонтального партиционирования
Range Partitioning (по диапазону):
Данные разделяются по диапазону значений ключа партиционирования.
-- PostgreSQL: range partitioning CREATE TABLE orders ( order_id BIGINT, created_at TIMESTAMP, amount DECIMAL(10, 2), user_id BIGINT ) PARTITION BY RANGE (created_at); CREATE TABLE orders_2025_01 PARTITION OF orders FOR VALUES FROM ('2025-01-01') TO ('2025-02-01'); CREATE TABLE orders_2025_02 PARTITION OF orders FOR VALUES FROM ('2025-02-01') TO ('2025-03-01'); CREATE TABLE orders_2025_03 PARTITION OF orders FOR VALUES FROM ('2025-03-01') TO ('2025-04-01');
Преимущества:
- Эффективные range-запросы (
WHERE created_at BETWEEN ... AND ...) - Partition pruning: запрос к одному месяцу сканирует только одну partition
- Простое архивирование: старые партиции можно отсоединить (detach) или перенести на холодное хранилище
Недостатки:
- Неравномерное распределение (hotspot): если 80% запросов идут к текущему месяцу — одна партиция перегружена
- Нужно создавать новые партиции заранее (или использовать auto-partitioning)
List Partitioning (по списку значений):
-- PostgreSQL: list partitioning CREATE TABLE orders ( order_id BIGINT, region TEXT, amount DECIMAL(10, 2) ) PARTITION BY LIST (region); CREATE TABLE orders_eu PARTITION OF orders FOR VALUES IN ('DE', 'FR', 'ES', 'IT', 'NL'); CREATE TABLE orders_us PARTITION OF orders FOR VALUES IN ('US'); CREATE TABLE orders_asia PARTITION OF orders FOR VALUES IN ('JP', 'CN', 'KR', 'IN');
Когда использовать: данные естественно группируются по дискретным категориям (регион, тип, статус), и запросы часто фильтруют по этим категориям.
Hash Partitioning (по хэшу):
Данные распределяются по partition на основе хэша ключа. Обеспечивает равномерное распределение.
-- PostgreSQL: hash partitioning CREATE TABLE events ( event_id BIGINT, user_id BIGINT, event_type TEXT, payload JSONB ) PARTITION BY HASH (user_id); CREATE TABLE events_p0 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 0); CREATE TABLE events_p1 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 1); CREATE TABLE events_p2 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 2); CREATE TABLE events_p3 PARTITION OF events FOR VALUES WITH (MODULUS 4, REMAINDER 3);
Преимущества:
- Равномерное распределение данных (нет hotspot)
- Эффективные точечные запросы (
WHERE user_id = 123→ попадает в одну partition)
Недостатки:
- Range-запросы неэффективны (запрос по диапазону user_id сканирует все partition)
- Добавление партиций требует перераспределения данных (consistent hashing решает частично)
Composite Partitioning (комбинированное):
-- PostgreSQL: composite partitioning (range + hash) CREATE TABLE events ( event_id BIGINT, user_id BIGINT, created_at TIMESTAMP ) PARTITION BY RANGE (created_at); CREATE TABLE events_2025_01 PARTITION OF events FOR VALUES FROM ('2025-01-01') TO ('2025-02-01') PARTITION BY HASH (user_id); CREATE TABLE events_2025_01_p0 PARTITION OF events_2025_01 FOR VALUES WITH (MODULUS 4, REMAINDER 0); -- ...
Первый уровень — range по дате (для partition pruning и архивирования), второй уровень — hash по user_id (для равномерного распределения внутри каждого месяца).
5.3 Партиционирование в Data Lake
В Data Lake партиционирование реализуется через структуру каталогов в object storage:
s3://data-lake/events/
├── year=2025/
│ ├── month=01/
│ │ ├── day=01/
│ │ │ ├── part-00000.parquet (150 MB)
│ │ │ ├── part-00001.parquet (150 MB)
│ │ │ └── part-00002.parquet (150 MB)
│ │ ├── day=02/
│ │ │ └── ...
│ │ └── day=31/
│ │ └── ...
│ ├── month=02/
│ │ └── ...
│ └── month=12/
│ └── ...
└── year=2024/
└── ...
Query engine (Athena, Spark, Presto) автоматически распознаёт Hive-style partitioning (key=value/) и применяет partition pruning:
-- Athena / Presto: автоматический partition pruning SELECT user_id, event_type, COUNT(*) FROM events WHERE year = 2025 AND month = 3 AND day = 15 GROUP BY user_id, event_type; -- Читает ТОЛЬКО файлы из s3://data-lake/events/year=2025/month=03/day=15/ -- Остальные партиции (терабайты) полностью игнорируются
# PySpark: запись с партиционированием df.write \ .partitionBy("year", "month", "day") \ .format("parquet") \ .mode("append") \ .save("s3://data-lake/events/")
Выбор ключа партиционирования для Data Lake:
| Паттерн запросов | Ключ партиционирования | Почему |
|---|---|---|
| Аналитика по дням/месяцам | year/month/day | 99% запросов фильтруют по дате |
| Мультитенант | tenant_id/year/month | Изоляция данных, чтение только своих |
| Логи по сервисам | service_name/date | Быстрый поиск по конкретному сервису |
| Гео-данные | country/region/city | Фильтрация по географии |
Типичные ошибки партиционирования Data Lake:
-
Слишком мелкие партиции. Тысячи файлов по 1 КБ вместо нескольких файлов по 100 МБ. S3 LIST операции становятся bottleneck, query engine тратит больше времени на открытие файлов, чем на чтение данных. Оптимальный размер файла: 100 МБ — 1 ГБ.
-
Слишком много уровней партиционирования.
year/month/day/hour/minute/service/region→ миллионы каталогов, большинство содержат один файл. Обычно достаточно 2–3 уровней. -
High cardinality ключ. Партиционирование по
user_idпри миллионах пользователей → миллионы каталогов. Используйте hash-bucketing вместо партиционирования для high cardinality ключей.
5.4 Форматы хранения данных
Выбор формата хранения критически влияет на производительность запросов, стоимость хранения и совместимость с инструментами.
Row-oriented vs Column-oriented:
Row-oriented (CSV, JSON, Avro):
┌───────────────────────────────────────────────┐
│ Row 1: id=1, name="Alice", age=30, city="NYC" │
│ Row 2: id=2, name="Bob", age=25, city="SF" │
│ Row 3: id=3, name="Carol", age=35, city="NYC" │
└───────────────────────────────────────────────┘
Хорошо для: INSERT, полное чтение строки, OLTP
Плохо для: SELECT AVG(age) — нужно читать ВСЕ столбцы каждой строки
Column-oriented (Parquet, ORC):
┌──────────────────────────────────────────┐
│ Column "id": [1, 2, 3] │
│ Column "name": ["Alice", "Bob", "Carol"] │
│ Column "age": [30, 25, 35] │
│ Column "city": ["NYC", "SF", "NYC"] │
└──────────────────────────────────────────┘
Хорошо для: SELECT AVG(age) — читает ТОЛЬКО столбец "age"
Хорошо для: сжатие (одинаковые типы данных в одном блоке)
Плохо для: INSERT одной строки, полное чтение строки
Сравнение форматов:
| Формат | Тип | Схема | Сжатие | Splitting | Типичное использование |
|---|---|---|---|---|---|
| CSV | Row | Нет | Плохое | Да | Простой обмен данными |
| JSON | Row | Нет (semi-structured) | Среднее | Да (JSONL) | API, логи, конфигурации |
| Avro | Row | Встроена (schema evolution) | Хорошее | Да | Kafka, streaming, ETL |
| Parquet | Column | Встроена | Отличное | Да | Аналитика, Data Lake |
| ORC | Column | Встроена | Отличное | Да | Hive, аналитика |
Apache Parquet — наиболее распространённый columnar-формат для Data Lake:
Parquet файл:
┌───────────────────────────────────────────┐
│ Row Group 1 (обычно 128 МБ) │
│ ┌──────────────────────────────────────┐ │
│ │ Column Chunk: "id" │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Page 1 │ │ Page 2 │ │ │
│ │ │ (values) │ │ (values) │ │ │
│ │ └──────────┘ └──────────┘ │ │
│ └──────────────────────────────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ Column Chunk: "name" │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Page 1 │ │ Page 2 │ │ │
│ │ └──────────┘ └──────────┘ │ │
│ └──────────────────────────────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ Column Chunk: "age" │ │
│ │ ┌──────────┐ │ │
│ │ │ Page 1 │ (min=20, max=40) │ │ ← Statistics
│ │ └──────────┘ │ │
│ └──────────────────────────────────────┘ │
├───────────────────────────────────────────┤
│ Row Group 2 │
│ ... │
├───────────────────────────────────────────┤
│ Footer: │
│ - Schema │
│ - Row Group offsets │
│ - Column Chunk offsets │
│ - Min/Max statistics per column chunk │
└───────────────────────────────────────────┘
Parquet хранит min/max статистику для каждого column chunk — это позволяет query engine пропускать row groups, не содержащие нужных значений (predicate pushdown). Например, если запрос WHERE age > 50, а row group имеет max(age) = 40, весь row group пропускается без чтения.
6 Паттерны хранения в реальных системах
Netflix: мультимодальное хранилище
Netflix использует различные типы хранилищ для разных задач:
- S3 — основное хранилище для видео-контента. Каждый фильм хранится в десятках вариантов (разрешения, кодеки, аудиодорожки, субтитры). Общий объём — петабайты.
- Cassandra — метаданные пользователей, история просмотров, персонализация. Выбрана за write throughput и линейную масштабируемость.
- EVCache (Memcached) — кэш для горячих данных (см. главу 6).
- CockroachDB — для данных, требующих строгой консистентности (подписки, биллинг).
- Data Lake на S3 — Parquet-файлы для аналитики, ML-пайплайнов и A/B тестирования. Обрабатывается через Apache Spark.
Uber: от монолита к гетерогенному хранению
Uber прошёл путь от одного PostgreSQL до десятков систем хранения:
- Schemaless (собственная разработка) → DocStore — основное хранилище для trips, users, payments. Append-only, immutable storage поверх MySQL.
- Apache Kafka — event log для всех событий (поездки, платежи, GPS-точки). Петабайты данных в день.
- Apache Hudi на S3 — Data Lakehouse для аналитики. Uber — один из создателей Apache Hudi.
- Elasticsearch — поиск по текстовым данным (рестораны, водители, адреса).
- Redis — кэширование и real-time данные (геолокация водителей).
- HDFS + Spark — batch-аналитика и ML-пайплайны.
Airbnb: от единой БД к Data Lake
Airbnb начинал с MySQL, но по мере роста перешёл на гетерогенную архитектуру:
- MySQL — основные данные (listings, users, bookings). Шардирование по geographic region.
- Amazon S3 + Hive → Apache Iceberg — Data Lake для аналитики (Airbnb — создатели Apache Iceberg).
- Elasticsearch — поиск listings с geo-фильтрами.
- Redis — кэш для поисковых запросов и session data.
- Apache Druid — real-time аналитика (мониторинг, dashboards).
7 Практические рекомендации
Чек-лист при проектировании хранения данных
-
Определите тип данных. Структурированные (SQL) → реляционная БД / DWH. Полуструктурированные (JSON, logs) → NoSQL / Data Lake. Неструктурированные (медиа, бинарные файлы) → Object Storage.
-
Оцените объём. Гигабайты → одна БД / один сервер. Терабайты → шардирование / партиционирование. Петабайты → распределённые системы (HDFS, S3, Data Lake).
-
Определите паттерн доступа. Random read/write с низкой латенси → Block Storage + БД. Sequential read/write (batch) → HDFS / Object Storage. Append-only (логи, события) → Object Storage + partitioning по времени.
-
Выберите формат хранения. OLTP → row-oriented (JSONB, Avro). OLAP → column-oriented (Parquet, ORC). Оба → раздельные хранилища или Lakehouse.
-
Спроектируйте партиционирование. Выберите ключ на основе паттерна запросов. Date-based для time-series и логов. Hash-based для равномерного распределения. Composite для сложных запросов.
-
Планируйте lifecycle. Горячие данные → SSD / Standard Storage. Тёплые данные (30+ дней) → IA / Nearline. Холодные данные (90+ дней) → Glacier / Archive. Автоматизируйте через lifecycle policies.
-
Обеспечьте durability. Критические данные → репликация в несколько AZ + versioning. Бэкапы → отдельный регион. Object Storage с 11-девятками durability — для данных, потеря которых недопустима.
-
Мониторинг. Ключевые метрики:
- Storage utilization — используемое vs доступное пространство
- I/O throughput (IOPS, MB/s) — для Block и File Storage
- Request latency (p50, p99) — для Object Storage
- Cost per TB — отслеживайте, чтобы вовремя мигрировать на дешёвый storage class
- Partition skew — неравномерное распределение данных по партициям
-
Оптимизируйте стоимость. Object Storage доминирует в расходах на данные. Используйте Intelligent-Tiering или Autoclass. Удаляйте неиспользуемые данные (expiration policies). Сжимайте (Parquet + Snappy/Zstd экономит 5–10x по сравнению с CSV).
8 Вопросы для самопроверки
-
Объясните разницу между Block Storage, File Storage и Object Storage. Приведите по два сценария использования для каждого типа.
-
Ваша компания обрабатывает 50 ТБ логов в день. Логи нужно хранить 90 дней для анализа и 7 лет для compliance. Спроектируйте стратегию хранения с учётом стоимости.
-
Почему HDFS плохо работает с миллионами мелких файлов (small files problem)? Предложите два способа решения.
-
Сравните Data Lake и Data Warehouse. Когда вы порекомендуете Lakehouse-подход? Приведите пример конкретной компании и задачи.
-
Вы проектируете хранилище для медиа-платформы: 10 миллионов видео, средний размер 500 МБ, 1 миллион новых загрузок в день. Какой тип хранилища вы выберете? Как организуете lifecycle?
-
Объясните разницу между row-oriented и column-oriented форматами. Почему Parquet эффективнее CSV для аналитических запросов?
-
В вашем Data Lake партиционирование по
year/month/day/hour/user_id. Запросы работают медленно. Что не так и как исправить? -
Вам нужно реализовать S3-совместимое хранилище on-premise для 500 ТБ данных. Какую технологию вы выберете и почему? Как обеспечите отказоустойчивость?
-
Чем erasure coding отличается от репликации? Когда каждый подход предпочтительнее? Приведите расчёт overhead для replication factor 3 vs erasure coding (8+4).
-
Вы переводите Data Warehouse (Redshift, 20 ТБ) на Lakehouse (Iceberg + S3 + Spark). Какие trade-offs вы видите? Что может пойти не так?
9 Дополнительные ресурсы
Papers
- The Google File System (Ghemawat, Gobioff, Leung, 2003) — оригинальная статья, описывающая архитектуру GFS, которая легла в основу HDFS. Фундаментальная работа по распределённым файловым системам.
- Amazon Aurora: Design Considerations for High Throughput Cloud-Native Relational Databases (Verbitski et al., 2017) — как Amazon переосмыслил хранение данных для облачных БД, отделив compute от storage.
- Delta Lake: High-Performance ACID Table Storage over Cloud Object Stores (Armbrust et al., Databricks, 2020) — архитектура Delta Lake, добавляющей ACID-транзакции поверх Parquet на S3.
- Apache Iceberg: A Table Format for Large Analytic Datasets (2020) — open table format от Netflix/Apple, ставший стандартом для Data Lakehouse.
- Dremel: Interactive Analysis of Web-Scale Datasets (Melnik et al., Google, 2010) — вдохновение для Apache Parquet и columnar-форматов.
Блоги
- Netflix Tech Blog (netflixtechblog.com) — статьи об архитектуре хранения данных Netflix, включая миграцию на Apache Iceberg и использование S3.
- Uber Engineering Blog (eng.uber.com) — серия статей о создании Apache Hudi и архитектуре Data Lake в Uber.
- AWS Architecture Blog (aws.amazon.com/blogs/architecture) — best practices по использованию S3, EBS, EFS и проектированию Data Lake.
- Databricks Blog (databricks.com/blog) — статьи о Delta Lake, Lakehouse-архитектуре и оптимизации Spark.
- MinIO Blog (blog.min.io) — бенчмарки, архитектура и сравнения с S3.
Talks
- "Building a Data Lake with S3 and Apache Iceberg" (Ryan Blue, Tabular, Subsurface) — практическое руководство по созданию Data Lakehouse с Apache Iceberg.
- "The Future of Data Storage" (Andy Jassy, AWS re:Invent) — тренды в облачном хранении данных.
- "HDFS Deep Dive" (Hadoop Summit) — внутренности HDFS, NameNode HA и Federation.
- "Parquet at Twitter" (Julien Le Dem, Strata) — как Twitter выбрал и оптимизировал Parquet для аналитики на петабайтных объёмах данных.