Базовая формула
Total storage = objects × size_per_object × retention_years × replication_factor
+ indexes (+20-40%)
+ backups
+ overhead (WAL, vacuum, fragmentation) ~10-20%
Storage sizing ошибаются чаще всего -- забывают про индексы, реплики, бэкапы. Итог: планировали 1 TB, получили 5 TB счёт.
Компоненты объёма
1. Raw data
Основные данные: строки БД, документы, файлы. Считается как:
raw = N × avg_size_per_record
Типичные размеры:
| Объект | Размер |
|---|---|
| User record (id, email, name, метаданные) | 1-2 KB |
| Tweet / short post | 280-500 bytes |
| Blog post (average) | 5-10 KB |
| Comment | 500 bytes - 2 KB |
| Photo (Instagram, full) | 2-5 MB |
| Photo thumbnail | 50-200 KB |
| Short video (TikTok 15s) | 5-10 MB |
| Long video (1min 1080p) | 50-100 MB |
| 4K video (1 hour) | 5-10 GB |
| Event / log line | 500 bytes - 2 KB |
| Time series point | 16-64 bytes |
2. Indexes
Индексы -- это 20-40% от raw data, иногда больше для write-heavy workloads.
| Тип индекса | Оверхед |
|---|---|
| B-tree primary key | ~10% |
| B-tree secondary (per column) | 10-20% |
| Full-text index | 30-100% (зависит от языка) |
| Geospatial | 15-30% |
| Bitmap | 5-15% |
Правило: если в таблице 5 индексов -- считайте, что storage удваивается.
3. Replication factor
Distributed storage всегда реплицирован. Типичные значения:
| Система | Replication |
|---|---|
| PostgreSQL primary + replica | 2x |
| PostgreSQL + 2 replicas | 3x |
| HDFS default | 3x |
| Cassandra (RF=3, quorum) | 3x |
| S3 standard | ~3x (но оплата 1x) |
| Kafka (replication.factor=3) | 3x |
Без replication factor ваша оценка в 3 раза меньше реальности.
4. Backups
Бэкапы хранятся минимум 30 дней, часто дольше. Типично:
- Full backup weekly + incremental daily = ~1.5x от production
- Plus point-in-time recovery WAL = +10-20%
Если data = 10 TB, backup storage ≈ 15-20 TB.
5. WAL / write-ahead log
Postgres WAL, Kafka segments, Cassandra commit log -- всё это overhead на писательскую нагрузку. Обычно 10-20% от raw data.
Компрессия
Компрессия сильно зависит от типа данных.
| Тип данных | Коэффициент сжатия |
|---|---|
| Text (JSON, logs) | 5-10x |
| Structured text (CSV, SQL dump) | 3-5x |
| HTML / XML | 5-8x |
| Already compressed (JPG, MP4, ZIP) | 1.0-1.05x (почти ничего) |
| Binary protocol (Protobuf, Avro) | 1.5-2x |
| Time series (Prometheus, InfluxDB) | 10-30x |
Практика. Включайте компрессию в БД (Postgres TOAST работает автоматически, MongoDB снижает на 50%+ для документов). Для объектного storage -- gzip/zstd перед загрузкой в S3.
Hot / Warm / Cold tiers
Не всё storage одинаково. Большинство данных старше 90 дней почти никто не читает. Сэкономьте 80% на cost, переместив в cheaper tier.
| Tier | Доступ | Latency | Cost (AWS примерно) |
|---|---|---|---|
| Hot | Постоянный | < 10 ms | $0.023/GB/month (S3 Standard) |
| Warm | Редкий | 10-100 ms | $0.0125/GB/month (S3 Standard-IA) |
| Cold | Архив | минуты | $0.004/GB/month (S3 Glacier) |
| Deep archive | Часы-дни | 12+ hours | $0.00099/GB/month (Glacier Deep) |
Lifecycle пример
day 0-30: S3 Standard (hot, постоянный доступ)
day 31-90: Standard-IA (warm, редкий)
day 91-365: Glacier (cold, аудит/compliance)
day 366+: Deep Archive (или delete, если не нужно)
Это снижает годовую стоимость на 70-90% для logs/events.
Growth curve
Не проектируйте под сегодня -- проектируйте под 3-5 лет роста.
Storage (TB)
│ ╭── ← 5x от сегодня
│ ╭────────╯
│ ╭────────╯
│ ╭────────╯
│ ╭────────╯
│───╯
└──┬────┬────┬────┬────┬────→ years
0 1 2 3 4 5
Year 0: 10 TB
Year 1: 20 TB (2x)
Year 3: 50 TB (5x)
Year 5: 100 TB (10x)
Правило. Assume 2x growth per year для растущего продукта. Для зрелого -- 1.2-1.5x.
Worked пример: Twitter-подобный сервис
Входные данные
- 500 M tweets per day (Twitter ~500M публичных tweet'ов/день на пике)
- avg tweet size: 280 bytes (text) + 300 bytes (metadata: user_id, timestamp, geo, retweet_of) = 580 bytes
- retention: вечно (публичные твиты)
- replication factor: 3 (Cassandra RF=3)
Text storage
Daily raw text = 500M × 580 B = 290 GB/day
Daily compressed (5x) = 58 GB/day
Indexes
Indexes (30%) = 58 × 0.3 ≈ 17 GB/day
Daily with indexes = 58 + 17 = 75 GB/day
Replication
Daily replicated = 75 × 3 = 225 GB/day
Media (25% tweets have image)
Images/day = 500M × 0.25 = 125M
Image size = 200 KB (с учётом thumbnails)
Media raw/day = 125M × 200 KB = 25 TB/day (NOT compressible)
Media × RF=3 = 75 TB/day
Годовой объём
Text + indexes (с RF): 225 GB × 365 = 82 TB/year
Media (с RF): 75 TB × 365 = 27 PB/year
Вывод: основной storage -- медиа (99.7% объёма), не тексты. Поэтому Twitter использует специализированный media storage (похожее на S3) с aggressive CDN-кэшированием.
Worked пример: e-commerce каталог
Входные данные
- 10M products
- avg product record: 5 KB (description, attributes, variants, prices history)
- 10 images per product, avg 300 KB
- 5 years retention
- Postgres primary + 2 replicas = RF 3
Подсчёт
Products raw: 10M × 5 KB = 50 GB
Indexes (+40%): 50 + 20 = 70 GB
Replication x3: 70 × 3 = 210 GB
Images raw: 10M × 10 × 300 KB = 30 TB
Image RF x3: 30 × 3 = 90 TB
Рост за 5 лет (assume 2x catalog)
Total (5 years): 210 GB × 2 = 420 GB (SQL DB)
90 TB × 2 = 180 TB (object storage)
Архитектурный вывод
- 420 GB SQL -- помещается в один Postgres instance
- 180 TB images -- нужен S3-совместимый storage + CDN
Калькулятор storage
<?php
declare(strict_types=1);
/**
* Storage sizing calculator: raw data -> total with indexes/replication/backup.
*/
final class StorageSizing
{
public function __construct(
private readonly int $objectCount,
private readonly int $avgSizeBytes,
private readonly float $indexOverhead = 0.3, // 30% typical
private readonly int $replicationFactor = 3,
private readonly float $backupOverhead = 0.5, // 50% for weekly+daily backups
private readonly float $compressionRatio = 1.0, // 1.0 = no compression
) {}
/**
* @return array{
* raw: int, compressed: int, with_indexes: int,
* replicated: int, with_backups: int, total: int
* }
*/
public function estimate(): array
{
$raw = $this->objectCount * $this->avgSizeBytes;
$compressed = (int) ($raw / $this->compressionRatio);
$withIndexes = (int) ($compressed * (1 + $this->indexOverhead));
$replicated = $withIndexes * $this->replicationFactor;
$withBackups = (int) ($replicated * (1 + $this->backupOverhead));
return [
'raw' => $raw,
'compressed' => $compressed,
'with_indexes' => $withIndexes,
'replicated' => $replicated,
'with_backups' => $withBackups,
'total' => $withBackups,
];
}
/**
* Project growth over N years with given yearly multiplier.
*/
public function projectGrowth(int $years, float $yearlyMultiplier = 2.0): int
{
$base = $this->estimate()['total'];
return (int) ($base * ($yearlyMultiplier ** $years));
}
}
// Usage:
// $s = new StorageSizing(
// objectCount: 500_000_000, // 500M tweets/day
// avgSizeBytes: 580,
// compressionRatio: 5.0,
// replicationFactor: 3,
// );
// $daily = $s->estimate();
// $fiveYear = $s->projectGrowth(5, 1.5);
- Забыть про индексы. "Данные занимают 100 GB" -- а с индексами 140, а с реплика́ми 420.
- Считать только production. Бэкапы, dev/stage, audit logs -- ещё +50-100%.
- Не учитывать compression. Text без сжатия -- в 5 раз больше реальности.
- Проектировать на сегодня. Через 2 года данных в 4 раза больше.
- Игнорировать small objects overhead. 1M записей по 100 bytes != 100 MB. В Postgres каждая строка имеет 24 bytes header + alignment. Реальность: 250-400 MB.
Выводы
- Формула:
objects × size × RF × (1 + index%) × (1 + backup%) / compression_ratio - Индексы +20-40%, репликация x2-x3, бэкапы +50% -- итого x5-6 от raw
- Hot/warm/cold tiers экономят 70-90% стоимости для logs/events
- Планируйте под 3-5 лет роста, assume 2x per year
- Media dominates в 99% случаев -- текстовая БД скромна, object storage огромен