EasyТеория3 min

Обзор подходов к проектированию

Введение в ключевые подходы к проектированию масштабируемых систем: какие бывают стратегии и когда их применять

Что значит "проектировать систему"

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

Фундаментальные противоречия

Требование A vs Требование B
Консистентность vs Доступность (CAP)
Простота vs Гибкость
Скорость разработки vs Производительность
Надежность vs Стоимость
Масштабируемость vs Сложность

Задача инженера -- найти оптимальный баланс для конкретной ситуации.

Категории подходов

1. Масштабирование (Scaling)

Как система обрабатывает рост нагрузки.

<?php

declare(strict_types=1);

/**
 * Two fundamental scaling approaches
 */

// Vertical Scaling: bigger machine
// Simple, but has a ceiling
// Good for: databases, single-leader systems
// Limit: largest available machine

// Horizontal Scaling: more machines
// Complex, but virtually unlimited
// Good for: stateless services, read replicas
// Challenge: data distribution and coordination

/**
 * Stateless service: easy to scale horizontally
 * Each request is self-contained
 */
final class StatelessApiHandler
{
    public function __construct(
        private readonly UserRepository $users,
        private readonly TokenValidator $tokens,
    ) {}

    public function handleRequest(Request $request): Response
    {
        // All state comes from the request or external storage
        // No local state = any server can handle any request
        $token = $request->getHeader('Authorization');
        $userId = $this->tokens->validate($token);
        $user = $this->users->findById($userId);

        return new Response(json_encode($user));
    }
}

2. Балансировка нагрузки (Load Balancing)

Как распределять трафик между серверами.

Ключевые алгоритмы:

  • Round Robin -- по кругу
  • Weighted Round Robin -- с учетом мощности
  • Least Connections -- к наименее загруженному
  • Consistent Hashing -- стабильное распределение
  • IP Hash -- привязка клиента к серверу

3. Кэширование (Caching)

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

Уровни кэширования:

Браузер (L1) → CDN (L2) → Reverse Proxy (L3) → Application (L4) → Database (L5)

4. Хранение данных (Data Storage)

Как организовать данные для эффективного чтения и записи.

Подход Когда использовать
SQL Структурированные данные, транзакции
NoSQL (Document) Гибкая схема, read-heavy
NoSQL (Key-Value) Кэширование, сессии
NoSQL (Column) Time-series, analytics
NoSQL (Graph) Связи между сущностями

5. Репликация и шардирование (Replication & Sharding)

Как распределить данные для надежности и производительности.

<?php

declare(strict_types=1);

/**
 * Replication vs Sharding - fundamental difference
 */

// Replication: same data on multiple nodes
// Purpose: availability and read performance
// Every replica has ALL the data

// Sharding: different data on different nodes
// Purpose: write performance and storage capacity
// Each shard has PART of the data

/**
 * Example: determining which shard holds user data
 */
final class ShardRouter
{
    /** @var array<\PDO> */
    private array $shards;

    public function __construct(array $shards)
    {
        $this->shards = $shards;
    }

    public function getShardForUser(string $userId): \PDO
    {
        // Simple hash-based sharding
        $shardIndex = crc32($userId) % count($this->shards);

        return $this->shards[$shardIndex];
    }
}

6. Консистентность (Consistency)

Как обеспечить согласованность данных в распределенной системе.

  • Strong consistency -- все читают одинаковые данные
  • Eventual consistency -- данные станут согласованными со временем
  • Causal consistency -- сохраняется причинно-следственный порядок

7. Event-Driven архитектура

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

<?php

declare(strict_types=1);

/**
 * Synchronous vs Event-driven communication
 */

// Synchronous: direct call, wait for response
// + Simple to understand
// + Immediate feedback
// - Tight coupling
// - Cascading failures

// Event-driven: publish event, consumers react
// + Loose coupling
// + Independent scaling
// + Resilient to failures
// - Eventual consistency
// - Harder to debug

/**
 * Event-driven order processing
 */
final class OrderCreatedEvent
{
    public function __construct(
        public readonly string $orderId,
        public readonly string $userId,
        public readonly float $total,
        public readonly \DateTimeImmutable $createdAt,
    ) {}
}

// Different services react to the same event independently
// OrderService publishes OrderCreated
// InventoryService: reserves items
// PaymentService: processes payment
// NotificationService: sends confirmation email
// AnalyticsService: updates metrics

8. Отказоустойчивость (Resilience)

Как система продолжает работать при сбоях компонентов.

Ключевые паттерны:

  • Circuit Breaker -- прекращение вызовов к упавшему сервису
  • Retry with backoff -- повторные попытки с увеличивающейся паузой
  • Bulkhead -- изоляция сбоев в одном компоненте
  • Timeout -- ограничение времени ожидания
  • Fallback -- альтернативный путь при сбое

Как выбрать подход

Дерево решений

<?php

declare(strict_types=1);

/**
 * Decision tree for choosing design approaches
 */
final class DesignDecisionTree
{
    public function recommend(SystemRequirements $req): array
    {
        $recommendations = [];

        // Scaling decision
        if ($req->peakQps > 1000) {
            $recommendations[] = 'Horizontal scaling with load balancer';
        }
        if ($req->peakQps > 10000) {
            $recommendations[] = 'CDN for static content';
            $recommendations[] = 'Caching layer (Redis)';
        }
        if ($req->peakQps > 100000) {
            $recommendations[] = 'Database sharding';
            $recommendations[] = 'Message queues for async processing';
        }

        // Consistency decision
        if ($req->requiresStrongConsistency) {
            $recommendations[] = 'Single-leader replication';
            $recommendations[] = 'Synchronous writes';
        } else {
            $recommendations[] = 'Multi-leader or leaderless replication';
            $recommendations[] = 'Async processing where possible';
        }

        // Availability decision
        if ($req->availabilityTarget >= 99.99) {
            $recommendations[] = 'Multi-region deployment';
            $recommendations[] = 'Active-active configuration';
            $recommendations[] = 'Circuit breakers on all external calls';
        }

        return $recommendations;
    }
}

Принцип KISS

Начинайте с простейшего решения, которое удовлетворяет текущим требованиям. Усложняйте только когда простое решение перестает работать.

Эволюция типичной системы:

Этап Пользователей Архитектура
MVP 100 Один сервер, SQL
Рост 10K + Cache, Read replicas
Масштаб 100K + LB, Horizontal scaling, CDN
Зрелость 1M+ + Sharding, Microservices, MQ

Как изучать подходы

Рекомендуемый порядок

  1. Масштабирование -- фундамент всего
  2. Кэширование -- самый эффективный способ улучшить производительность
  3. Балансировка -- необходима для горизонтального масштабирования
  4. Репликация и шардирование -- для роста данных
  5. Консистентность -- для корректности
  6. Event-Driven -- для сложных систем
  7. Отказоустойчивость -- для production-ready систем

Каждый из этих подходов подробно рассмотрен в следующих главах.

Выводы

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