Обновлено: 30 апреля 2026 г. в 07:40

Object Storage (S3)

средний

Классическая задача: разнести метаданные и данные, обеспечить очень высокую долговечность, выбрать схему хранения и поддержать загрузку больших объектов по частям.

Объектное хранилище кажется простым API для файлов, но внутри это архитектура про долговечность данных, огромное пространство имён и фоновые процессы восстановления.

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

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

Долговечность данных

Ключевой вопрос не только в количестве копий, но и в том, как быстро система замечает повреждения, чинит их и разводит данные по независимым доменам отказа.

Слой метаданных

Метаданные определяют, где лежит объект и как работает LIST, поэтому именно этот слой чаще всего становится настоящим узким местом системы.

Горячие бакеты

Неравномерная нагрузка по bucket и prefix быстро создаёт горячие точки, если заранее не продумать разбиение каталога и отдельные индексы.

Цена хранения

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

хранит файлы, изображения, видео и резервные копии как независимые объекты с доступом по HTTP API. В отличие от файловой системы оно опирается на плоское и проектируется вокруг очень высокой , предсказуемой стоимости хранения и практически неограниченного масштаба.

Источник

System Design Interview Vol. 2

Глава Design S3 разбирает путь записи, слой метаданных и причины, по которым S3 выглядит простым только снаружи.

Читать обзор

Примеры объектных хранилищ

  • Amazon S3: эталонный интерфейс индустрии с очень высокой долговечностью данных.
  • Google Cloud Storage: тесно связан с экосистемой аналитики и ML в Google Cloud.
  • Azure Blob Storage: предлагает несколько классов хранения от горячего до архивного.
  • MinIO: S3-совместимое решение с открытым исходным кодом для частных инсталляций и локальной инфраструктуры.
  • Ceph: универсальная распределённая платформа, которая поддерживает блочное, файловое и объектное хранение.

Функциональные требования

Базовые операции

  • PUT /bucket/object — загрузка объекта
  • GET /bucket/object — чтение или скачивание объекта
  • DELETE /bucket/object — удаление объекта
  • LIST /bucket?prefix= — перечисление объектов по префиксу

Дополнительные возможности

  • Загрузка больших объектов по частям
  • Хранение нескольких версий одного объекта
  • Автоматический перевод данных между классами хранения
  • Временный доступ через pre-signed URL

Нефункциональные требования

Для S3-подобной системы важно заранее договориться не только о масштабе хранения, но и о целевой сервиса и ожидаемой на пути чтения и записи.

ТребованиеЦелевое значениеПочему это важно
Долговечность данных99.999999999% (11 девяток)Потеря пользовательских данных недопустима даже при сбоях оборудования.
Доступность99.99%Объекты должны быть доступны почти постоянно для приложений и пользователей.
МасштабЭкзабайты и вышеХранилище должно расти без переразметки всей системы.
Пропускная способностьTbps+Система должна выдерживать массовые параллельные загрузки и скачивания.
Размер объектаДо 5 TB (как в S3)Нужно обслуживать и небольшие файлы, и очень крупные архивы.

Высокоуровневая архитектура

Теория

DDIA: Storage Engines

Помогает понять, почему слой метаданных и слой данных живут по разным правилам и требуют разных структур хранения.

Читать обзор

Архитектурная схема

Путь записи и чтения в объектном хранилище
Клиенты
Веб · мобильные клиенты · SDK
Балансировщик
Приём внешнего трафика
API Gateway
Аутентификация · лимиты · маршрутизация
Сервис метаданных
Ключи · ACL · версии
Объектный сервис
Размещение · реплики
Сервис бакетов
Пространство имён · политики
База метаданных
Шардированное KV
Слой хранения
Диски · кодирование
База бакетов
Конфигурация

Сервис метаданных

Здесь хранятся имя объекта, размер, content-type, checksum, версии, правила доступа и ссылка на физическое размещение данных. Этот слой отвечает за поиск объекта по ключу и за операции LIST, поэтому именно он чаще всего становится главным источником эксплуатационной сложности.

Пример записи метаданных:

{
  "bucket_id": "uuid",
  "object_key": "/photos/2024/vacation.jpg",
  "object_id": "uuid",
  "size": 4_500_000,
  "content_type": "image/jpeg",
  "checksum": "sha256:abc123...",
  "version_id": "v3",
  "created_at": "2024-01-15T10:30:00Z",
  "storage_class": "STANDARD",
  "replicas": ["node1", "node2", "node3"]
}

Слой хранения данных

Сами байты объекта лежат отдельно от метаданных. Такой разрез позволяет независимо масштабировать каталог объектов и физическое хранение, а также выбирать разные стратегии размещения для горячих и холодных данных.

Подходы к хранению:

  • Репликация: несколько копий на разных узлах или в разных AZ
  • Кодирование с избыточностью: меньше накладных расходов на хранение ценой более сложного восстановления
  • Многоуровневое хранение: горячие данные, редкий доступ и архив

Практические оптимизации:

  • Последовательная запись крупных объектов
  • Пакетирование мелких файлов
  • Фоновая очистка и уплотнение редко используемых сегментов

Путь загрузки объекта

Путь загрузки

Упрощённый сценарий записи объекта
1
Клиент
PUT /bucket/object
2
API Gateway
Проверка доступа и квот
3
Объектный сервис
Выбор размещения
4
Слой хранения
Запись и копии
5
База метаданных
Фиксация метаданных
6
Ответ
200 OK и ETag
Нажмите «Запустить», чтобы посмотреть путь записи объекта по шагам.

Загрузка крупных файлов по частям

нужна для больших объектов: клиент может передавать части параллельно, возобновлять операцию после сбоя и не пересылать уже записанные куски повторно.

# 1. Инициализация
POST /bucket/object?uploads → upload_id

# 2. Загрузка частей (параллельно)
PUT /bucket/object?uploadId=X&partNumber=1 → ETag1
PUT /bucket/object?uploadId=X&partNumber=2 → ETag2
...

# 3. Завершение
POST /bucket/object?uploadId=X
{
  "parts": [
    {"partNumber": 1, "ETag": "..."},
    {"partNumber": 2, "ETag": "..."}
  ]
}

Долговечность данных и размещение копий

Глубже

Database Internals

Полезно для разговора о репликации, восстановлении и цене каждой дополнительной гарантии хранения.

Читать обзор

На практике объектное хранилище сочетает для быстрого восстановления и для экономии места на больших объёмах холодных данных.

Репликация

Один и тот же объект хранится в нескольких копиях на разных узлах, стойках или в разных зонах доступности.

Плюсы:

  • Простой путь записи и чтения
  • Быстрое восстановление после отказа узла
  • Низкая вычислительная нагрузка

Минусы:

  • Примерно трёхкратные затраты на хранение при трёх копиях
  • Высокая стоимость для огромных объёмов архивных данных

Кодирование с избыточностью

Данные разбиваются на k полезных фрагментов и m дополнительных блоков, поэтому объект можно восстановить даже после потери части носителей.

Плюсы:

  • Сильно ниже накладные расходы на хранение, чем у трёх копий
  • Хорошо подходит для редко читаемых данных и архивов
  • Позволяет сохранить высокую долговечность без чрезмерной цены

Минусы:

  • Выше нагрузка на CPU во время записи, чтения и восстановления
  • Ремонт после сбоя сложнее, чем при простой репликации

Откуда берутся одиннадцать девяток

Само число не появляется из магии. Оно складывается из разнесения копий по независимым доменам отказа, быстрой замены повреждённых фрагментов и постоянной проверки целостности.

# Допустим:
# - Annual Failure Rate (AFR) одного диска = 2%
# - 3 копии в разных failure domains

P(потеря одного диска) = 0.02
P(потеря двух дисков одновременно) = 0.02 × 0.02 = 0.0004
P(потеря трёх дисков до восстановления) = 0.0004 × 0.02 = 0.000008

# Дальше помогают:
# - разные AZ
# - быстрое восстановление
# - scrubbing и контроль checksum
# → очень высокая долговечность данных

Шардирование метаданных

Когда число объектов доходит до миллиардов, слой метаданных становится главным узким местом. снимает нагрузку, но сразу усложняет LIST-запросы и выбор приемлемой между основным каталогом и дополнительными индексами.

1. Разбиение по bucket

Самый простой вариант: все объекты одного bucket лежат на одном шарде. Проблема в том, что популярные bucket быстро становятся горячими точками.

2. Разбиение по хэшу object key

hash(bucket_id + object_key) % N даёт хорошее распределение нагрузки, но LIST-запрос приходится отправлять во все шарды и затем собирать результат обратно.

3. Гибридный подход

Основной каталог шардируется по одному ключу, а для prefix-запросов поддерживается отдельный индекс. Именно такой путь чаще всего выбирают боевые системы.

Почему LIST дороже, чем кажется

При хэш-разбиении запрос LIST /bucket?prefix=/photos/ не знает заранее, в каком шарде окажется нужный диапазон ключей. Поэтому координатор рассылает запрос по всем шардам и затем объединяет частичные ответы.

Что помогает:

  • Отдельный индекс для prefix-запросов
  • Диапазонное разбиение для упорядоченного листинга
  • Денормализация в отдельную таблицу каталога

Цена решения:

  • Дополнительные затраты на хранение индексов
  • Необходимость синхронизировать основной каталог и индексы
  • Более сложный путь записи и обновления метаданных

Безопасность

Контроль доступа

  • IAM Policies: права на уровне пользователя или роли
  • Bucket Policies: правила доступа на уровне ресурса
  • ACLs: права на конкретный объект, если нужен более тонкий контроль
  • Pre-signed URLs: временный доступ без постоянной выдачи ключей

Шифрование

  • SSE-S3: шифрование на стороне сервиса с управляемыми ключами
  • SSE-KMS: серверное шифрование с ключами, управляемыми через KMS
  • SSE-C: серверное шифрование с ключами, которые приносит клиент
  • Client-side: шифрование до отправки объекта в хранилище

Классы хранения

Связь

Интеграция с сетью доставки контента

Объектное хранилище часто выступает исходным слоем для глобальной раздачи контента, где важны версия объектов, предсказуемое чтение и контроль стоимости.

Читать обзор
КлассЗадержкаСтоимостьТипичный сценарий
Standard (Hot)миллисекунды$$$Частый доступ, рабочие данные приложения
Infrequent Accessмиллисекунды$$Резервные копии и данные с редкими чтениями
Archive (Glacier)часы$Долгосрочное хранение и регуляторные архивы
Deep Archive12+ часов¢Очень редкие обращения и архивы “на всякий случай”

Политики жизненного цикла

{
  "rules": [
    {
      "filter": {"prefix": "logs/"},
      "transitions": [
        {"days": 30, "storageClass": "INFREQUENT_ACCESS"},
        {"days": 90, "storageClass": "GLACIER"}
      ],
      "expiration": {"days": 365}
    }
  ]
}

Вопросы для интервью

1. Как добраться до одиннадцати девяток долговечности?

Разнести копии или кодовые фрагменты по разным AZ, регулярно проверять checksum, быстро чинить повреждённые данные и не экономить на мониторинге фонового ремонта.

2. Как ускорить загрузку очень больших файлов?

Загружать файл по частям, разрешать параллельную отправку, поддерживать возобновление после обрыва и по возможности отдавать клиенту pre-signed URL для прямой записи в хранилище.

3. Когда выбирать репликацию, а когда кодирование с избыточностью?

Репликация подходит для горячих данных с частыми чтениями и требованием к быстрому восстановлению. Кодирование с избыточностью выгоднее для холодных данных, когда экономия места важнее дополнительной вычислительной нагрузки.

4. Как реализовать версионирование без потери старых объектов?

Каждый PUT создаёт новую запись с уникальным version_id, а DELETE пишет delete marker в метаданные, не стирая данные немедленно.

5. Что делать с “мусором” после удалений и незавершённых загрузок?

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

Ключевые выводы

  • Метаданные и данные живут отдельно — именно это позволяет независимо масштабировать каталог и фактическое хранение байтов.
  • Долговечность достигается сочетанием техник — разнесение копий, фоновый ремонт, checksum и контроль доменов отказа работают вместе.
  • Классы хранения меняют экономику — горячие и архивные данные нельзя держать по одной и той же цене.
  • LIST и метаданные часто сложнее, чем PUT/GET — именно здесь чаще всего появляются горячие точки, индексы и спорные гарантии консистентности.
  • Загрузка по частям нужна не ради красоты API — она делает работу с большими объектами устойчивой к сбоям и удобной для клиентов.

Связанные главы

Чтобы отмечать прохождение, включи трекинг в Настройки