На интервью на лида важно не начать с “загружаем файл в S3”, а показать, что
система загрузки файлов — это не просто upload endpoint, а контур: приём
файла, хранение, метаданные, права доступа, проверка, обработка, ретраи,
безопасность, лимиты, lifecycle, скачивание и наблюдаемость.
Хороший ответ можно строить так.
1. Сначала уточняю требования
Я бы начал с вопросов:
“Сначала я уточню характер файлов и требования к загрузке, потому что
загрузка аватарок, банковских документов, видео и медицинских файлов — это
разные системы.”
Я бы спросил:
- Какие файлы загружаем: изображения, PDF, видео, архивы, документы?
- Максимальный размер файла?
- Средний размер файла?
- Сколько загрузок в день / час / секунду?
- Нужна ли resumable upload, то есть продолжение загрузки после обрыва?
- Нужна ли multipart upload для больших файлов?
- Кто имеет доступ к файлам?
- Файлы публичные или приватные?
- Нужна ли антивирусная проверка?
- Нужна ли обработка после загрузки: thumbnails, transcoding, OCR, parsing?
- Нужно ли хранить версии файлов?
- Нужно ли удаление по TTL / lifecycle policy?
- Нужен ли audit trail?
- Какие требования по compliance: GDPR, PII, encryption, data retention?
- Нужна ли CDN-раздача?
- Нужно ли ограничивать типы файлов?
- Что считаем успешной загрузкой: файл попал в storage или файл ещё и прошёл
проверку?
И дальше можно сказать:
“Для примера я спроектирую универсальную систему загрузки приватных
пользовательских файлов размером до нескольких гигабайт, с поддержкой
больших файлов, проверкой безопасности, метаданными, асинхронной обработкой
и контролем доступа.”
2. Главная архитектурная идея
Я бы не прогонял большие файлы через backend, если в этом нет необходимости.
Плохой базовый вариант:
Client -> Backend -> File Storage
Почему плохо:
- backend становится бутылочным горлышком;
- растёт нагрузка на сеть и память;
- сложнее масштабировать;
- большие файлы могут убивать API-инстансы;
- дороже гонять трафик через приложение.
Лучше:
Client
|
| 1. request upload session
v
Backend API
|
| 2. create metadata + presigned URL
v
Object Storage
Client
|
| 3. upload file directly
v
Object Storage
Object Storage Event
|
v
Queue
|
v
Processing Workers
То есть backend управляет правами, метаданными, upload session и
состоянием, но сам файл напрямую идёт в object storage: S3, Google Cloud
Storage, Azure Blob Storage, MinIO.
3. Базовые компоненты системы
Client
|
v
Upload API
|
+--> Metadata DB
+--> Auth / Permission Service
+--> Object Storage
+--> Message Queue
|
v
Processing Workers
|
+--> Antivirus Scanner
+--> Thumbnail Generator
+--> Metadata Extractor
+--> Transcoding / OCR / Parsing
|
v
File Status Updater
Основные компоненты:
- Upload API — создаёт upload session, проверяет права, выдаёт presigned
URL. - Object Storage — хранит binary content.
- Metadata DB — хранит статус, владельца, размер, MIME type, storage key,
checksum. - Queue — запускает асинхронную обработку после загрузки.
- Workers — проверяют файл, сканируют, генерируют preview, обновляют
статус. - Download API — контролирует доступ при скачивании.
- CDN — для публичных или часто скачиваемых файлов.
- Audit / Observability — логи, метрики, tracing.
4. Основной upload flow
Шаг 1. Клиент создаёт upload session
POST /files/upload-session
Пример запроса:
{
"fileName": "contract.pdf",
"contentType": "application/pdf",
"size": 10485760,
"checksum": "sha256:abc123",
"context": {
"projectId": "project-123"
}
}
Backend проверяет:
- пользователь авторизован;
- пользователь имеет право загружать файл в этот project / workspace;
- размер не превышает лимит;
- MIME type разрешён;
- quota не превышена;
- filename безопасный;
- checksum валиден по формату.
После этого backend создаёт запись в базе:
files
- id
- owner_id
- project_id
- original_file_name
- storage_key
- content_type
- size
- checksum
- status
- created_at
- updated_at
Статус сначала:
PENDING_UPLOAD
И возвращает клиенту presigned URL:
{
"fileId": "file-789",
"uploadUrl": "https://storage-provider.com/presigned-url",
"expiresAt": "2026-06-14T21:30:00Z",
"requiredHeaders": {
"Content-Type": "application/pdf"
}
}
Шаг 2. Клиент загружает файл напрямую в object storage
Client -> Object Storage
Backend в этот момент не держит файл у себя.
Это важный архитектурный пункт:
“Я бы предпочёл direct upload to object storage через presigned URL. Backend
остаётся control plane, а object storage — data plane. Так мы разделяем
управление и передачу тяжёлых данных.”
Шаг 3. Storage сообщает о завершении загрузки
Есть два варианта.
Вариант 1. Клиент сам сообщает backend
POST /files/{fileId}/complete
Backend проверяет, что объект реально появился в storage:
HEAD object
Проверяет:
- размер;
- content type;
- checksum;
- storage key;
- upload session not expired.
Потом переводит файл в статус:
UPLOADED
И кладёт событие в очередь:
file.uploaded
Вариант 2. Object storage сам публикует событие
Например:
S3 ObjectCreated event -> Queue -> Worker
Это надёжнее для server-side обнаружения загрузки, но всё равно часто
оставляют /complete, чтобы клиент мог быстрее получить статус.
5. Статусы файла
Я бы явно заложил state machine.
PENDING_UPLOAD
UPLOADED
PROCESSING
READY
FAILED
REJECTED
DELETED
Расшифровка:
PENDING_UPLOAD — upload session создана, файл ещё не загружен
UPLOADED — файл появился в object storage
PROCESSING — идёт проверка / обработка
READY — файл доступен пользователю
FAILED — техническая ошибка обработки
REJECTED — файл запрещён: вирус, неправильный тип, policy violation
DELETED — файл удалён логически или физически
Это важно, потому что файл не должен становиться доступным сразу после
загрузки, если нужна проверка.
Хорошая фраза для интервью:
“Я бы разделил факт загрузки и факт готовности файла. Uploaded не равно
Ready. Файл может быть загружен в storage, но ещё не прошёл security scan,
preview generation или business validation.”
6. Metadata DB
Хранилище метаданных может быть PostgreSQL.
Пример таблицы:
files
- id
- owner_id
- workspace_id
- original_file_name
- normalized_file_name
- storage_bucket
- storage_key
- content_type
- detected_content_type
- size
- checksum_sha256
- status
- visibility
- created_at
- updated_at
- deleted_at
Дополнительно:
file_versions
- id
- file_id
- version
- storage_key
- size
- checksum_sha256
- created_at
file_processing_jobs
- id
- file_id
- job_type
- status
- attempt_count
- error_message
- created_at
- updated_at
file_access_audit
- id
- file_id
- user_id
- action
- ip
- user_agent
- created_at
7. Object Storage
Для binary content я бы использовал object storage:
S3 / GCS / Azure Blob / MinIO
Почему не база данных:
- файлы могут быть большими;
- object storage дешевле;
- проще lifecycle policy;
- проще CDN;
- выше durability;
- удобнее multipart upload;
- не раздуваем основную transactional DB.
Storage key лучше делать не по имени файла, а по внутреннему ID:
/workspace/{workspaceId}/files/{fileId}/original
Или:
/files/2026/06/14/{fileId}
Не стоит использовать пользовательский filename как путь, потому что:
- могут быть спецсимволы;
- могут быть коллизии;
- могут быть path traversal-попытки;
- имя файла может содержать чувствительные данные.
8. Большие файлы и multipart upload
Если файлы маленькие, достаточно простого presigned PUT.
Но для больших файлов нужен multipart upload:
1. Client requests multipart upload session
2. Backend creates multipart upload in storage
3. Backend returns presigned URLs for parts
4. Client uploads parts in parallel
5. Client calls complete multipart upload
6. Storage assembles object
7. Backend verifies object and updates status
API может быть такой:
POST /files/multipart/start
POST /files/{fileId}/multipart/parts
POST /files/{fileId}/multipart/complete
POST /files/{fileId}/multipart/abort
На интервью:
“Для больших файлов я бы использовал multipart upload с presigned URLs. Это
даёт параллельную загрузку, возможность ретраить отдельные части и не
начинать весь upload заново при сетевом сбое.”
9. Resumable upload
Если требуется продолжение загрузки после обрыва, есть варианты:
Multipart upload
TUS protocol
Custom chunk upload
Если строить своё:
upload_sessions
- id
- file_id
- user_id
- status
- total_size
- uploaded_parts
- expires_at
Клиент может спросить:
GET /files/{fileId}/upload-status
Ответ:
{
"fileId": "file-789",
"uploadedParts": [1, 2, 3, 7],
"missingParts": [4, 5, 6, 8]
}
Но на интервью хорошо сказать:
“Я бы не изобретал resumable upload без необходимости. Если object storage
уже поддерживает multipart upload, лучше использовать его механизм, а не
строить собственный протокол передачи байтов через backend.”
10. Проверка безопасности
После загрузки файл не сразу становится доступен.
Pipeline:
UPLOADED
-> PROCESSING
-> Antivirus scan
-> MIME type detection
-> File extension validation
-> Optional content validation
-> READY or REJECTED
Проверки:
- антивирус;
- реальный MIME type, а не только заголовок от клиента;
- расширение файла;
- размер;
- checksum;
- запрет executable content;
- запрет архивов с zip bomb;
- проверка количества файлов внутри архива;
- проверка path traversal внутри архивов;
- ограничение PDF/scripts/macros, если нужно;
- PII / DLP-проверки, если домен требует.
Очень важная фраза:
“Я бы не доверял content type, который прислал клиент. Его нужно считать
user input. Реальный тип файла должен определяться server-side.”
11. Обработка файлов
После security scan можно делать обработку.
Для изображений:
thumbnail generation
image resizing
EXIF extraction
EXIF cleanup
Для видео:
transcoding
preview generation
duration extraction
Для документов:
PDF preview
OCR
text extraction
indexing for search
Для аудио:
transcription
duration extraction
waveform generation
Это должно быть асинхронно:
file.uploaded -> queue -> workers -> update metadata
Если обработка тяжёлая, можно разделить очереди:
file-antivirus-queue
file-thumbnail-queue
file-ocr-queue
file-transcoding-queue
12. Доступ к файлам
Скачивание тоже не должно быть просто:
GET /files/{fileId}/download
Backend должен:
- проверить авторизацию;
- проверить права пользователя;
- проверить статус
READY; - проверить, что файл не удалён;
- проверить tenant/workspace isolation;
- создать short-lived presigned download URL.
Flow:
Client -> Download API
Download API -> Auth check
Download API -> DB metadata check
Download API -> generate presigned URL
Client -> Object Storage
На интервью:
“Для приватных файлов я бы не делал bucket публичным. Backend выдаёт
короткоживущие signed URLs только после проверки прав.”
Для публичных файлов можно использовать CDN:
CDN -> Object Storage
Но даже там можно использовать signed cookies / signed URLs, если доступ
ограниченный.
13. Авторизация и права доступа
Нужны уровни:
owner
workspace member
admin
public
shared by link
temporary access
В metadata можно хранить:
visibility:
- private
- workspace
- public
- shared_link
Отдельно можно хранить ACL:
file_permissions
- file_id
- subject_type: user / group / workspace
- subject_id
- permission: read / write / delete / owner
Но на больших системах лучше не делать слишком сложный ACL на каждый файл без
необходимости. Часто файл наследует права от сущности:
project -> task -> attachment
То есть если файл прикреплён к задаче, доступ к файлу определяется доступом к
задаче.
Хорошая фраза:
“Я бы старался не дублировать сложную permission model внутри file service.
Если файл является attachment к business entity, доступ лучше вычислять
через доступ к этой entity, а file service использовать как storage/control
layer.”
14. Идемпотентность
При загрузке файлов тоже нужна идемпотентность.
Проблемы:
- клиент повторил создание upload session;
- сеть оборвалась;
- клиент не получил ответ;
/completeвызван два раза;- storage event пришёл дважды;
- worker обработал одно событие дважды.
Поэтому:
idempotency_key
unique(owner_id, idempotency_key)
Для /complete:
if status already READY / PROCESSING / UPLOADED:
return current state
Для workers:
processing job unique(file_id, job_type)
Фраза:
“Я бы проектировал обработку как at-least-once, но идемпотентную. Storage
events и queue messages могут приходить повторно, поэтому duplicate event не
должен создавать duplicate file или duplicate processing.”
15. Consistency model
Система загрузки файлов почти всегда eventually consistent.
Например:
File uploaded to storage
Metadata updated later
Scan completed later
Preview generated later
Search index updated later
Важно честно разделить состояния.
Пользователь может видеть:
Uploading...
Processing...
Ready
Failed
Rejected
На интервью:
“Я бы не пытался делать всю цепочку синхронной. Успешный upload означает,
что файл принят системой. А доступность файла для использования наступает
после асинхронной проверки и обработки.”
16. Cleanup и orphan files
Один из важных, часто забываемых пунктов.
Проблемы:
metadata created, but file never uploaded
file uploaded, but /complete not called
multipart upload started, but never completed
processing failed and left temp files
user deleted file, but object remained in storage
Нужны cleanup jobs:
delete expired PENDING_UPLOAD sessions
abort stale multipart uploads
delete orphan objects
delete temporary processing files
apply retention policy
Например:
PENDING_UPLOAD older than 24 hours -> mark expired -> delete object if exists
multipart upload older than 24 hours -> abort
DELETED older than 30 days -> physical delete
Фраза:
“Для file upload system я бы обязательно заложил cleanup-механику. Иначе
object storage со временем превращается в кладбище orphan files.”
17. Versioning
Если нужны версии файлов:
file_id = logical file
file_version_id = physical uploaded object
Пример:
files
- id
- current_version_id
- owner_id
- status
file_versions
- id
- file_id
- version_number
- storage_key
- checksum
- size
- created_at
Тогда можно:
- откатиться на старую версию;
- хранить историю;
- сравнивать версии;
- не ломать ссылки на logical file.
Если версии не нужны, можно не усложнять MVP.
18. Deduplication
Если много одинаковых файлов, можно делать дедупликацию по checksum:
checksum_sha256
size
content_type
Но осторожно:
- нельзя раскрывать пользователю факт, что такой файл уже есть у другого
пользователя; - права доступа всё равно отдельные;
- физический объект может быть один, но logical file records разные;
- при удалении одного logical file нельзя удалить physical object, если на
него есть ссылки.
На интервью можно сказать:
“Дедупликацию я бы рассматривал как оптимизацию второго этапа. В MVP
достаточно хранить checksum для integrity check и будущей дедупликации.”
19. Rate limiting и quotas
Нужно ограничивать:
max file size
max files per user
max total storage per user/workspace
uploads per minute
bandwidth
number of active upload sessions
number of multipart parts
Иначе один пользователь может:
- забить storage;
- создать слишком много upload sessions;
- перегрузить processing workers;
- сгенерировать большой счёт за storage/egress.
20. Storage lifecycle
Нужны политики:
temporary uploads -> delete after 24h
deleted files -> hard delete after 30 days
old versions -> move to cold storage
logs/audit -> retain according to policy
processed previews -> regenerate or store
Для S3/GCS/Azure Blob можно использовать lifecycle rules.
21. CDN
Если файлы часто скачиваются или публичные:
Client -> CDN -> Object Storage
Для приватного доступа:
signed CDN URL
signed cookies
short TTL
Для аватарок, изображений, публичных документов CDN сильно снижает нагрузку
и latency.
Но для чувствительных файлов CDN нужно использовать аккуратно:
- короткий TTL;
- запрет кэширования приватных файлов без контроля;
- signed URLs;
- cache invalidation при удалении/замене.
22. Observability
Метрики:
upload_sessions_created_total
uploads_completed_total
uploads_failed_total
upload_latency
processing_latency
files_ready_total
files_rejected_total
antivirus_failures_total
queue_lag
storage_errors_total
presigned_url_generation_errors_total
orphan_files_count
multipart_abandoned_count
download_requests_total
download_denied_total
Логи:
fileId
uploadSessionId
userId
workspaceId
storageKey
status
contentType
size
checksum
correlationId
errorCode
Алерты:
processing queue lag grows
antivirus scanner unavailable
storage error rate grows
too many rejected files
orphan files grow
upload completion rate drops
download 403 spike
Фраза:
“В file upload system observability критична, потому что пользователь видит
только ‘загрузка зависла’ или ‘файл не открылся’. Без статусов, correlation
id, queue lag и истории обработки команда будет слепой.”
23. Failure scenarios
Клиент начал upload, но не закончил
PENDING_UPLOAD expires
cleanup job deletes session
multipart upload aborted
Файл загрузился, но backend не получил complete
storage event detects object
or reconciliation job checks storage
status updated server-side
Storage event пришёл дважды
idempotent handler
same fileId + storageKey
no duplicate processing
Worker упал во время обработки
message redelivery
processing job status
retry with backoff
DLQ after max attempts
Антивирус недоступен
do not mark file READY
keep PROCESSING or FAILED_TEMPORARY
retry later
alert team
Файл оказался вредоносным
status = REJECTED
quarantine or delete object
audit event
notify user if needed
Пользователь пытается скачать файл до проверки
deny download
return status PROCESSING
Presigned URL истёк
client requests new upload URL
same upload session if still valid
Пользователь загрузил файл с расширением .jpg, но внутри executable
server-side MIME detection
reject mismatch by policy
24. Security
Обязательно:
authentication
authorization
short-lived presigned URLs
private bucket
encryption at rest
encryption in transit
server-side MIME validation
virus scan
file size limits
extension allowlist
PII masking in logs
audit trail
tenant isolation
least privilege IAM
Особенно важно:
“Storage bucket не должен быть публичным по умолчанию. Upload и download
должны происходить через short-lived signed URLs, а права должны
проверяться backend-ом.”
Также нельзя логировать:
- полные presigned URLs;
- чувствительные filenames;
- содержимое файлов;
- PII из payload.
25. API
Минимальный набор API:
POST /files/upload-session
POST /files/{fileId}/complete
GET /files/{fileId}
GET /files/{fileId}/download-url
DELETE /files/{fileId}
GET /files?projectId=...
Для multipart:
POST /files/multipart/start
POST /files/{fileId}/multipart/parts
POST /files/{fileId}/multipart/complete
POST /files/{fileId}/multipart/abort
Для статуса:
GET /files/{fileId}/status
Пример ответа:
{
"fileId": "file-789",
"fileName": "contract.pdf",
"status": "PROCESSING",
"size": 10485760,
"contentType": "application/pdf",
"createdAt": "2026-06-14T20:15:00Z"
}
26. MVP
Для MVP я бы сделал:
Upload API
Metadata DB
Object Storage
Presigned upload URL
Presigned download URL
Basic status tracking
File size limit
Content type allowlist
Basic async worker
Basic antivirus scan if domain requires
Basic cleanup job
Basic metrics/logs
Не стал бы сразу делать:
advanced deduplication
complex ACL per file
full resumable custom protocol
multi-provider storage abstraction
AI/OCR/transcoding pipeline
full DLP
advanced lifecycle tiers
Но архитектуру надо оставить такой, чтобы это можно было добавить.
27. Короткий сильный ответ на интервью
Можно ответить так:
“Я бы проектировал file upload system как систему, где backend управляет
метаданными, правами и состояниями, но сами байты файла идут напрямую в
object storage через presigned URL. Это разделяет control plane и data plane
и снимает с backend тяжёлый трафик.Flow такой: клиент запрашивает upload session, backend проверяет права,
лимиты, тип файла, создаёт metadata record со статусом PENDING_UPLOAD и
выдаёт presigned URL. Клиент загружает файл напрямую в object storage.
После completion backend или storage event переводит файл в UPLOADED, кладёт
событие в очередь, workers выполняют antivirus scan, MIME detection,
preview generation или другую обработку. Только после этого файл получает
статус READY.Для больших файлов я бы использовал multipart upload, чтобы можно было
ретраить отдельные части и продолжать загрузку после сетевых сбоев. Для
скачивания backend проверяет права и выдаёт short-lived presigned download
URL. Bucket остаётся private.Отдельно я бы заложил idempotency, cleanup для orphan uploads, retry/DLQ
для processing jobs, rate limits, quotas, audit trail, encryption,
observability и lifecycle policies.”
28. Самая сильная финальная формулировка
“Система загрузки файлов — это не endpoint, который принимает
multipart/form-data. Это контур управления жизненным циклом файла: upload
session, storage, metadata, access control, security scan, processing,
status machine, download authorization, cleanup, observability и retention.
Главные риски здесь — потерять файл, дать доступ не тому пользователю,
принять вредоносный файл, положить backend большими upload-ами, накопить
orphan objects или сделать файл доступным до проверки. Поэтому я бы строил
систему вокруг object storage, presigned URLs, metadata DB, очередей,
асинхронной обработки, идемпотентности и строгого контроля доступа.”