EasyТеория13 min

Обзор кейсов проектирования

Как подходить к кейсам системного дизайна: типовая структура решения, методология и фреймворк

Кейсы проектирования (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,
    ) {}
}
### Шаг 5: Схема данных (5 минут)
-- 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);
        }
    }
}
## Как использовать этот раздел
  1. Читайте кейс целиком -- каждый содержит полное решение от требований до масштабирования
  2. Пробуйте решить сами -- прочтите только требования и попробуйте спроектировать самостоятельно
  3. Сравните решения -- ваш дизайн может отличаться, и это нормально -- важны trade-offs
  4. Обращайте внимание на PHP-код -- это рабочие паттерны для реальных проектов
  5. Практикуйте с таймером -- на интервью у вас 35-45 минут на весь кейс

Чек-лист для самопроверки

✓ Уточнил требования (функциональные и нефункциональные)
✓ Сделал оценку нагрузки (DAU, QPS, storage)
✓ Нарисовал high-level архитектуру
✓ Определил API (endpoints, request/response)
✓ Спроектировал схему данных
✓ Углубился в 2-3 ключевых компонента
✓ Обсудил bottlenecks и масштабирование
✓ Упомянул trade-offs и альтернативные решения

Вопросы для самопроверки

  1. Какие 7 шагов фреймворка решения кейса?
  2. Как рассчитать QPS из DAU?
  3. В чём разница между функциональными и нефункциональными требованиями?
  4. Почему cursor-based пагинация лучше offset-based?
  5. Какие три паттерна (cache-aside, idempotency, distributed lock) используются чаще всего?