Multipart-экспорты и большие выгрузки
1) Когда нужны «большие» экспорты и что важно
Сценарии: финансовые отчеты, выгрузка активности пользователей, аудит/регуляторы, BI-выгрузки, партнерские каталоги, резервные копии. Ключевые требования:- Согласованность данных (snapshot/точка во времени).
- Проходимость по объему (параллельная запись/чтение, потоковая сериализация).
- Возобновляемость (resumable) и частичная доставка.
- Целостность (checksum) и верифицируемость (манифест).
- Безопасность/PII (маскирование, шифрование, контроль доступа).
- Управление стоимостью (компрессия, тайм-ауты, CDN, TTL).
2) Форматы данных: плюсы/минусы
CSV — компактно, быстро писать/читать; минусы: экранирование, типы теряются. Хорошо для табличных отчетов.
JSON Lines (JSONL) — по строке на объект, удобен для стриминга и частичной выборки; минусы: объем.
Parquet/Avro — колоночные/схемные форматы, сжатие и predicate pushdown; идеальны для аналитики и больших данных.
Mixed: JSONL для API-загрузки → оффлайн-конверсия в Parquet.
Компрессия: `gzip`/`zstd` (лучше). Для очень больших объемов — сплит-архивы (по ~128–512 МБ на часть).
3) Согласованность: как получить «снимок»
БД: транзакционная изоляция REPEATABLE READ/SNAPSHOT; для потоков — логические слоты репликации или отметка `watermark` (макс. `updated_at`/версия).
Event sourcing: экспорт по offset журнала.
Срезы: «полный» экспорт + «дельты» (последующие выгрузки изменений с момента `watermark`).
4) Разбиение на части (multipart/chunking)
4.1 Виды «multipart»
Upload (к нам): multipart/form-data (мелкие файлы), S3 Multipart Upload (MPU) / GCS Resumable (крупные).
Download (от нас): HTTP Range (`bytes=start-end`), `multipart/byteranges` (несколько диапазонов в одном ответе), «zip of parts», каталоги в объектном сторе.
4.2 Стратегии разбиения
По размеру (например, 256 МБ на часть).
По ключу/дате (шардирование по `tenant_id`, `YYYY/MM/DD`).
По таблице/сущности (отдельные файлы на типы).
Баланс: части 64–512 МБ хорошо скачиваются параллельно и не перегревают память.
5) API-архитектура экспорта (асинхронная модель)
Шаги:1. `POST /exports` → задание в очереди (метаданные: формат, фильтры, шифрование, срок жизни).
2. Воркеры строят snapshot, стримят данные и пишут части в объектное хранилище.
3. Генерируют манифест (JSON) с перечнем частей, размерами, checksum, версией схемы.
4. `GET /exports/{id}` возвращает статус и ссылку(и) на части / pre-signed URL.
5. `GET /exports/{id}/manifest.json` — машина правды для верификации/дозагрузки.
Пример манифеста:json
{
"export_id": "exp_2025_10_31_001",
"created_at": "2025-10-31T14:23:00Z",
"schema": "orders_v3",
"format": "parquet+zstd",
"parts": [
{"name":"part-00000. parquet. zst","size":268435456,"sha256":"...","url":"...","range":"bytes=0-268435455"},
{"name":"part-00001. parquet. zst","size":241172480,"sha256":"...","url":"..."}
],
"total_bytes": 509607936,
"encryption": {"type":"AES-256-GCM","key_id":"kms/keys/exp"},
"watermark": {"type":"updated_at","value":"2025-10-31T00:00:00Z"}
}
6) Возобновляемые выгрузки (resumable)
HTTP Range: клиент догружает «хвост» файла: `Range: bytes=241172480-`.
Несколько диапазонов: `Range: bytes=0-999,2000-2999` → ответ `Content-Type: multipart/byteranges`.
Клиентская стратегия: параллельные «воркеры» на части, верификация `sha256` каждой, ретраи с экспоненциальным backoff.
CDN: поддержка Range, выключенная буферизация больших ответов.
7) Большие загрузки к нам (resumable upload)
S3 Multipart Upload: клиенты загружают части (5–5000), сервер собирает `CompleteMultipartUpload`.
GCS Resumable: один сеанс, смещения — клиент может продолжать с `Content-Range`.
TUS (протокол) — независимый возобновляемый аплоад поверх HTTP.
Паттерн B2B: отдаем pre-signed URL для аплоада частей напрямую в стор, а метаданные — в наш API.
8) Компрессия, шифрование, целостность
Компрессия: `zstd` предпочтителен (лучше ratio/скорость). Сжимайте каждую часть отдельно (удобнее возобновлять/кэшировать).
Шифрование:- На проводе: TLS 1.2+.
- В покое: server-side KMS (SSE-KMS) или client-side (AES-256-GCM) с оберткой ключа (key wrapping).
- Никогда не кладите «сырой» ключ в манифест.
- Checksum: минимум SHA-256 на часть + общий для всего набора. Проверяйте на клиенте перед ack.
9) Интеграция с периметром: NGINX/CDN
NGINX (Range + большие таймауты + отключить буферизацию):nginx server {
listen 443 ssl http2;
server_name downloads. example. com;
location /exports/ {
proxy_buffering off;
proxy_request_buffering off;
proxy_read_timeout 3600s;
add_header Accept-Ranges bytes;
proxy_pass http://export-backend;
}
}
Заголовки ответов:
- `Content-Disposition: attachment; filename="export_2025-10-31_part-00000.parquet.zst"`
- `ETag`/`If-Range` для корректной догрузки.
- `Cache-Control` (например, `private, max-age=3600`) для персональных выгрузок.
10) Безопасность и комплаенс
Аутентификация/авторизация: выдача экспортов только владельцу/ролям; временные ссылки (pre-signed) с коротким TTL.
PII: маскирование/псевдонимизация; в манифесте — только технические поля.
GDPR/локальные регуляторы: удаление экспортов по TTL, аудит скачиваний, запрет кросс-региональной выдачи без основания.
Rate limiting & quotas: ограничить количество одновременных экспортов и общий объем в сутки/месяц (per-tenant).
Анти-скрапинг: CAP/бот-фильтры на выдачу ссылок, ограничение диапазонов (макс. параллельных частей).
11) Наблюдаемость и эксплуатация
Метрики:- `export_jobs_total{status}` (queued/running/succeeded/failed/expired)
- `export_bytes_total`, `export_part_duration_ms{p50,p95,p99}`
- `download_range_requests_total`, `resumes_total`, `checksum_fail_total`
- `storage_cost_estimate` и `egress_bytes_cdn`
- Кто создал экспорт, фильтры, watermark, список скачиваний (IP/UA/время).
- Хеши частей и сверка на стороне клиента (подтверждения).
- Спаны на этапы: snapshot → serialize → upload part → finalize.
12) Производительность и стоимость
Параллельность: генерируйте несколько частей одновременно (N воркеров), но лимитируйте I/O.
Память: потоковая сериализация (итераторы, курсоры БД, чанки по 4–16 МБ).
Дедуп: для часто повторяющихся экспортов умный cache of parts по фильтрам/хешу.
CDN: выгодно для «общих» публичных наборов; для персональных — осторожно (безопасность/PII).
13) Примеры интерфейсов
13.1 Создание экспорта (REST)
http
POST /exports
Content-Type: application/json
Authorization: Bearer <token>
{
"format": "parquet+zstd",
"filters": {"date_from":"2025-10-01","date_to":"2025-10-31","tenant":"acme"},
"split_size_mb": 256,
"encryption": {"mode":"server-side-kms","key_id":"kms/keys/exp"}
}
Ответ:
json
{
"id":"exp_2025_10_31_001",
"status":"queued",
"estimated_parts": 12,
"manifest_url": "/exports/exp_2025_10_31_001/manifest. json"
}
13.2 Выдача части с Range
http
GET /exports/exp_.../part-00003. parquet. zst
Range: bytes=1048576-
13.3 Псевдокод воркера
pseudo snapshot = db. begin_snapshot()
for shard in plan_shards(snapshot):
part_stream = encode_stream(shard. rows, format="parquet", compress="zstd")
url = object_store. upload_stream(part_stream, part_name, encryption=KMS)
manifest. add(part_name, size, sha256, url)
write_manifest(manifest)
14) Паттерны дельта-экспортов
Полный экспорт раз в N (день/неделя) + дельты каждый час по `updated_at > watermark`.
На стороне потребителя: применить дельты по порядку, верифицируя `version`/`seq`.
Храните последний watermark в потребителе и в манифесте.
15) Анти-паттерны
Генерация экспорта в запросе (синхронно) — таймауты и OOM.
Один гигантский файл без разбиения — невозможность возобновления/параллельной закачки.
Отсутствие checksum и манифеста — нельзя доказать целостность.
Выдача постоянных публичных ссылок на персональные данные.
Буферизация SSE/CDN или отключенные Range — ломает «дозагрузку».
Экспорт «грязных» данных (без snapshot/изоляции).
16) Чек-лист внедрения
- Формат и компрессия: CSV/JSONL/Parquet + `zstd/gzip`.
- Согласованность: транзакционный snapshot или watermark/offset.
- Разбиение: части 64–512 МБ, параллельная генерация и скачивание.
- Манифест: список частей, размеры, SHA-256, версия схемы, watermark.
- Возобновление: HTTP Range, поддержка `multipart/byteranges`, клиентские ретраи.
- Безопасность: pre-signed URLs, TTL, шифрование (KMS/AEAD), маскирование PII.
- Лимиты/квоты: per-tenant, суточные объемы, число активных джоб.
- Наблюдаемость: метрики джоб/частей, аудит скачиваний, алерты на checksum-fail.
- Стоимость: CDN для публичных наборов, TTL и авто-cleanup в сторе.
- Runbooks/Game Days: обрывы сети, недоступность стора, перегрев БД, сбой KMS.
17) Game Days (плейбуки)
Обрыв сети при скачивании: клиент должен продолжить с `Range`.
Сбой загрузки одной части: ретрай части без пересоздания всего экспорта.
Падение воркера: джоба возобновляется с последнего неподтвержденного чанка.
Недоступен KMS: безопасная деградация (пауза генерации, не выпускать незашифровано).
Рост данных ×2: проверить время генерации, перераспределить параллелизм, не убить БД.
18) Итоги
Надежные большие выгрузки — это асинхронная архитектура + разбиение на части + возобновление + проверяемая целостность. Делайте snapshot, пишите части параллельно, публикуйте манифест с checksum, поддерживайте HTTP Range и короткоживущие ссылки. Продумайте безопасность, лимиты и наблюдаемость — и гигабайтные экспорты перестанут быть ночным кошмаром для команд и пользователей.