GraphLMS

ОС
Начать

ОС · 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 МБ:

  1. Sequential (последовательно) — данные лежат подряд, на соседних секторах одной дорожки. Один seek в начале, один раз дождались сектора — и дальше просто льём данные, пока пластина крутится. Механика платится один раз.
  2. 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 → 14

SSTF резко сокращает суммарный 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-планирование — это командная работа ОС и контроллера.

головка
0469213718353 (старт)6567371498122124183
суммарное перемещение головки: 236 цилиндров · порядок: 53 → 65 → 67 → 37 → 14 → 98 → 122 → 124 → 183
Головка диска ходит по цилиндрам. Переключай политику (FIFO/SSTF/SCAN) и смотри путь головки и суммарное перемещение — оно прямо пропорционально времени 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).
Устройства ввода-вывода: прерывания и DMAFlash и SSD