EasyТеория13 min

Оперативная память и хранилища

Иерархия памяти, сравнение HDD vs SSD vs NVMe, latency numbers и их влияние на архитектуру систем

Иерархия памяти

Компьютерная память организована в иерархию, где каждый уровень отличается по скорости, объёму и стоимости. Чем быстрее память -- тем она дороже и меньше по объёму.

Регистры CPU     ~1 нс     ~1 КБ      $$$$$$
     |
L1 Cache         ~1 нс     32-64 КБ   $$$$$
     |
L2 Cache         ~4 нс     256 КБ-1 МБ $$$$
     |
L3 Cache         ~10 нс    8-64 МБ     $$$
     |
RAM (DDR5)       ~100 нс   16-512 ГБ   $$
     |
NVMe SSD         ~16 мкс   1-8 ТБ      $
     |
SATA SSD         ~100 мкс  1-8 ТБ      $
     |
HDD              ~2-10 мс  1-20 ТБ     ¢
     |
Сеть             ~0.5-150 мс  ∞        ¢

Ключевой принцип: Разница между уровнями -- не проценты, а порядки величин. RAM быстрее SSD в 100-1000 раз. SSD быстрее HDD в 100 раз. Это фундаментально влияет на архитектуру систем.

Оперативная память (RAM)

Характеристики

RAM (Random Access Memory) -- энергозависимая память с произвольным доступом. Данные теряются при выключении питания.

Параметр DDR4 DDR5
Частота 2133-3200 МГц 4800-8400 МГц
Пропускная способность до 25.6 ГБ/с до 67.2 ГБ/с
Латентность ~80-100 нс ~80-100 нс
Макс. объём модуля 64 ГБ 128 ГБ
Каналы 1 на модуль 2 на модуль

Виртуальная память

Операционная система создает для каждого процесса иллюзию непрерывного адресного пространства через виртуальную память.

Страницы (Pages):

  • Стандартный размер: 4 КБ
  • Huge Pages: 2 МБ или 1 ГБ
  • TLB (Translation Lookaside Buffer) кэширует трансляцию адресов

Swap:

  • Когда физическая RAM заполнена, ОС перемещает данные на диск
  • Производительность падает катастрофически (RAM -> SSD = 1000x медленнее)
  • В production swap обычно отключают или минимизируют
<?php

declare(strict_types=1);

/**
 * Memory awareness in PHP applications.
 * Understanding memory limits prevents OOM crashes.
 */
final class MemoryMonitor
{
    /**
     * Check current memory usage and proximity to limit.
     *
     * @return array{used_mb: float, limit_mb: float, usage_percent: float}
     */
    public static function getUsage(): array
    {
        $used = memory_get_usage(true);
        $peak = memory_get_peak_usage(true);
        $limit = self::parseMemoryLimit(ini_get('memory_limit') ?: '128M');

        return [
            'used_mb' => round($used / 1024 / 1024, 2),
            'peak_mb' => round($peak / 1024 / 1024, 2),
            'limit_mb' => round($limit / 1024 / 1024, 2),
            'usage_percent' => round(($used / $limit) * 100, 1),
        ];
    }

    /**
     * Process large datasets without loading everything into memory.
     * This is crucial for PHP where memory_limit is typically 128-256MB.
     *
     * @return \Generator<int, array<string, mixed>>
     */
    public static function streamLargeDataset(\PDO $pdo, string $query): \Generator
    {
        // Use unbuffered query to avoid loading all rows into memory
        $pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

        $stmt = $pdo->query($query);

        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            yield $row;
        }

        $pdo->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
    }

    private static function parseMemoryLimit(string $limit): int
    {
        $value = (int) $limit;
        $unit = strtolower(substr($limit, -1));

        return match ($unit) {
            'g' => $value * 1024 * 1024 * 1024,
            'm' => $value * 1024 * 1024,
            'k' => $value * 1024,
            default => $value,
        };
    }
}
## Типы хранилищ

HDD (Hard Disk Drive)

HDD использует вращающиеся магнитные диски и механическую головку чтения/записи.

Характеристики:

  • Последовательное чтение: 100-250 МБ/с
  • Случайное чтение: 0.5-2 МБ/с (главная слабость)
  • Латентность: 2-10 мс (время перемещения головки + ожидание вращения)
  • IOPS: 75-200

Почему HDD медленный для случайного доступа: Головке нужно физически переместиться к нужной дорожке (seek time, ~5-10 мс), а затем дождаться, пока нужный сектор повернётся (rotational latency, ~2-4 мс для 7200 RPM).

SSD (Solid State Drive)

SSD использует флеш-память NAND без движущихся частей.

SATA SSD:

  • Последовательное чтение: 500-560 МБ/с (ограничение SATA интерфейса)
  • Случайное чтение: 30-100 МБ/с
  • Латентность: 25-100 мкс
  • IOPS: 50,000-100,000

NVMe SSD:

  • Последовательное чтение: 3,000-7,000 МБ/с
  • Случайное чтение: 200-1,000 МБ/с
  • Латентность: 10-20 мкс
  • IOPS: 500,000-1,000,000+

Сравнительная таблица

Параметр HDD SATA SSD NVMe SSD
Последовательное чтение 200 МБ/с 550 МБ/с 5,000 МБ/с
Случайное чтение 1 МБ/с 50 МБ/с 500 МБ/с
Латентность 5 мс 100 мкс 15 мкс
IOPS (4K random) 150 80,000 800,000
Стоимость за ТБ $15-30 $50-100 $80-200
Срок службы 3-5 лет 5-10 лет 5-10 лет
Износ при записи Нет Да (NAND деградация) Да

Для System Design: Выбор хранилища зависит от паттерна доступа. HDD подходит для последовательного чтения больших файлов (логи, бэкапы). NVMe SSD -- для баз данных с интенсивным случайным доступом.

Write Amplification и износ SSD

SSD записывает данные страницами (4-16 КБ), но стирает блоками (128 КБ - 4 МБ). Это вызывает write amplification -- физически записывается больше данных, чем логически.

Логическая запись: 4 КБ
Физическая запись: может быть 16-64 КБ (блок нужно прочитать, изменить, записать целиком)
Write Amplification Factor = 4-16x

Это важно для БД с интенсивной записью -- SSD изнашивается быстрее при мелких случайных записях.

Кэш процессора

Принцип локальности

Кэш работает эффективно благодаря двум принципам:

  • Временная локальность: если данные использовались, они скоро понадобятся снова
  • Пространственная локальность: если данные использовались, соседние данные тоже понадобятся

Cache Line

CPU загружает данные из RAM не по одному байту, а кэш-линиями (обычно 64 байта). Это означает, что последовательный обход массива намного быстрее случайного.

<?php

declare(strict_types=1);

/**
 * Cache-friendly data access patterns matter even in PHP.
 * Sequential access is much faster than random due to CPU cache behavior.
 */
final class CacheAwareProcessing
{
    /**
     * Sequential access: cache-friendly.
     * CPU prefetcher predicts the pattern and preloads data.
     */
    public static function processSequential(array $data): float
    {
        $sum = 0.0;
        $count = count($data);

        for ($i = 0; $i < $count; $i++) {
            $sum += $data[$i];
        }

        return $sum;
    }

    /**
     * Random access: cache-unfriendly.
     * Each access likely causes a cache miss.
     */
    public static function processRandom(array $data): float
    {
        $sum = 0.0;
        $count = count($data);
        $indices = range(0, $count - 1);
        shuffle($indices);

        foreach ($indices as $i) {
            $sum += $data[$i];
        }

        return $sum;
    }

    /**
     * Structure of Arrays (SoA) vs Array of Structures (AoS).
     *
     * SoA is more cache-friendly when processing one field at a time.
     */
    public static function demonstrateLayouts(): void
    {
        // AoS: Array of Structures (common but less cache-friendly)
        $users = [
            ['name' => 'Alice', 'age' => 30, 'score' => 95],
            ['name' => 'Bob', 'age' => 25, 'score' => 87],
            // Each row mixes different types in memory
        ];

        // SoA: Structure of Arrays (more cache-friendly for columnar access)
        $names = ['Alice', 'Bob'];
        $ages = [30, 25];
        $scores = [95, 87];
        // Processing all scores touches contiguous memory
    }
}
## Числа для System Design

Пропускная способность и объёмы

Что считаем Формула Пример
Размер текста ~1 байт/символ (ASCII) 1 млн записей x 1 КБ = 1 ГБ
Размер изображения зависит от формата 1 млн фото x 500 КБ = 500 ГБ
Запросов в секунду DAU x запросов/юзер / 86400 10M DAU x 10 = 1157 RPS
Объём за год RPS x размер x 86400 x 365 1000 RPS x 1 КБ = 31.5 ТБ/год

Количество девяток (Availability)

SLA Допустимый downtime/год Допустимый downtime/месяц
99% 3.65 дня 7.3 часа
99.9% 8.77 часа 43.8 минуты
99.99% 52.6 минуты 4.38 минуты
99.999% 5.26 минуты 26.3 секунды
<?php

declare(strict_types=1);

/**
 * Back-of-the-envelope estimation helper.
 * Essential skill for System Design interviews.
 */
final class CapacityEstimator
{
    private const SECONDS_PER_DAY = 86_400;
    private const SECONDS_PER_MONTH = 2_592_000; // 30 days
    private const SECONDS_PER_YEAR = 31_536_000; // 365 days

    /**
     * Estimate storage requirements.
     *
     * @param int $dailyActiveUsers DAU
     * @param int $actionsPerUser Average actions per user per day
     * @param int $bytesPerAction Average bytes per action (record)
     * @param int $retentionYears How many years to keep data
     * @return array{daily_gb: float, monthly_gb: float, total_tb: float}
     */
    public static function estimateStorage(
        int $dailyActiveUsers,
        int $actionsPerUser,
        int $bytesPerAction,
        int $retentionYears = 3,
    ): array {
        $dailyActions = $dailyActiveUsers * $actionsPerUser;
        $dailyBytes = $dailyActions * $bytesPerAction;

        $dailyGb = $dailyBytes / (1024 ** 3);
        $monthlyGb = $dailyGb * 30;
        $totalTb = ($dailyGb * 365 * $retentionYears) / 1024;

        return [
            'daily_gb' => round($dailyGb, 2),
            'monthly_gb' => round($monthlyGb, 2),
            'total_tb' => round($totalTb, 2),
        ];
    }

    /**
     * Estimate required throughput.
     *
     * @return array{avg_rps: int, peak_rps: int, bandwidth_mbps: float}
     */
    public static function estimateThroughput(
        int $dailyActiveUsers,
        int $actionsPerUser,
        int $bytesPerAction,
        float $peakMultiplier = 3.0,
    ): array {
        $totalDaily = $dailyActiveUsers * $actionsPerUser;
        $avgRps = (int) ceil($totalDaily / self::SECONDS_PER_DAY);
        $peakRps = (int) ceil($avgRps * $peakMultiplier);
        $bandwidthMbps = ($peakRps * $bytesPerAction * 8) / (1024 * 1024);

        return [
            'avg_rps' => $avgRps,
            'peak_rps' => $peakRps,
            'bandwidth_mbps' => round($bandwidthMbps, 2),
        ];
    }
}

// Example: Social media platform
// 10M DAU, 20 posts/views per day, 2KB per action, 3 year retention
$storage = CapacityEstimator::estimateStorage(
    dailyActiveUsers: 10_000_000,
    actionsPerUser: 20,
    bytesPerAction: 2048,
    retentionYears: 3,
);
// daily: ~0.37 GB, monthly: ~11.18 GB, total: ~1.31 TB

$throughput = CapacityEstimator::estimateThroughput(
    dailyActiveUsers: 10_000_000,
    actionsPerUser: 20,
    bytesPerAction: 2048,
);
// avg: ~2315 RPS, peak: ~6944 RPS
## Влияние на System Design

Правила выбора хранилища

  1. Hot data (частый доступ) -> RAM (Redis, Memcached)
  2. Warm data (периодический доступ) -> NVMe SSD (PostgreSQL, MongoDB)
  3. Cold data (редкий доступ) -> HDD или object storage (S3, архивы)
  4. Archival data (почти никогда) -> Tape / Glacier

Типичные ошибки

  • Хранить всё в RAM -- дорого и не нужно
  • Использовать HDD для базы данных с random access -- катастрофически медленно
  • Игнорировать write amplification при высокой нагрузке на запись
  • Не учитывать swap -- если swap активен, производительность непредсказуема

Совет для интервью: Всегда начинайте оценку с back-of-the-envelope расчётов. Покажите, что вы можете быстро прикинуть объём данных, RPS и требования к хранилищу. Это демонстрирует зрелость инженера.

Выводы

  • Иерархия памяти -- фундаментальный принцип, влияющий на все уровни системы
  • Разница между уровнями -- порядки величин, а не проценты
  • Выбор хранилища определяется паттерном доступа (последовательный vs случайный)
  • Back-of-the-envelope расчёты -- обязательный навык для System Design
  • PHP-приложения ограничены memory_limit, поэтому потоковая обработка критична для больших данных