College Online
0%

Disco e Escalonamento de I/O

Módulo 7 · Aula 2 ~30 min de leitura Nível: Intermediário

Vídeo da aula estará disponível em breve

Estrutura de um Disco Rígido (HDD)

Embora SSDs estejam substituindo HDDs, entender a estrutura de um disco magnético é fundamental para compreender por que algoritmos de escalonamento de disco existem e por que acesso sequencial é mais rápido que aleatório.

Diagrama — Anatomia de um HDD
Vista lateral:                     Vista superior (prato):
┌─────────────────┐
│  Prato 0 (top)  │  ←cabeça 0         ┌─────────────────┐
│  Prato 0 (bot)  │  ←cabeça 1         │  ╱─────────╲    │
│  Prato 1 (top)  │  ←cabeça 2         │ ╱ ╱───────╲ ╲   │
│  Prato 1 (bot)  │  ←cabeça 3         │╱ ╱ ╱─────╲ ╲ ╲  │
│  Prato 2 (top)  │  ←cabeça 4         │ │ │ [eixo]│ │ │← trilha (track)
│  Prato 2 (bot)  │  ←cabeça 5         │╲ ╲ ╲─────╱ ╱ ╱  │
│        ⊕        │  ←eixo (spindle)    │ ╲ ╲───────╱ ╱   │
└─────────────────┘                     │  ╲─────────╱    │
                                        └─────────────────┘
                                              ↑
                                           setor (512B ou 4KB)

Termos:
  Prato (platter): disco magnético giratório (5400/7200/15000 RPM)
  Trilha (track): anel concêntrico em um prato
  Setor (sector): menor unidade de leitura/escrita (512B ou 4KB)
  Cilindro (cylinder): conjunto de trilhas na mesma posição em todos os pratos
  Cabeça (head): braço com ímã que lê/escreve dados magnéticos

Endereçamento:
  CHS (antigo): Cylinder-Head-Sector → posição física
  LBA (moderno): Logical Block Address → número sequencial 0, 1, 2, ...
  O controlador traduz LBA → posição física

Tempo de Acesso no HDD

Componentes do tempo de acesso
Tempo de acesso = seek time + latência rotacional + transfer time

1. SEEK TIME (tempo de busca):
   Mover o braço para a trilha correta.
   Típico: 3-12 ms (média: ~8 ms)
   É o componente DOMINANTE → escalonamento otimiza isso!

2. LATÊNCIA ROTACIONAL:
   Esperar o setor desejado passar sob a cabeça.
   Disco a 7200 RPM: 1 rotação = 60/7200 = 8.33 ms
   Média: metade de uma rotação = 4.17 ms

3. TRANSFER TIME (tempo de transferência):
   Ler os dados do setor.
   ~100-200 MB/s sequencial para HDDs modernos
   Para 4KB: 4096 / 200MB = ~0.02 ms (negligível)

Exemplo completo:
  Ler 4KB em trilha aleatória:
  8ms (seek) + 4.2ms (rotação) + 0.02ms (transfer) ≈ 12ms
  = ~83 operações aleatórias por segundo (IOPS)

Compare com SSD:
  Ler 4KB aleatório: 0.05-0.1 ms
  = ~100.000 IOPS (1000x mais que HDD!)

Por isso escalonamento de disco importa para HDD:
minimizar seek time pode dobrar ou triplicar o throughput.

Algoritmos de Escalonamento de Disco

Quando múltiplas requisições de I/O estão pendentes, o escalonador de disco decide a ordem de atendimento para minimizar o movimento do braço (seek time). Vamos analisar cada algoritmo com o mesmo exemplo:

Dados do exemplo
Fila de requisições (cilindros): 98, 183, 37, 122, 14, 124, 65, 67
Posição atual da cabeça: cilindro 53
Total de cilindros: 0 a 199
Direção: movendo para cima (cilindros maiores)
FCFS — First Come, First Served
Atende na ordem de chegada. Simples mas ineficiente.

Ordem: 53 → 98 → 183 → 37 → 122 → 14 → 124 → 65 → 67

  53 ────────→ 98           |45|
  98 ──────────────→ 183    |85|
  183 ←────────────── 37    |146|
  37 ────────────→ 122      |85|
  122 ←──────────── 14      |108|
  14 ──────────────→ 124    |110|
  124 ←──────── 65          |59|
  65 → 67                   |2|

  Movimento total: 45+85+146+85+108+110+59+2 = 640 cilindros
  Muito movimento desnecessário (vai e volta)!
SSTF — Shortest Seek Time First
Atende a requisição mais próxima da posição atual.
Análogo ao SJF no escalonamento de CPU.

Ordem: 53 → 65 → 67 → 37 → 14 → 98 → 122 → 124 → 183

  53 → 65        |12|
  65 → 67        |2|
  67 ← 37        |30|
  37 ← 14        |23|
  14 ──→ 98      |84|
  98 → 122       |24|
  122 → 124      |2|
  124 → 183      |59|

  Movimento total: 12+2+30+23+84+24+2+59 = 236 cilindros
  Muito melhor que FCFS! Mas pode causar starvation
  de requisições em cilindros distantes.
SCAN — Algoritmo do Elevador
O braço se move em uma direção, atende requisições no caminho,
depois inverte. Como um elevador.

Direção inicial: subindo (→)

Ordem: 53 → 65 → 67 → 98 → 122 → 124 → 183 → [199] → 37 → 14

  53 → 65 → 67 → 98 → 122 → 124 → 183 → [199]  (sobe até o fim)
  [199] ← 37 ← 14                                 (desce)

  Subindo: 53→199 = 146
  Descendo: 199→14 = 185
  Movimento total: 146 + 185 = 331 cilindros

  Sem starvation: toda requisição é eventualmente atendida.
  Desvantagem: cilindros recém visitados esperam uma ida+volta
  completa para serem atendidos novamente.
C-SCAN — Circular SCAN
Como SCAN, mas ao chegar no final, volta para o início
sem atender requisições. Mais uniforme no tempo de espera.

Ordem: 53 → 65 → 67 → 98 → 122 → 124 → 183 → [199] → [0] → 14 → 37

  53 → 65 → 67 → 98 → 122 → 124 → 183 → [199]  (sobe)
  [199] → [0]                                      (volta sem atender)
  [0] → 14 → 37                                   (sobe de novo)

  Subindo: 53→199 = 146
  Retorno: 199→0 = 199 (sem I/O, rápido)
  Subindo: 0→37 = 37
  Movimento total: 146 + 199 + 37 = 382 cilindros

  Mais justo: tempo de espera uniforme para todos os cilindros.
  Na prática, o retorno é rápido (sem paradas).
LOOK e C-LOOK
Variantes práticas: não vão até o fim (0 ou 199),
invertem na última requisição real.

C-LOOK:
  53 → 65 → 67 → 98 → 122 → 124 → 183  (sobe até última req)
  183 → 14                                (volta para a menor req)
  14 → 37                                (continua subindo)

  Movimento: 130 + 169 + 23 = 322 cilindros
  Mais eficiente que C-SCAN puro!

Resumo comparativo (para o exemplo dado):
  ┌──────────┬─────────────────┬────────────┐
  │ Algoritmo│ Mov. total (cyl)│ Starvation │
  ├──────────┼─────────────────┼────────────┤
  │ FCFS     │      640        │ Não        │
  │ SSTF     │      236        │ Sim        │
  │ SCAN     │      331        │ Não        │
  │ C-SCAN   │      382        │ Não        │
  │ C-LOOK   │      322        │ Não        │
  └──────────┴─────────────────┴────────────┘

  Linux usa variantes de C-LOOK (deadline, mq-deadline, BFQ).

Arquitetura de SSDs

SSDs (Solid State Drives) usam memória flash NAND em vez de pratos magnéticos. Sem partes móveis, o acesso aleatório é tão rápido quanto o sequencial, tornando os algoritmos de escalonamento de disco tradicionais irrelevantes.

Diagrama — SSD internamente
┌───────────────────────────────────────────┐
│                SSD                        │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐  │
│  │ NAND    │  │ NAND    │  │ NAND    │  │
│  │ Flash   │  │ Flash   │  │ Flash   │  │
│  │ Chip 0  │  │ Chip 1  │  │ Chip N  │  │
│  └────┬────┘  └────┬────┘  └────┬────┘  │
│       │            │            │        │
│  ┌────┴────────────┴────────────┴────┐   │
│  │        Controlador FTL            │   │
│  │   (Flash Translation Layer)       │   │
│  │                                   │   │
│  │   LBA → página física             │   │
│  │   Wear leveling                   │   │
│  │   Garbage collection              │   │
│  │   Write buffer (DRAM cache)       │   │
│  └───────────────┬───────────────────┘   │
│                  │ NVMe / SATA            │
└──────────────────┼───────────────────────┘
                   │
              Host (PCIe / SATA)

NAND Flash - particularidades:
  Leitura:  por página (4-16 KB)     ~25 μs
  Escrita:  por página               ~250 μs
  Apagar:   por BLOCO (256-512 pág)  ~2 ms

  Não pode sobrescrever! Precisa apagar bloco inteiro primeiro.
  FTL resolve: escreve em página nova, marca antiga como inválida.
  Garbage collection: move páginas válidas, apaga blocos com inválidas.
  Wear leveling: distribui escritas para não gastar sempre as mesmas células.

HDD vs SSD:
  ┌──────────────┬──────────────┬──────────────┐
  │              │     HDD      │     SSD      │
  ├──────────────┼──────────────┼──────────────┤
  │ Random read  │   ~0.1 KIOPS │  ~100 KIOPS  │
  │ Random write │   ~0.1 KIOPS │   ~50 KIOPS  │
  │ Seq. read    │  ~200 MB/s   │ ~3500 MB/s   │
  │ Seq. write   │  ~150 MB/s   │ ~3000 MB/s   │
  │ Latência     │   ~8 ms      │  ~0.05 ms    │
  │ $/GB         │   ~$0.02     │   ~$0.08     │
  │ Vida útil    │   ~5 anos    │ TBW limitado │
  └──────────────┴──────────────┴──────────────┘

Níveis de RAID

RAID (Redundant Array of Independent Disks) combina múltiplos discos para melhorar desempenho e/ou confiabilidade. Cada nível de RAID faz um trade-off diferente entre velocidade, espaço e tolerância a falhas.

RAID 0 — Striping (sem redundância)
Dados distribuídos entre N discos. Sem espelhamento.

  Disco 0:  [A1][A3][A5]
  Disco 1:  [A2][A4][A6]

  + Desempenho: N vezes mais rápido (leituras paralelas)
  + Capacidade: 100% aproveitada
  - Confiabilidade: se UM disco falha, TUDO é perdido
  - Probabilidade de falha AUMENTA com mais discos

  Uso: scratch storage, cache, onde dados são descartáveis
RAID 1 — Mirroring (espelhamento)
Cada dado escrito em 2 (ou mais) discos idênticos.

  Disco 0:  [A1][A2][A3][A4]
  Disco 1:  [A1][A2][A3][A4]  ← cópia exata

  + Confiabilidade: sobrevive a falha de 1 disco
  + Leitura: pode ler de qualquer disco (2x throughput)
  - Capacidade: 50% aproveitada (metade é espelho)
  - Escrita: deve escrever em ambos os discos

  Uso: sistema operacional, bancos de dados pequenos
RAID 5 — Striping com paridade distribuída
Dados distribuídos + paridade rotativa entre os discos.
Mínimo: 3 discos.

  Disco 0:  [A1] [A2] [Bp]
  Disco 1:  [A3] [Bp] [C1]
  Disco 2:  [Bp] [B1] [C2]
               ↑ paridade (XOR dos dados)

  Paridade: Bp = A1 XOR A2 (permite reconstruir qualquer bloco)

  Se Disco 1 falha:
    B1 original = Bp XOR A1  (reconstruído dos outros discos)

  + Sobrevive a falha de 1 disco
  + Capacidade: (N-1)/N (com 3 discos: 67%)
  + Leitura rápida (como RAID 0, quase)
  - Escrita lenta: cada escrita atualiza dado + paridade
    (read-modify-write para a paridade)
  - Rebuild após falha é lento e perigoso (disco sobrevivente
    sob stress → chance de segundo falha)

  Uso: file servers, NAS, storage geral
RAID 6 e RAID 10
RAID 6: como RAID 5, mas com DUAS paridades.
  Sobrevive a falha de 2 discos simultaneamente.
  Mínimo: 4 discos. Capacidade: (N-2)/N.
  Mais seguro para discos grandes (rebuild demorado).

RAID 10 (1+0): RAID 0 de pares RAID 1.
  Disco 0: [A1][A3]  ┐ espelho
  Disco 1: [A1][A3]  ┘
  Disco 2: [A2][A4]  ┐ espelho
  Disco 3: [A2][A4]  ┘

  + Combina performance de RAID 0 com segurança de RAID 1
  + Rebuild rápido (só copia de um disco para outro)
  - Capacidade: 50%
  - Mínimo: 4 discos

  Uso: bancos de dados em produção, workloads de alta performance

Resumo:
  ┌────────┬───────────┬───────────┬────────────────┬────────────┐
  │  RAID  │ Min discos│ Capacidade│ Tolerância     │ Performance│
  ├────────┼───────────┼───────────┼────────────────┼────────────┤
  │   0    │     2     │   100%    │ 0 falhas       │ Excelente  │
  │   1    │     2     │    50%    │ 1 falha        │ Boa leitura│
  │   5    │     3     │  (N-1)/N  │ 1 falha        │ Boa        │
  │   6    │     4     │  (N-2)/N  │ 2 falhas       │ Razoável   │
  │  10    │     4     │    50%    │ 1 por espelho  │ Excelente  │
  └────────┴───────────┴───────────┴────────────────┴────────────┘
Shell — RAID e disco no Linux
# Ver escalonador de I/O em uso
$ cat /sys/block/sda/queue/scheduler
[mq-deadline] none

# Mudar escalonador (para BFQ, por exemplo)
$ echo bfq | sudo tee /sys/block/sda/queue/scheduler

# Ver status do RAID (software RAID com mdadm)
$ cat /proc/mdstat
md0 : active raid1 sda1[0] sdb1[1]
      500G blocks [2/2] [UU]

# Criar RAID 1 com mdadm
$ sudo mdadm --create /dev/md0 --level=1 --raid-devices=2 /dev/sda1 /dev/sdb1

# Benchmark de I/O com fio
$ fio --name=rand-read --ioengine=libaio --rw=randread \
      --bs=4k --numjobs=4 --size=1G --runtime=30

# Estatísticas SMART do disco
$ sudo smartctl -a /dev/sda | grep -E "(Reallocated|Temperature|Hours)"

No harness.os

O Neon Postgres que armazena os dados do harness.os roda em SSDs NVMe na cloud. Os conceitos de RAID e escalonamento de disco aparecem em como o storage é gerenciado:

Diagrama — Storage no Neon Postgres
Neon Postgres: arquitetura storage-compute separado

  Compute (CPU):
    Postgres process → WAL → safekeepers (réplicas do WAL)

  Storage (disco):
    Page servers → S3-compatible object storage

  Redundância:
    WAL: replicado em 3 safekeepers (similar a RAID 1 distribuído)
    Pages: armazenadas em object storage com redundância interna
    Nenhum dado depende de um único disco → zero risco de perda

  Branching instantâneo:
    Criar branch = novo ponteiro para as mesmas pages
    Sem cópia de dados → sem I/O de disco
    Copy-on-write: páginas copiadas apenas quando modificadas

  O harness.os se beneficia:
    - Branches para testes (sem custo de I/O)
    - WAL shipping para backup contínuo
    - Storage escala automaticamente
    - IOPS altos via NVMe (queries rápidas mesmo com índices grandes)

Exercícios

  1. Para a fila de requisições [82, 170, 43, 140, 24, 16, 190] com cabeça na posição 50 movendo para cima, calcule o movimento total para FCFS, SSTF e C-LOOK. Qual algoritmo é mais eficiente neste caso?
  2. Explique por que SSDs não precisam de algoritmos de escalonamento de disco como SCAN ou SSTF. O que o Linux usa em vez disso para SSDs? (Pesquise sobre o escalonador none e mq-deadline.)
  3. Um servidor de banco de dados usa 6 discos de 2TB cada. Compare a capacidade útil, tolerância a falhas e performance de escrita para RAID 5 e RAID 10. Qual você recomendaria e por quê?

Resumo

Verifique seu entendimento

Em RAID 5 com 4 discos de 1TB cada, qual é a capacidade útil e quantas falhas simultâneas o sistema tolera?

  • 4TB úteis, tolera 1 falha
  • 3TB úteis, tolera 1 falha
  • 2TB úteis, tolera 2 falhas
  • 3TB úteis, tolera 2 falhas