Кейсы проектирования (System Design Case Studies) -- это структурированные задачи, в которых необходимо спроектировать масштабируемую систему от требований до детальной архитектуры. Этот раздел содержит 20 реальных кейсов разной сложности.
Зачем изучать кейсы
| Причина | Описание |
|---|---|
| Интервью | System design interview -- обязательный этап в крупных компаниях |
| Практика | Применение теории (CAP, масштабирование, кэширование) на реальных задачах |
| Архитектурное мышление | Умение декомпозировать сложные системы на компоненты |
| Trade-offs | Навык выбора между противоречивыми требованиями |
Типовая структура решения кейса
Каждый кейс системного дизайна следует единому фреймворку из 7 шагов.
Шаг 1: Уточнение требований (5 минут)
Никогда не начинайте проектировать сразу. Задайте вопросы.
Функциональные требования -- что система должна делать:
- Какие основные use cases?
- Кто пользователи системы?
- Какие данные система хранит/обрабатывает?
- Какие API нужны?
Нефункциональные требования -- как система должна работать:
- Сколько пользователей / запросов?
- Какая допустимая задержка (latency)?
- Доступность: 99.9% или 99.99%?
- Consistency vs Availability -- что важнее?
- Региональность: один регион или глобально?
Шаг 2: Оценка нагрузки (5 минут)
Back-of-the-envelope estimation -- ключевой навык.
<?php
declare(strict_types=1);
final class LoadEstimation
{
/**
* Example: URL Shortener estimation
*/
public static function estimate(): array
{
// Daily Active Users
$dau = 100_000_000;
// Each user creates 1 short URL per day on average
$writesPerDay = $dau * 1;
// Read/Write ratio = 100:1
$readsPerDay = $writesPerDay * 100;
// QPS (Queries Per Second)
$writeQps = (int) ($writesPerDay / 86400); // ~1157 QPS
$readQps = (int) ($readsPerDay / 86400); // ~115,740 QPS
// Storage per record: ~500 bytes
$storagePerDay = $writesPerDay * 500; // ~50 GB/day
$storagePerYear = $storagePerDay * 365; // ~18 TB/year
// Bandwidth
$incomingBandwidth = $writeQps * 500; // ~578 KB/s
$outgoingBandwidth = $readQps * 500; // ~57 MB/s
return [
'write_qps' => $writeQps,
'read_qps' => $readQps,
'storage_per_year' => self::formatBytes($storagePerYear),
'outgoing_bandwidth' => self::formatBytes($outgoingBandwidth) . '/s',
];
}
private static function formatBytes(int|float $bytes): string
{
$units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024;
$i++;
}
return round($bytes, 2) . ' ' . $units[$i];
}
}
| Метрика | Значение |
|---|---|
| Секунд в дне | 86,400 (~10^5) |
| Секунд в месяце | 2,592,000 (~2.5 * 10^6) |
| QPS из DAU | DAU * actions / 86400 |
| 1 символ UTF-8 | 1-4 байта |
| UUID | 36 байт (строка) / 16 байт (бинарный) |
| Средний tweet/пост | ~300 байт |
| Изображение (сжатое) | 200 KB - 1 MB |
| Видео (1 минута, 720p) | ~50 MB |
Шаг 3: High-Level архитектура (10 минут)
Нарисуйте основные компоненты и связи:
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ Client │────>│ Load │────>│ Web Server │
│ (App) │ │ Balancer │ │ (Nginx) │
└──────────┘ └──────────────┘ └──────┬───────┘
│
┌──────────────────────┼──────────────────────┐
│ │ │
┌──────▼───────┐ ┌─────────▼────────┐ ┌────────▼───────┐
│ App Server │ │ App Server │ │ App Server │
│ (PHP-FPM) │ │ (PHP-FPM) │ │ (PHP-FPM) │
└──────┬───────┘ └─────────┬─────────┘ └────────┬───────┘
│ │ │
┌──────▼──────────────────────▼──────────────────────▼───────┐
│ Service Layer │
│ ┌─────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ │
│ │ Cache │ │ Queue │ │ Search │ │ CDN │ │
│ │ Redis │ │ RabbitMQ │ │ Elastic │ │ │ │
│ └─────────┘ └──────────┘ └───────────┘ └──────────┘ │
└───────────────────────┬───────────────────────────────────┘
│
┌───────────────────────▼───────────────────────────────────┐
│ Data Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐ │
│ │ PostgreSQL │ │ PostgreSQL │ │ Object │ │
│ │ (Primary) │ │ (Replica) │ │ Storage (S3) │ │
│ └──────────────┘ └──────────────┘ └────────────────┘ │
└───────────────────────────────────────────────────────────┘
Шаг 4: Дизайн API (5 минут)
Определите ключевые endpoint-ы:
<?php
declare(strict_types=1);
/**
* Typical API design for a case study
*/
interface SystemApiInterface
{
// Core CRUD operations
// POST /api/v1/resources
public function create(CreateResourceRequest $request): ResourceResponse;
// GET /api/v1/resources/{id}
public function get(string $id): ResourceResponse;
// PUT /api/v1/resources/{id}
public function update(string $id, UpdateResourceRequest $request): ResourceResponse;
// DELETE /api/v1/resources/{id}
public function delete(string $id): void;
// List with pagination (cursor-based!)
// GET /api/v1/resources?cursor=xxx&limit=20
public function list(string $cursor, int $limit): PaginatedResponse;
}
// Always define clear request/response DTOs
final readonly class CreateResourceRequest
{
public function __construct(
public string $name,
public string $type,
public array $metadata = [],
) {}
}
final readonly class ResourceResponse
{
public function __construct(
public string $id,
public string $name,
public string $type,
public string $createdAt,
) {}
}
final readonly class PaginatedResponse
{
public function __construct(
public array $items,
public ?string $nextCursor,
public bool $hasMore,
) {}
}
-- Standard pattern for any entity
CREATE TABLE resources (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL,
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Always index foreign keys and frequently filtered columns
CREATE INDEX idx_resources_type ON resources (type);
CREATE INDEX idx_resources_created_at ON resources (created_at);
-- For cursor-based pagination
CREATE INDEX idx_resources_cursor ON resources (created_at, id);
Шаг 6: Детальный дизайн (15 минут)
Углубитесь в 2-3 ключевых компонента. Обсудите:
- Как работает кэширование?
- Как масштабируется база данных?
- Как обеспечивается отказоустойчивость?
- Как обрабатываются edge cases?
Шаг 7: Bottlenecks и масштабирование (5 минут)
Обсудите узкие места и пути решения:
| Проблема | Решение |
|---|---|
| Высокий read QPS | Read replicas, кэширование (Redis) |
| Высокий write QPS | Шардирование, очереди |
| Большой объём данных | Шардирование, архивация, object storage |
| Высокая latency | CDN, кэширование, денормализация |
| Single Point of Failure | Репликация, failover, multi-AZ |
| Hotspot (горячие ключи) | Consistent hashing, кэширование |
Матрица кейсов по сложности
| # | Кейс | Сложность | Ключевые темы |
|---|---|---|---|
| 1 | URL Shortener | Medium | Хэширование, кэширование, масштабирование |
| 2 | Rate Limiter | Medium | Алгоритмы, Redis, distributed systems |
| 3 | Система уведомлений | Medium | Очереди, retry, приоритизация |
| 4 | API Gateway | Medium | Middleware, routing, аутентификация |
| 5 | CDN | Medium | Кэширование, DNS, распределённость |
| 6 | Поисковая система | Hard | Индексы, ранжирование, full-text |
| 7 | Web Crawler | Hard | Очереди, дедупликация, масштабирование |
| 8 | Object Storage | Hard | Chunking, репликация, метаданные |
| 9 | Распределённая FS | Hard | Master/slave, consistency, replication |
| 10 | Система чатов | Hard | WebSocket, доставка, presence |
| 11 | Видеохостинг | Hard | Транскодирование, стриминг, CDN |
| 12 | Twitter Feed | Hard | Fanout, timeline, trending |
| 13 | A/B Testing | Hard | Хэширование, статистика, трафик |
| 14 | Геолокация | Hard | Geohash, spatial index, proximity |
| 15 | Бронирование билетов | Hard | Concurrency, locking, consistency |
| 16 | Бронирование отелей | Hard | Availability, pricing, inventory |
| 17 | Real-time Gaming | Expert | Matchmaking, state sync, lag |
| 18 | Airbnb | Expert | Поиск, бронирование, matching |
| 19 | Uber | Expert | Геолокация, real-time, surge |
| 20 | Платёжная система | Expert | Идемпотентность, PCI DSS, reconciliation |
Общие паттерны для кейсов
<?php
declare(strict_types=1);
/**
* Base service pattern used across case studies
*/
abstract class BaseService
{
public function __construct(
protected readonly \Redis $redis,
protected readonly \PDO $db,
protected readonly LoggerInterface $logger,
) {}
/**
* Cache-aside pattern (used in most case studies)
*/
protected function cacheAside(string $key, int $ttl, callable $loader): mixed
{
$cached = $this->redis->get($key);
if ($cached !== false) {
return json_decode($cached, true);
}
$data = $loader();
$this->redis->setex($key, $ttl, json_encode($data));
return $data;
}
/**
* Idempotency key pattern (used in payment, booking systems)
*/
protected function executeIdempotent(
string $idempotencyKey,
callable $operation,
): mixed {
$existing = $this->redis->get("idempotency:{$idempotencyKey}");
if ($existing !== false) {
return json_decode($existing, true);
}
$result = $operation();
$this->redis->setex(
"idempotency:{$idempotencyKey}",
86400, // 24h
json_encode($result),
);
return $result;
}
/**
* Distributed lock pattern (used in booking, inventory)
*/
protected function withLock(string $resource, int $ttl, callable $callback): mixed
{
$lockKey = "lock:{$resource}";
$lockValue = bin2hex(random_bytes(16));
$acquired = $this->redis->set(
$lockKey,
$lockValue,
['NX', 'EX' => $ttl],
);
if (!$acquired) {
throw new \RuntimeException("Failed to acquire lock for: {$resource}");
}
try {
return $callback();
} finally {
// Release only if we still own the lock
$script = <<<'LUA'
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
LUA;
$this->redis->eval($script, [$lockKey, $lockValue], 1);
}
}
}
- Читайте кейс целиком -- каждый содержит полное решение от требований до масштабирования
- Пробуйте решить сами -- прочтите только требования и попробуйте спроектировать самостоятельно
- Сравните решения -- ваш дизайн может отличаться, и это нормально -- важны trade-offs
- Обращайте внимание на PHP-код -- это рабочие паттерны для реальных проектов
- Практикуйте с таймером -- на интервью у вас 35-45 минут на весь кейс
Чек-лист для самопроверки
✓ Уточнил требования (функциональные и нефункциональные)
✓ Сделал оценку нагрузки (DAU, QPS, storage)
✓ Нарисовал high-level архитектуру
✓ Определил API (endpoints, request/response)
✓ Спроектировал схему данных
✓ Углубился в 2-3 ключевых компонента
✓ Обсудил bottlenecks и масштабирование
✓ Упомянул trade-offs и альтернативные решения
Вопросы для самопроверки
- Какие 7 шагов фреймворка решения кейса?
- Как рассчитать QPS из DAU?
- В чём разница между функциональными и нефункциональными требованиями?
- Почему cursor-based пагинация лучше offset-based?
- Какие три паттерна (cache-aside, idempotency, distributed lock) используются чаще всего?