ОС · Persistence · 14 мин
Жёсткие диски и планирование запросов
Зачем backend-инженеру понимать железку из прошлого века
Кажется, что HDD (Hard Disk Drive — «жёсткий диск», тот самый «винчестер» с крутящимися блинами внутри) — это устаревшее железо. Зачем его изучать в 2026, когда везде SSD и облака? Ответ простой: именно физика HDD объясняет, почему базы данных, файловые системы и логи устроены так, как они устроены. Привычка «писать последовательно, а не вразнобой» родилась из боли работы с жёстким диском. И эта привычка пережила сам диск — она до сих пор сидит в Postgres, Kafka, LSM-деревьях и журналах файловых систем (см. журналирование и LFS).
Эта глава — продолжение разговора про устройства ввода-вывода (io-devices). Там мы говорили, как ОС вообще общается с железом. Здесь — что за железо такое HDD и почему обращаться к нему надо с умом.
Геометрия: что крутится внутри коробочки
Представьте старый виниловый проигрыватель. Крутится пластинка, над ней — игла на подвижном рычаге. Чтобы услышать нужную песню, надо подвести иглу к нужной дорожке и дождаться, пока она докрутится до начала трека. HDD устроен почти так же, только «песни» — это ваши данные, а «иголка» читает магнитные намагниченности.
Разберём детали по словам:
- Пластина (platter) — круглый магнитный диск. Их в корпусе обычно несколько, они насажены на общую ось и крутятся вместе.
- Шпиндель (spindle) — ось, на которой всё крутится. Скорость вращения мерят в RPM (Rotations Per Minute — оборотов в минуту): типичные 7200 RPM, у быстрых серверных — 15000 RPM.
- Дорожка (track) — узкое концентрическое кольцо на пластине. На одной пластине их сотни тысяч. Это как одна бороздка на виниле, только замкнутая в круг.
- Сектор (sector) — кусочек дорожки, минимальная единица чтения/записи. Исторически 512 байт, у современных дисков 4 КБ. Диск не умеет прочитать «полсектора» — только сектор целиком.
- Головка (head) — то, что читает и пишет, парит над пластиной на высоте в доли микрона. Сидит на подвижном рычаге.
Вид на одну пластину сверху:
________________
/ дорожки \ ← track (кольцо)
/ ____________ \
/ / ______ \ \
| | / () \ | | () = шпиндель (ось вращения)
| | \ __ / | |
\ \ ‾‾‾‾‾‾ / /
\ ‾‾‾‾‾‾‾‾‾‾‾‾ /
\________________ ● / ● = головка на рычаге
\
рычаг подводит головку
к нужной дорожке (→ ←)Что нарисовано: одна пластина — это набор вложенных колец-дорожек. Головка сидит на рычаге сбоку и может двигаться к центру и от центра, чтобы встать над нужной дорожкой. Сама пластина при этом непрерывно вращается.
Три составляющие времени доступа
Чтобы прочитать один сектор, диску надо проделать три вещи по очереди. Это — сердце всей главы, запомните три слова: seek, rotation, transfer.
T_доступа = T_seek + T_rotation + T_transfer
(подвести (дождаться (считать
головку нужного данные)
к дорожке) сектора)
┌──────────────┬──────────────┬───────────┐
│ SEEK │ ROTATION │ TRANSFER │
│ ~3–12 мс │ ~2–6 мс │ доли мс │
│ (самое │ (зависит │ (быстро, │
│ дорогое!) │ от RPM) │ если │
│ │ │ читаем │
│ │ │ подряд) │
└──────────────┴──────────────┴───────────┘
МЕХАНИКА, МЕДЛЕННО ЭЛЕКТРОНИКАРазберём каждую:
- Seek (поиск дорожки) — физическое перемещение рычага с головкой к нужной дорожке. Это механическое движение тяжёлого рычага, поэтому оно самое дорогое: единицы миллисекунд. Аналогия: вы за столом и тянетесь рукой к нужной полке стеллажа. Чем дальше полка — тем дольше тянуться.
- Rotation (вращательная задержка) — головка уже над нужной дорожкой, но нужный сектор сейчас «с другой стороны» пластины, надо дождаться, пока он подъедет под головку. В среднем ждём половину оборота. При 7200 RPM один оборот = 8.3 мс, значит средняя rotation ≈ 4.15 мс.
- Transfer (передача) — собственно чтение/запись данных, пока сектор едет под головкой. Тут диск быстр: десятки–сотни МБ/с.
Ключевой вывод: два из трёх слагаемых — это механика (seek и rotation), и она доминирует. Электроника передаёт данные быстро, но сначала надо дождаться, пока железо физически встанет в нужную позицию.
Маленький расчёт RPM → задержка
Полезная формула для собеса. Один оборот в миллисекундах:
T_оборота (мс) = 60 000 / RPM
7200 RPM → 8.33 мс/оборот → средняя rotation ≈ 4.17 мс
10000 RPM → 6.00 мс/оборот → средняя rotation ≈ 3.00 мс
15000 RPM → 4.00 мс/оборот → средняя rotation ≈ 2.00 мсСредняя rotation — половина оборота, потому что в среднем нужный сектор где-то на полпути.
Главное следствие: random vs sequential
Теперь самое важное, ради чего всё затевалось. Сравним два сценария чтения 1 МБ:
- Sequential (последовательно) — данные лежат подряд, на соседних секторах одной дорожки. Один seek в начале, один раз дождались сектора — и дальше просто льём данные, пока пластина крутится. Механика платится один раз.
- Random (случайно) — нам нужны разбросанные по диску кусочки. Перед каждым маленьким кусочком — новый seek и новая rotation. Механика платится на каждой операции.
SEQUENTIAL: один seek, дальше всё подряд
┌────┬────┬────┬────┬────┬────┬────┐
│ S+R│ ▓▓ │ ▓▓ │ ▓▓ │ ▓▓ │ ▓▓ │ ▓▓ │ ▓ = transfer (дёшево)
└────┴────┴────┴────┴────┴────┴────┘
платим дальше просто читаем поток
механику
1 раз
RANDOM: seek+rotation ПЕРЕД КАЖДЫМ кусочком
┌──────┬──┬──────┬──┬──────┬──┬──────┬──┐
│ S+R │▓ │ S+R │▓ │ S+R │▓ │ S+R │▓ │
└──────┴──┴──────┴──┴──────┴──┴──────┴──┘
дорого дёш дорого ... механика КАЖДЫЙ разЧто нарисовано: при последовательном чтении дорогая «механическая» часть (S+R) оплачивается один раз в начале, дальше идёт дешёвый поток. При случайном — перед каждым крошечным кусочком снова дорогой seek+rotation. Отсюда — пропасть в скорости.
Порядки величин (примерно, обычный HDD 7200 RPM):
| Режим доступа | Что происходит | Пропускная способность |
|---|---|---|
| Sequential read/write | один seek, льём поток | ~150–200 МБ/с |
| Random read/write (4 КБ) | seek+rotation на каждый блок | ~0.5–2 МБ/с |
| Разница | — | в ~100 раз! |
Вот он, фундамент. Случайный доступ к HDD медленнее последовательного примерно в сто раз. Поэтому:
- БД пишут журнал (WAL) последовательно в конец файла, а не правят данные на месте вразнобой.
- Kafka — это по сути «лог, который только дописывают в конец», и потому летает даже на HDD.
- LSM-деревья копят записи в памяти и сбрасывают на диск большими последовательными пачками (см. LFS).
- Файловые системы стараются класть блоки одного файла рядом (см. реализация ФС).
Кстати, у SSD seek и rotation отсутствуют (нет движущихся частей!), поэтому random там в разы ближе к sequential — но и там последовательность всё ещё чуть выгоднее из-за внутренней организации флеша.
Планирование запросов к диску
Раз seek такой дорогой, возникает идея: если в очереди ждут несколько запросов к разным дорожкам, можно переупорядочить их так, чтобы головка двигалась поменьше. Этим занимается планировщик дискового ввода-вывода (I/O scheduler). Это похоже на планирование процессов, только ресурс — не CPU, а движение головки, и цель — минимизировать суммарный seek.
Возьмём один и тот же набор запросов и прогоним через разные политики. Пусть головка стоит на дорожке 53, а в очереди ждут дорожки: 98, 183, 37, 122, 14, 124, 65, 67.
FIFO (FCFS) — в порядке прихода
Самое простое: обслуживаем в том порядке, в каком запросы пришли. Честно, но головка может метаться туда-сюда.
Дорожки: 0 37 53 65 67 98 122 124 183
|····|····●····|··|·····|····|···|·······|
старт ───────────── 53
53 →98 →183 →37 →122 →14 →124 →65 →67 (порядок прихода)
Движение головки (зигзаги = потерянное время):
53 →→→→→→→→ 98
98 →→→→→→→→→→→→→ 183
183 ←←←←←←←←←←←←←←←←←←←← 37 (большой откат назад!)
37 →→→→→→→→→ 122
122 ←←←←←←←←←←←← 14
14 →→→→→→→→→→→→ 124
124 ←←←←←← 65
65 →→ 67Что нарисовано: головка прыгает то вперёд, то далеко назад (183 → 37 — почти через весь диск). Каждый прыжок — это дорогой seek. FIFO ничего не оптимизирует.
SSTF — ближайший первым
SSTF (Shortest Seek Time First) — всегда обслуживаем тот запрос, чья дорожка ближе к текущему положению головки. Жадная стратегия «бери ближайшее».
старт 53. Ближайшие по очереди:
53 →65 →67 →98 →122 →124 →183, потом ←37 →←14
53 → 65 → 67 → 98 → 122 → 124 → 183 (идём вправо, малыми шагами)
└──── затем большой откат влево
183 ←←←←←←←←←←←←←←← 37 → 14SSTF резко сокращает суммарный seek по сравнению с FIFO. Но есть беда — голодание (starvation): если головка крутится в плотном «облаке» близких запросов, далёкий запрос (например, на дорожке 14) может ждать бесконечно, пока рядом всё время появляются новые близкие. Это та же проблема, что у SJF в планировании CPU: жадность по «ближнему» обижает «дальних».
SCAN («лифт») — едем до края и разворачиваемся
SCAN (он же elevator, «лифт») лечит голодание. Головка едет в одну сторону (скажем, к большим номерам), обслуживая всё по пути, доезжает до края, потом разворачивается и едет обратно, обслуживая остальных. Как лифт: едет вверх, собирает всех, кому вверх, потом едет вниз.
старт 53, едем ВПРАВО до края, потом ВЛЕВО:
0 ........ 14 ... 37 .. 53 .. 65 67 . 98 . 122 124 ... 183 [край]
●───────────────────────────────→ (вправо)
←─────────────────────────────────────────────────────── (разворот)
обслужили 37 и 14 на обратном путиЧто нарисовано: головка проходит диск как лифт — сначала в одну сторону до упора, затем в другую. Никто не голодает: при развороте мы гарантированно вернёмся за всеми. Суммарный seek хороший, и он предсказуемый.
C-SCAN — круговой лифт, ради справедливости
У обычного SCAN есть перекос: дорожки в середине обслуживаются чаще краёв (головка проходит середину дважды за цикл — туда и обратно). C-SCAN (Circular SCAN) делает иначе: доехав до края, головка быстро перескакивает в начало (без обслуживания по дороге) и снова едет в ту же сторону. Получается, что все дорожки обслуживаются «по кругу» и одинаково честно — задержки распределяются равномернее.
SCAN: →→→→→→→→| разворот |←←←←←←←← (середина проходится 2 раза)
C-SCAN: →→→→→→→→| прыжок в начало | →→→→→→→→ (всегда в одну сторону)
└─ быстрый возврат, без чтения ─┘Сравнение политик
| Политика | Идея | Плюс | Минус |
|---|---|---|---|
| FIFO | как пришло | честность, простота | головка мечется, большой seek |
| SSTF | ближайший первым | малый суммарный seek | голодание дальних |
| SCAN | лифт до края и назад | нет голодания, малый seek | середина обслуживается чаще |
| C-SCAN | круговой лифт | равномерные задержки | холостой прыжок в начало |
Важная оговорка для собеса: современные диски сами знают свою точную геометрию (где какой сектор физически лежит), поэтому реальное планирование часто живёт в прошивке диска (он умеет учитывать не только seek, но и rotation — это называют SPTF, Shortest Positioning Time First). ОС же группирует и сливает соседние запросы (merging) и решает, что и когда отдать диску. Так что seek-планирование — это командная работа ОС и контроллера.
Какая составляющая времени доступа к HDD обычно самая дорогая?
Почему случайный доступ к HDD на порядки медленнее последовательного?
Диск вращается со скоростью 7200 RPM. Чему примерно равна средняя вращательная задержка (rotation), в миллисекундах? Округлите до целого.
В чём главный недостаток политики SSTF (ближайший первым)?
Какие политики планирования борются с голоданием дальних запросов за счёт движения головки «как лифт»?
Чем C-SCAN отличается от обычного SCAN?
Что спрашивают на собесе
- Назовите три составляющие времени доступа к HDD и какая из них самая дорогая (seek и rotation — механика, transfer — быстрый; seek обычно самый дорогой).
- Почему случайный доступ к HDD на порядки медленнее последовательного и как это повлияло на дизайн БД и логов (WAL, Kafka, LSM — всё пишется последовательно).
- Как из RPM посчитать среднюю вращательную задержку (60000/RPM — оборот, половина — средняя rotation).
- В чём недостаток SSTF и как SCAN/C-SCAN его решают (голодание дальних дорожек vs «лифт» с гарантией обхода).
- Чем C-SCAN отличается от SCAN и зачем (равномерность задержек по всем дорожкам).
- Чем HDD принципиально отличается от SSD с точки зрения random vs sequential (у SSD нет механики, random намного ближе к sequential).