Disco e Escalonamento de I/O
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.
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
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:
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)
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)!
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.
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.
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).
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.
┌───────────────────────────────────────────┐
│ 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.
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
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
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: 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 │
└────────┴───────────┴───────────┴────────────────┴────────────┘
# 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:
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
- 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?
- 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
noneemq-deadline.) - 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?
- HDDs têm tempo de acesso dominado pelo seek time; escalonamento de disco otimiza o movimento do braço
- FCFS é justo mas ineficiente; SSTF minimiza seek mas pode causar starvation; SCAN/C-SCAN/C-LOOK equilibram eficiência e justiça
- SSDs usam memória flash NAND sem partes móveis: acesso aleatório tão rápido quanto sequencial (~100K IOPS)
- FTL, wear leveling e garbage collection são os desafios do controlador SSD
- RAID combina discos: RAID 0 (velocidade), RAID 1 (espelhamento), RAID 5 (paridade), RAID 10 (espelhamento + striping)