Хранилище

Предпосылки: оперативная память (DRAM, row buffer, bandwidth vs latency).

Оперативная память | Устройство flash | Шины и DMA

Регистры, кеши и RAM объединяет одно свойство: при отключении питания данные исчезают. Для выполнения программы это нормально — загрузил, выполнил, результат отдал. Но база заказов на 500 ГБ должна переживать сбои: пользователь нажал «оплатить», транзакция зафиксирована, а через секунду в дата-центре пропало питание. Нужен носитель, который хранит данные без питания — энергонезависимый (non-volatile). Цена такой устойчивости — переход из мира наносекунд в мир миллисекунд или микросекунд.

Рынок делят две технологии одной проблемы. HDD (Hard Disk Drive, жёсткий диск) хранит данные как намагниченные участки на вращающихся пластинах: ёмко и дёшево, но случайный доступ стоит миллисекунды — упирается в механику. SSD (Solid State Drive, твердотельный накопитель) хранит данные в полупроводниковых ячейках без движущихся частей: случайный доступ в 150–200 раз быстрее, но ячейки изнашиваются при записи. Оба на порядки медленнее оперативной памяти. Разница в том, что HDD выставил правила игры на десятилетия — последовательное чтение на сотни раз быстрее случайного — а SSD эти правила переписал, и теперь базы данных живут по-другому. Ограничения HDD первичны: из них выросли архитектурные решения, которые SSD ломает, — поэтому сначала HDD.

HDD: механика вращающихся пластин

Данные в HDD хранятся как намагниченные участки на металлических пластинах (platters), вращающихся на общем шпинделе. Чтение и запись выполняет головка (read/write head), парящая над поверхностью на высоте ~10 нм. Поверхность разделена на концентрические дорожки (tracks), дорожки — на секторы (sectors) размером 512 Б или 4 КБ. Серверный диск содержит 2–5 пластин и вращается на 7200 RPM (Revolutions Per Minute, оборотов в минуту) или 15 000 RPM для высокопроизводительных массивов.

     вид сверху на пластину
  ___________________________
 /     ______sector______    \
|    /  _______________  \    |
|   |  /               \  |   |
|   | |    дорожка 0    | |   |    <-- головка
|   | |    дорожка 1    | |   |        двигается
|   | |    дорожка 2    | |   |        по радиусу
|   |  \ ______________/  |   |
|    \____________________/   |
 \___________________________/
             шпиндель
          (ось вращения)

Чтобы прочитать конкретный сектор, HDD выполняет три физических действия, и каждое стоит времени.

Seek time (время позиционирования) — актуатор физически перемещает головку к нужной дорожке по радиусу пластины. Для серверного диска 7200 RPM: 8–12 мс случайно, 0.5–1 мс до соседней дорожки. Это механическое движение и нижняя граница латентности random I/O.

Rotational latency (задержка вращения) — после того как головка встала на дорожку, нужно дождаться нужного сектора под головкой. В среднем — половина оборота. При 7200 RPM полный оборот занимает 8.33 мс, половина — ~4.17 мс. При 15 000 RPM — ~2 мс.

Transfer time (время передачи) — само чтение данных с поверхности. Для 4 КБ на 7200 RPM — порядка 0.02 мс. Ничтожно по сравнению с seek и rotational latency.

Итого для случайного чтения 4 КБ на 7200 RPM: ~10 мс + ~4 мс + ~0.02 мс ≈ 14 мс. Для сравнения: чтение из DRAM — ~100 нс, разница ~140 000 раз. Seek и rotational latency доминируют настолько, что размер запроса почти не влияет на время: 4 КБ — ~14 мс, 64 КБ — ~14.3 мс, 256 КБ — ~15.4 мс. Почти вся латентность — ожидание, пока головка доберётся до места. Отсюда и правило HDD-ориентированных систем: если уж заплатил ~14 мс за позиционирование — прочитай побольше за один визит.

IOPS: сколько операций в секунду

Если одна случайная операция стоит ~14 мс, максимум случайных операций в секунду: 1000 мс / 14 мс ≈ 70 IOPS (Input/Output Operations Per Second — операций ввода-вывода в секунду). На практике серверный диск 7200 RPM выдаёт 75–100 IOPS random, 15 000 RPM — 180–210.

База данных обрабатывает запрос, и планировщик читает 1000 случайных страниц по 8 КБ (поиск по индексу, когда нужные строки разбросаны по диску). На HDD 7200 RPM это 1000 / 75 ≈ 13 секунд чистого ожидания I/O — один пользовательский запрос. Если в систему приходит 100 таких запросов в секунду, одного диска не хватает даже на один запрос за секунду. До эпохи SSD продакшен жил на RAID-массивах: RAID 10 из 8 дисков суммирует IOPS до 8 × 75 = 600 — позволяет обслуживать несколько запросов параллельно, но требует 8 дисков ценой половины ёмкости на зеркалирование.

Случайная запись подчиняется той же арифметике: seek + rotation + transfer, те же ~14 мс и ~70–100 IOPS. Но с одним нюансом. Системный вызов fsync — команда «действительно записать на диск, не держать в буфере», которую базы используют, чтобы данные пережили сбой питания — заставляет HDD дождаться, пока сектор физически окажется на пластине. С включённым аппаратным буфером запись принимается за ~0.1 мс, но содержимое буфера теряется при сбое. Честный fsync — ~14 мс. Журнал упреждающей записи базы (см. WAL — журнал упреждающей записи) требует именно честного fsync, поэтому latency коммита на HDD ≈ 14 мс, а транзакций в секунду — около семидесяти на один диск.

Последовательный доступ: другая арифметика

Если читать данные подряд, seek выполняется один раз, дальше головка остаётся на месте и поток идёт напрямую. Скорость последовательного чтения на HDD 7200 RPM — 150–200 МБ/с с внешних (быстрых) дорожек, 80–100 МБ/с с внутренних. Контраст со случайным: 75 IOPS × 4 КБ = 300 КБ/с против 150 МБ/с — разница около 500 раз. Это ещё более крутая версия того, что видно в RAM на row buffer hit/miss: там разница в 10–30 раз, потому что DRAM переключает строки электронно, а HDD — механически.

Этот разрыв определил архитектуру хранения данных на десятилетия. Log-structured подход в базах (последовательная дозапись изменений вместо перезаписи на месте, включая WAL — журнал упреждающей записи и log-structured merge tree), физическое упорядочивание таблицы по ключу кластерного индекса, выбор между sequential scan и index scan — всё это существует потому, что на HDD последовательное чтение на два-три порядка быстрее случайного.

Почему внешние дорожки быстрее внутренних? Пластина вращается с постоянной угловой скоростью — значит за один оборот под головкой проходит больше данных на длинной (внешней) дорожке, чем на короткой (внутренней). С зонной записью (Zoned Bit Recording, ZBR) внешние зоны содержат больше секторов на дорожку. Файловые системы и ОС об этом знают: «начало диска» (низкие LBA (Logical Block Address — логический адрес блока)) соответствует внешним, быстрым дорожкам.

I/O scheduler: попытка спрятать seek

400 КБ/с против 150 МБ/с — разрыв колоссальный. На сервере редко бывает один запрос: в очереди стоят десятки. Ими можно торговать, чтобы сократить суммарный seek. ОС не обязана передавать запросы диску в том порядке, в котором приложения их отправили. Планировщик ввода-вывода (I/O scheduler) переупорядочивает очередь, чтобы минимизировать суммарное перемещение головки — как лифт, который двигается в одном направлении, обслуживая все этажи по пути, потом разворачивается. Реальный IOPS может вырасти в 1.5–3 раза по сравнению с наивным FIFO. В Linux для HDD используется mq-deadline: балансирует переупорядочивание и гарантию, что ни один запрос не ждёт дольше настроенного таймаута.

Для SSD переупорядочивание полезно куда меньше — головки нет, seek не существует. Поэтому Linux для NVMe-дисков обычно использует none: запросы передаются устройству напрямую. Проверить текущий планировщик: cat /sys/block/nvme0n1/queue/scheduler. Исключение — нагрузки с жёсткими требованиями к tail latency, где mq-deadline даёт предел времени ожидания на отдельный запрос.

SSD: убираем механику

У SSD нет пластин, головок и вращения. Данные хранятся в NAND flash — полупроводниковой памяти, где каждая ячейка удерживает заряд годами без питания. Чтение — электрический процесс: подать напряжение, измерить ток, определить бит. Случайное чтение одной страницы (4–16 КБ) — 50–100 мкс, в 150–200 раз быстрее HDD. При этом латентность почти не зависит от адреса: страница в начале и в конце диска читаются за одно и то же время — понятий «близко» и «далеко» больше нет.

Те же 1000 случайных чтений по 8 КБ: 1000 × 75 мкс = 75 мс. Запрос, который на HDD занимал 13 секунд, выполняется за время моргания глаза. IOPS: один канал выдаёт ~13 000 операций в секунду, но контроллер работает с 8–16 каналами параллельно, отсюда 75 000–100 000 IOPS для SATA SSD и 500 000–1 000 000 IOPS для NVMe. Внутренний параллелизм определяет IOPS так же, как банки и ранги определяют пропускную способность DRAM: множество независимых блоков работают одновременно, контроллер мультиплексирует запросы.

Последовательное чтение тоже быстрее: SATA SSD — до 550 МБ/с (упирается в интерфейс), NVMe SSD — 3–7 ГБ/с, PCIe 5.0 — до 14 ГБ/с. Но ключевое отличие от HDD в другом: разрыв между последовательным и случайным на SSD — всего в 2–5 раз, а не в сотни. Случайное чтение больше не катастрофа.

Именно это сломало старые правила баз данных. На HDD планировщик PostgreSQL предпочитал sequential scan даже при чтении небольшой доли строк, потому что случайный IOPS стоил в сотни раз дороже последовательного. На SSD index scan выигрывает при значительно меньшей селективности. Конфигурационный параметр random_page_cost в PostgreSQL по умолчанию = 4.0 (предполагает HDD: случайное чтение в 4 раза дороже последовательного с учётом кеша страниц ОС). Для SSD рекомендуется 1.1–1.5. Неправильное значение — и планировщик выбирает sequential scan вместо index scan, а запрос вместо 5 мс занимает 500 мс.

Скрытая цена: три операции одной памяти

Скорость чтения SSD далась не бесплатно. Внутри NAND не две операции, как на HDD (read / write), а три — и характеристики у них радикально разные. Чтение одной страницы быстрее всех — 50–100 мкс. Запись в два раза медленнее чтения: 200–500 мкс, плюс критическое ограничение — записать можно только в страницу, которую уже стёрли. Переписать «поверх» нельзя. Стирание — самая медленная операция, 2–5 мс, и стирается не отдельная страница, а блок из 128–512 страниц целиком. Стереть одну страницу невозможно в принципе — такова физика NAND.

Из этой асимметрии сразу два следствия. Первое: для обновления 4 КБ данных недостаточно записать новую версию «на старое место» — нужно где-то найти уже стёртую страницу, записать туда, а старую пометить как устаревшую. Второе: рано или поздно свободные стёртые страницы кончаются, и SSD должен стирать блоки — но внутри блока почти всегда есть живые данные, которые приходится сначала перенести в другой блок. Эта фоновая работа увеличивает реальный объём записи в несколько раз по сравнению с тем, что попросила ОС — явление называется write amplification (усиление записи). Она же — главная причина tail latency: средняя латентность чтения 75 мкс, но если операция попала в момент фоновой уборки, ждёт 2–5 мс — в 20–50 раз дольше среднего.

Контроллер SSD прячет всю эту сложность за слоем, который ОС видит как обычный блочный диск. Устройство этого слоя — таблица маппинга, сборка мусора, TRIM, выравнивание износа, многоканальный параллелизм — в Устройство flash. Дальше — только те следствия, которые влияют на выбор носителя и настройку систем, работающих поверх.

NVMe vs SATA: один и тот же NAND, разный интерфейс

Первые SSD подключались через интерфейс SATA (Serial ATA, последовательный ATA), спроектированный для HDD. SATA — последовательная шина до 600 МБ/с с протоколом AHCI (Advanced Host Controller Interface), поддерживающим одну очередь команд глубиной 32. Для HDD с его 100 IOPS очередь на 32 — с запасом. Для SSD на 500 000 IOPS — узкое горлышко.

NVMe (Non-Volatile Memory Express, «экспресс-доступ к энергонезависимой памяти») — протокол, разработанный специально для flash. Работает поверх шины PCIe напрямую, без посредников. Пропускная способность PCIe 4.0 x4 — до 8 ГБ/с, PCIe 5.0 x4 — до 16 ГБ/с (13–26 раз больше SATA). Очереди: NVMe поддерживает до 65 535 очередей по 65 535 команд в каждой — каждое ядро CPU может иметь собственные submission queue и completion queue, без конкуренции за общий ресурс. На сервере с 64 ядрами SATA с единственной очередью упирается в конкуренцию ядер за вход; NVMe масштабируется линейно. Протокольные накладные расходы: ~2.5 мкс у NVMe против ~6 мкс у AHCI. Тот же NAND-чип, подключённый через SATA, отдаёт 550 МБ/с и ~50 000 IOPS; через NVMe — 3–7 ГБ/с и 500 000–1 000 000 IOPS. Аналогия: магистральная труба, подключённая через садовый шланг — давление есть, расход ограничен шлангом.

Форм-фактор NVMe — обычно M.2 (компактная плата 22×80 мм, в разъём на материнской плате) или U.2 (2.5-дюймовый корпус с кабелем, для серверов). Важно не путать форм-фактор с протоколом: существуют M.2-диски с SATA-интерфейсом (550 МБ/с) и M.2-диски с NVMe (3–7 ГБ/с). Одинаковый разъём — радикально разная производительность.

Целостность при сбое питания

Энергонезависимость NAND не означает автоматическую защиту от потери данных при сбое. В момент записи данные проходят три стадии: попадают в DRAM-буфер контроллера SSD, затем записываются на flash-страницу, затем обновляется таблица маппинга. Если питание пропадёт между первой и третьей стадией, данные могут быть записаны частично, а таблица маппинга остаться в неконсистентном состоянии. Серверные SSD оснащены конденсаторами (power-loss protection), хранящими достаточно энергии, чтобы при потере внешнего питания контроллер успел сбросить DRAM-буфер на flash — 10–50 мс. Потребительские SSD такой защиты не имеют: на уровне операционной системы ответ на fsync возвращается, но сами данные ещё могут быть в volatile DRAM-буфере контроллера. Для базы на потребительском SSD это реальный риск повреждения при внезапном отключении.

Полная картина: иерархия задержек и выбор носителя

От регистра до HDD выстраивается единая иерархия, и именно она определяет, как конкретная система распределяет данные между уровнями.

Уровень         Латентность     Пропускная способность   Ёмкость
-------         -----------     ----------------------   -------
Регистры        ~0.3 нс (1т)   --                       128 Б
L1 кеш          ~1 нс (3-4т)   > 1 ТБ/с                 32-48 КБ
L2 кеш          ~3-4 нс        ~500 ГБ/с                256 КБ-1 МБ
L3 кеш          ~10-20 нс      ~200 ГБ/с                8-64 МБ
DRAM            ~60-100 нс     30-50 ГБ/с (DDR5)        16-512 ГБ
SSD (NVMe)      ~75 мкс        3-7 ГБ/с                 0.5-8 ТБ
SSD (SATA)      ~75 мкс        0.55 ГБ/с                0.25-4 ТБ
HDD (7200)      ~14 мс         0.15-0.2 ГБ/с (seq)      1-20 ТБ

Каждый переход вниз — латентность растёт на один-два порядка, ёмкость растёт, стоимость за гигабайт падает. DRAM → SSD — в 750 раз. SSD → HDD — около 200 раз. Суммарно регистр → HDD — около 50 000 000 раз. Стоимость за ГБ движется наоборот: DRAM 0.05–0.10, HDD $0.01–0.02. Каждый следующий уровень дешевле на порядок, и именно эта экономика определяет, почему иерархия существует: если бы SRAM стоил как HDD, никаких уровней не понадобилось бы.

В продакшене HDD и SSD чаще всего работают вместе. SSD держит горячие данные: индексы, активные таблицы, WAL-журнал, метаданные файловой системы — всё, где критичен random IOPS и низкая латентность. HDD обслуживает холодные: архивы, бэкапы, аналитические логи, которые читаются последовательно и редко — стоимость за терабайт у HDD в 5–10 раз ниже. Типичная сборка сервера БД: 2–4 NVMe SSD в RAID под активную базу, массив HDD под бэкапы и WAL-архивы. Распределённые системы строят hot/cold tier: последние 7 дней — на SSD, история за годы — на HDD.

Выбор носителя виден на числах. База получает 10 000 запросов в секунду, каждый порождает 5 случайных чтений (поиск по индексу + чтение страницы) — 50 000 random IOPS. На HDD 7200 RPM это 500 дисков только для чтения. На одном NVMe SSD с 500 000 IOPS — 10% нагрузки. Разница в инфраструктуре: стойка серверов с HDD-массивами против одного диска за 150 долларов. На практике буферный кеш базы и page cache ОС обслуживают 90–99% чтений из RAM, и до диска доходит лишь малая доля. Но даже оставшийся 1% при 10 000 QPS — это 500 random IOPS; на одном HDD это секунды ожидания в хвосте распределения. SSD обслуживает промахи кеша за микросекунды, делая хвост предсказуемым.

Практические следствия для настройки:

  • Заполненность SSD критически влияет на запись. При 90%+ свободных стёртых блоков почти нет, write amplification растёт, производительность падает в 2–5 раз. Серверные SSD резервируют 10–28% ёмкости (over-provisioning) — недоступные ОС, но доступные контроллеру для фоновой уборки; это ключевая защита от деградации.
  • Tail latency при fsync. Средний ответ 75 мкс, но 99.9-й перцентиль может быть в 20–50 раз выше — если операция попала в момент стирания блока. Серверные SSD ограничивают длительность внутренних пауз и указывают 99.99-й перцентиль в спецификации (обычно ~500 мкс при среднем ~100 мкс). Для нагрузок, где tail важнее среднего, — обязательный параметр выбора.
  • random_page_cost=1.1–1.5 для SSD вместо дефолтного 4.0. Без этого планировщик ошибочно выбирает sequential scan.

Передача данных между устройством и RAM

NVMe SSD выдаёт 500 000 IOPS и 7 ГБ/с, но эти данные должны физически попасть с контроллера SSD в оперативную память. Если CPU копирует каждый байт сам, передача 4 КБ займёт сотни load/store инструкций, а при 500 000 IOPS целое ядро уходит только на перекладывание данных. Нужен механизм, позволяющий устройствам писать в RAM напрямую, без участия процессора. Он называется DMA (Direct Memory Access, прямой доступ к памяти) — по какому физическому пути идут при этом команды и данные, разбирается в следующей заметке.

Sources

  • John L. Hennessy, David A. Patterson, 2017, Computer Architecture: A Quantitative Approach — 6th edition, Appendix D: Storage Systems
  • Remzi H. Arpaci-Dusseau, Andrea C. Arpaci-Dusseau, Operating Systems: Three Easy Pieces — Chapter 37: Hard Disk Drives, Chapter 44: Flash-based SSDs (ostep.org)
  • NVM Express, 2024, NVM Express Base Specification, Revision 2.1 — nvmexpress.org
  • smartctl -a /dev/sda — SMART-данные и wear level текущего диска

Оперативная память | Устройство flash | Шины и DMA