MidПрактика16 min

Долгосрочная подготовка

План подготовки к System Design интервью на 3-6 месяцев: что изучать, как практиковаться, ресурсы и стратегия

Философия подготовки

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

Три столпа подготовки

Столп Описание Доля времени
Теория Фундаментальные концепции 30%
Практика Решение задач, mock interviews 40%
Опыт Работа с реальными системами 30%

План на 6 месяцев

Месяц 1-2: Фундамент

Цель: Освоить базовые концепции распределенных систем.

Темы для изучения:

  • Сетевые протоколы (HTTP, TCP, WebSocket)
  • Базы данных (SQL, NoSQL, когда что)
  • Кэширование (уровни, стратегии инвалидации)
  • Балансировка нагрузки (алгоритмы)
  • Очереди сообщений (RabbitMQ, Kafka -- концепции)
<?php

declare(strict_types=1);

/**
 * Month 1-2: Build foundation by implementing core patterns
 *
 * Exercise: Implement a simple in-memory cache with TTL
 * This teaches: cache eviction, time management, memory limits
 */
final class SimpleCache
{
    /** @var array<string, CacheEntry> */
    private array $entries = [];

    private int $maxSize;

    public function __construct(int $maxSize = 1000)
    {
        $this->maxSize = $maxSize;
    }

    public function get(string $key): mixed
    {
        if (!isset($this->entries[$key])) {
            return null;
        }

        $entry = $this->entries[$key];

        // Check TTL
        if ($entry->isExpired()) {
            unset($this->entries[$key]);
            return null;
        }

        // Update access time for LRU
        $entry->lastAccessed = time();

        return $entry->value;
    }

    public function set(string $key, mixed $value, int $ttlSeconds = 3600): void
    {
        // Evict if at capacity
        if (count($this->entries) >= $this->maxSize && !isset($this->entries[$key])) {
            $this->evictLRU();
        }

        $this->entries[$key] = new CacheEntry(
            value: $value,
            expiresAt: time() + $ttlSeconds,
            lastAccessed: time(),
        );
    }

    /**
     * LRU eviction: remove the least recently used entry.
     * In production, use a doubly-linked list for O(1) eviction.
     * Here simplified for learning purposes.
     */
    private function evictLRU(): void
    {
        $oldestKey = null;
        $oldestTime = PHP_INT_MAX;

        foreach ($this->entries as $key => $entry) {
            if ($entry->lastAccessed < $oldestTime) {
                $oldestTime = $entry->lastAccessed;
                $oldestKey = $key;
            }
        }

        if ($oldestKey !== null) {
            unset($this->entries[$oldestKey]);
        }
    }
}

final class CacheEntry
{
    public function __construct(
        public readonly mixed $value,
        public readonly int $expiresAt,
        public int $lastAccessed,
    ) {}

    public function isExpired(): bool
    {
        return time() > $this->expiresAt;
    }
}
### Месяц 3-4: Углубление

Цель: Изучить продвинутые паттерны и решить 10-15 задач.

Темы:

  • Шардирование и репликация
  • Консистентность (CAP, eventual consistency)
  • Event-driven архитектура
  • Микросервисы vs монолит
  • CDN и географическое распределение

Практика:

Решайте по 2-3 задачи в неделю. Формат:

  1. Таймер на 45 минут
  2. Решаете вслух (записывайте голос или видео)
  3. Сравниваете с эталонным решением
  4. Записываете, что упустили
<?php

declare(strict_types=1);

/**
 * Month 3-4: Practice designing components
 *
 * Exercise: Implement consistent hashing
 * This teaches: data distribution, rebalancing, virtual nodes
 */
final class ConsistentHashRing
{
    /** @var array<int, string> hash => node */
    private array $ring = [];

    /** @var array<int> sorted hashes */
    private array $sortedHashes = [];

    public function __construct(
        private readonly int $virtualNodes = 150,
    ) {}

    public function addNode(string $node): void
    {
        for ($i = 0; $i < $this->virtualNodes; $i++) {
            $hash = $this->hash(sprintf('%s:%d', $node, $i));
            $this->ring[$hash] = $node;
            $this->sortedHashes[] = $hash;
        }

        sort($this->sortedHashes);
    }

    public function removeNode(string $node): void
    {
        for ($i = 0; $i < $this->virtualNodes; $i++) {
            $hash = $this->hash(sprintf('%s:%d', $node, $i));
            unset($this->ring[$hash]);
        }

        $this->sortedHashes = array_values(array_filter(
            $this->sortedHashes,
            fn(int $h) => isset($this->ring[$h]),
        ));
    }

    /**
     * Get the node responsible for a given key.
     * Finds the first node clockwise from the key's hash.
     */
    public function getNode(string $key): ?string
    {
        if (empty($this->sortedHashes)) {
            return null;
        }

        $hash = $this->hash($key);

        // Binary search for the first hash >= key hash
        foreach ($this->sortedHashes as $nodeHash) {
            if ($nodeHash >= $hash) {
                return $this->ring[$nodeHash];
            }
        }

        // Wrap around to the first node
        return $this->ring[$this->sortedHashes[0]];
    }

    private function hash(string $key): int
    {
        return crc32($key);
    }
}
### Месяц 5-6: Продвинутый уровень

Цель: Освоить сложные сценарии и научиться уверенно вести интервью.

Темы:

  • Distributed transactions (Saga, 2PC)
  • Rate limiting (алгоритмы, распределенная реализация)
  • Consistency patterns (Raft consensus, vector clocks)
  • Observability (distributed tracing, metrics)
  • Cost estimation (cloud pricing models)

Практика:

  • Mock interviews с другими людьми (2-3 в месяц)
  • Запись и разбор своих ответов
  • Решение задач с таймером
<?php

declare(strict_types=1);

/**
 * Month 5-6: Advanced patterns
 *
 * Exercise: Implement distributed rate limiter using Redis
 * with sliding window algorithm
 */
final class DistributedRateLimiter
{
    public function __construct(
        private readonly \Redis $redis,
        private readonly int $maxRequests,
        private readonly int $windowSeconds,
    ) {}

    /**
     * Sliding window counter using Redis sorted sets.
     *
     * Algorithm:
     * 1. Remove expired entries (older than window)
     * 2. Count current entries
     * 3. If under limit, add new entry
     * 4. All in a single pipeline (atomic-ish)
     */
    public function attempt(string $identifier): RateLimitResult
    {
        $key = sprintf('ratelimit:%s', $identifier);
        $now = microtime(true);
        $windowStart = $now - $this->windowSeconds;

        // Lua script for atomic check-and-add
        $script = <<<'LUA'
            local key = KEYS[1]
            local window_start = tonumber(ARGV[1])
            local now = tonumber(ARGV[2])
            local max_requests = tonumber(ARGV[3])
            local window = tonumber(ARGV[4])

            -- Remove expired entries
            redis.call('ZREMRANGEBYSCORE', key, '-inf', window_start)

            -- Count current entries
            local current = redis.call('ZCARD', key)

            if current < max_requests then
                -- Add new entry
                redis.call('ZADD', key, now, now .. ':' .. math.random(1000000))
                redis.call('EXPIRE', key, window)
                return {1, max_requests - current - 1}
            else
                return {0, 0}
            end
        LUA;

        $result = $this->redis->eval(
            $script,
            [$key, (string) $windowStart, (string) $now, $this->maxRequests, $this->windowSeconds],
            1,
        );

        return new RateLimitResult(
            allowed: (bool) $result[0],
            remaining: (int) $result[1],
            resetAt: (int) ceil($now) + $this->windowSeconds,
        );
    }
}

final readonly class RateLimitResult
{
    public function __construct(
        public bool $allowed,
        public int $remaining,
        public int $resetAt,
    ) {}
}
## Ежедневная рутина

Минимальная ежедневная практика (30 минут)

Активность Время
Чтение статьи о distributed systems 15 мин
Мини-задача (один компонент) 15 мин

Расширенная практика (1-2 часа в выходные)

Активность Время
Полная задача с таймером 45 мин
Разбор решения и заметки 30 мин
Чтение engineering blog 15 мин

Что читать

Книги

Книга Что дает
Designing Data-Intensive Applications Фундаментальные концепции
System Design Interview (Alex Xu) Конкретные задачи и решения
Building Microservices (Sam Newman) Паттерны микросервисов
Database Internals (Alex Petrov) Глубокое понимание БД

Engineering блоги

  • Netflix Tech Blog -- масштабирование и resilience
  • Uber Engineering -- real-time и geo-системы
  • Stripe Engineering -- платежи и consistency
  • Cloudflare Blog -- сетевая инфраструктура
  • Meta Engineering -- социальные графы и feed

Трекер прогресса

<?php

declare(strict_types=1);

/**
 * Track your preparation progress
 */
final class PreparationTracker
{
    // Core topics to master
    public const TOPICS = [
        'networking' => ['HTTP', 'TCP', 'DNS', 'CDN', 'WebSocket'],
        'storage' => ['SQL', 'NoSQL', 'Object storage', 'Time-series DB'],
        'caching' => ['Cache-aside', 'Write-through', 'Invalidation', 'CDN cache'],
        'messaging' => ['Message queues', 'Pub/Sub', 'Event streaming'],
        'scaling' => ['Horizontal', 'Vertical', 'Sharding', 'Replication'],
        'reliability' => ['Circuit breaker', 'Retry', 'Bulkhead', 'Fallback'],
        'consistency' => ['ACID', 'CAP', 'Eventual', 'Strong'],
        'security' => ['Auth', 'Rate limiting', 'Encryption', 'CORS'],
    ];

    // System design problems to practice (minimum 20)
    public const PROBLEMS = [
        'URL Shortener',
        'Pastebin',
        'Instagram / Photo Sharing',
        'Twitter / News Feed',
        'Chat System (WhatsApp)',
        'YouTube / Video Streaming',
        'Dropbox / File Storage',
        'Web Crawler',
        'Notification System',
        'Rate Limiter',
        'Search Autocomplete',
        'Nearby Places (Yelp)',
        'Uber / Ride Sharing',
        'Ticketmaster / Booking',
        'Payment System',
        'Metrics / Monitoring',
        'Distributed Cache',
        'Message Queue',
        'Collaborative Editor',
        'Ad Click Counter',
    ];
}
## Ошибки при подготовке

1. Заучивание вместо понимания

Если вы заучили решение для URL Shortener, но не понимаете, почему выбран Base62, вы не сможете адаптировать решение при изменении требований.

2. Только теория без практики

Чтение книг -- необходимо, но недостаточно. Без практики с таймером и mock interviews знания не конвертируются в навык.

3. Игнорирование soft skills

System Design -- это 50% технические знания и 50% коммуникация. Тренируйте объяснение решений вслух.

4. Перфекционизм

Не нужно знать все до идеала. Достаточно уверенно обсуждать ключевые концепции и честно говорить о границах своих знаний.

Выводы

Долгосрочная подготовка -- это марафон, а не спринт. Постоянная практика по 30 минут в день эффективнее, чем интенсив перед интервью. Инвестируйте в глубокое понимание принципов -- это навык на всю карьеру.