> Как оценить необходимое дисковое пространство для хранения сокращенных ссылок с учетом нагрузки и времени жизни (Python)

Уровень: senior · Роль: backend · Язык: Python · Категория: Технические вопросы

Компании: Международный аэропорт Шереметьево

Стек: Python

> Пример ответа

Короткий ответ

Оценка дискового пространства сводится к формуле: количество записей × размер одной записи + overhead индексов. Количество записей определяется как RPS × время жизни × коэффициент дублирования. Для сокращенных ссылок типичный размер записи - 100-200 байт (short code, оригинальный URL, timestamp, user_id). При 1000 RPS и времени жизни 1 год получаем ~31.5 млрд записей, что при 150 байтах на запись - ~4.7 ТБ без учета индексов. Индексы добавляют 30-50% к объему.

Подробное объяснение

Основные параметры модели:

  1. Нагрузка (RPS) - количество создаваемых ссылок в секунду. Важно учитывать пиковые значения, а не средние, с запасом ×2-3.

  2. Время жизни (TTL) - период, в течение которого ссылка должна храниться. После истечения запись удаляется или архивируется.

  3. Размер записи - складывается из:

    • short code: 6-8 символов (base62) - 6-8 байт
    • оригинальный URL: в среднем 100-200 байт, но бывают длинные - закладывайте 500 байт
    • timestamp создания и expires_at: 8 + 8 байт
    • user_id (если есть): 4-8 байт
    • дополнительные поля (click count, flags): 10-20 байт
    • итого: 150-250 байт на запись
  4. Коэффициент дублирования - если одна и та же ссылка сокращается повторно, либо создается новая запись, либо возвращается существующая. Второй вариант уменьшает объем, но требует дополнительного индекса по хешу URL.

  5. Overhead БД - для PostgreSQL или MySQL это ~30-40% на страницы, транзакции, MVCC. Для Cassandra или DynamoDB - репликация ×3 и compaction overhead.

Формула:

total_bytes = writes_per_second × ttl_seconds × record_size × (1 + index_overhead) × replication_factor

Пример: 1000 RPS, TTL = 1 год (31 536 000 сек), record = 200 байт, index overhead = 0.4, replication = 1 (одиночная БД).

1000 × 31 536 000 × 200 × 1.4 ≈ 8.8 ТБ

Для распределенной системы с репликацией ×3 - 26.5 ТБ.

Важно учитывать, что реальный объем будет больше из-за:

  • служебных данных (WAL, snapshots, temp files)
  • фрагментации и неполного заполнения страниц
  • роста размера индексов при вставках

На практике

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

Порядок оценки:

  1. Уточните RPS - спросите про пиковые значения и сезонность.
  2. Определите TTL - есть ли политика удаления старых ссылок.
  3. Оцените средний размер URL - можно взять 200 байт как разумный верхний предел.
  4. Посчитайте количество записей за период.
  5. Добавьте overhead индексов и служебных данных.
  6. Если система распределенная - умножьте на replication factor.

Также стоит упомянуть альтернативные подходы:

  • Хранение только хеша и ID - оригинальный URL в отдельной таблице или object storage, в основной таблице только ссылка на него. Это уменьшает размер записи до 30-40 байт.
  • Архивация старых записей - перемещение данных старше N месяцев в холодное хранилище (S3, Glacier), что снижает нагрузку на горячую БД.
  • Использование колоночных хранилищ для аналитики, если нужны только агрегаты по ссылкам.

Пример кода

Расчет объема в Python:

PYTHON
def estimate_storage(
rps: int,
ttl_days: int,
record_size_bytes: int = 200,
index_overhead: float = 0.4,
replication_factor: int = 1,
peak_multiplier: float = 2.0
) -> dict:
ttl_seconds = ttl_days * 24 * 3600
peak_rps = rps * peak_multiplier
total_records = peak_rps * ttl_seconds
base_size = total_records * record_size_bytes
with_indexes = base_size * (1 + index_overhead)
with_replication = with_indexes * replication_factor
return {
"total_records": total_records,
"base_size_gb": base_size / (1024**3),
"with_indexes_gb": with_indexes / (1024**3),
"final_size_gb": with_replication / (1024**3),
}
# Пример: 1000 RPS, TTL 1 год, репликация ×3
result = estimate_storage(
rps=1000,
ttl_days=365,
record_size_bytes=200,
replication_factor=3
)
for key, value in result.items():
print(f"{key}: {value:,.2f}")

Вывод:

total_records: 63,072,000,000
base_size_gb: 11,748.05
with_indexes_gb: 16,447.27
final_size_gb: 49,341.80

Как отвечать на собеседовании

Начните с уточняющих вопросов: какой RPS, какое время жизни, есть ли репликация, какая БД используется. Это покажет системное мышление.

Затем предложите формулу и пройдите по каждому компоненту: количество записей, размер записи, overhead. Покажите расчет на конкретных цифрах.

Обязательно упомяните trade-off: можно уменьшить размер записи, вынеся URL в отдельное хранилище, но это усложнит чтение. Или сократить TTL, но тогда пользователи потеряют доступ к старым ссылкам.

В конце спросите, какие ограничения есть у системы - это покажет, что вы думаете о реальных constraints.

Что проверяет интервьюер

  • Умение декомпозировать задачу на параметры (RPS, TTL, размер записи).
  • Понимание overhead БД: индексы, репликация, служебные данные.
  • Способность делать быстрые оценки (back-of-the-envelope) без калькулятора.
  • Знание trade-off между хранением и скоростью доступа.
  • Учет пиковых нагрузок, а не только средних.

Типичные ошибки

  • Забывают про репликацию и overhead индексов - итоговая цифра занижается в 2-3 раза.
  • Используют средний RPS вместо пикового - недооценка в периоды нагрузки.
  • Не учитывают, что URL могут быть длинными (например, с UTM-метками) - лучше закладывать 500 байт.
  • Считают только размер данных, игнорируя WAL, temp files, vacuum (для PostgreSQL).
  • Не упоминают возможность архивации старых данных - это важный способ снизить объем горячего хранилища.
  • Путают логический размер записи с физическим - на диске запись занимает больше из-за выравнивания и заголовков страниц.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью