Иерархия памяти
Компьютерная память организована в иерархию, где каждый уровень отличается по скорости, объёму и стоимости. Чем быстрее память -- тем она дороже и меньше по объёму.
Регистры 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
}
}
Пропускная способность и объёмы
| Что считаем | Формула | Пример |
|---|---|---|
| Размер текста | ~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
Правила выбора хранилища
- Hot data (частый доступ) -> RAM (Redis, Memcached)
- Warm data (периодический доступ) -> NVMe SSD (PostgreSQL, MongoDB)
- Cold data (редкий доступ) -> HDD или object storage (S3, архивы)
- 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, поэтому потоковая обработка критична для больших данных