SQL Server Fundamentos: Storage Engine (Parte I)

Eu estou finalizando a gravação de um vídeo para a série SQL Server Fundamentos, projeto iniciado pelo meu amigo e mentor técnico Fabricio Catae no Channel 9.
Vou focar este primeiro vídeo em um dos componentes que eu mais gosto de falar, o Storage Engine. Como o vídeo ainda está em fase de produção, resolvi transcrever o conteúdo deste vídeo em formato de post aqui no meu blog.

Infelizmente não poderia cobrir todos os aspectos do Storage Engine em um vídeo de 15 minutos, por isso acredito que teremos mais vídeos falando especificamente deste componente. Espero que gostem. 

STORAGE ENGINE

O Storage Engine é um dos componentes mais complexos do SQL Server. Ele é o principal componente, junto a Engine Relacional, dentro do SQL Server. Apesar de o Storage Engine e o Relational Engine serem totalmente independentes, eles interagem entre si através dos components de acesso nativo, como o OLE DB.

As principais responsabilidades do Storage Engine são:

  • Gerenciar o armazenamento dos dados em discos e todo o IO necessário para acesso a essa estrutura
  • Armazenar e gerenciar as estruturas físicas dos objetos, utilizando blocos, como páginas
  • Prover a capacidade de manter dados em uma estrutura de memória, chamada de buffer, ou cache
  • Controlar concorrência
  • Gerenciar transações e locks
  • Recuperar os dados em caso de falhas

Vamos falar um pouco sobre alguns dos itens mencionados. 

ARMAZENAMENTO

Assim como outras plataformas relacionais, o SQL Server possui uma estrutura de memória, parte do Storage Engine, conhecida como Buffer Pool, ou Buffer Cache. Essa estrutura armazena páginas de memória de 8k, o tamanho utilizado, por padrão no SQL Server, para armazenamento dos dados.

As páginas de 8k são armazenadas fisicamente nos arquivos de dados, os .mdf e .ndf, em grupos de 8 páginas. Essa estrutura de 8 páginas de 8k chama-se extents, o que forma uma estrutura com tamanho total de 64k. O SQL Server utiliza a estrutura de extents para otimizar as leituras e escritas por ler e escrever mais dados ao invés de apenas 8k, porém algumas operações fazem especificamente IOs de 8k, por lidarem com páginas específicas.

Vamos dar um pouco mais de foco agora em como ocorre a interação do Buffer Pool com os demais componentes e demais tarefas do SQL quando uma leitura é realizada.

PROCESSO DE LEITURA DOS DADOS

Quando efetuamos uma leitura a partir da nossa aplicação, por exemplo, uma dll C#, há um fluxo natural a ser seguido.

Sua aplicação se conecta ao SQL Server através da camada de Protocolos e sua consulta deve obrigatoriamente passar pela Engine Relacional. O Relational Engine então faz a validação, ou Parse da Query, e durante o processo de otimização gera um Plano de Execução para esta consulta. Este plano então é armazenado numa área de memória, conhecida como Plan Cache para otimizar as próximas execuções desta query. Não vou entrar em muitos detalhes sobre o otimizador de queries ou plan cache nesse momento, mas falaremos mais sobre isso quando entrarmos em Relational Engine.

Este plano de execução gerado pela Engine Relacional serve como um mapa para que o próximo componente, o Storage Engine, seja capaz de buscar os dados solicitados da melhor forma possível, ou quase isso, já que durante a otimização, o otimizador de queries não escolhe necessariamente o melhor plano mas sim um plano bom o suciente para a execução da Query. A Razão é simples, demandaria muito tempo escolher o melhor plano possível.

A partir deste plano o Storage Engine pode realizar as tarefas necessárias para buscar os dados que suas consultas solicitaram. O primeiro local no qual o Storage Engine tenta buscar os seus dados é no seu próprio cache, ou seja, o Buffer Pool. Todas as consultas passam obrigatoriamente por essa área de memória. Caso as páginas de dados que possuem os dados que você precisa estejam em memória (nesta área de cache), a sua consulta pode ser retornada com sucesso. Sem a necessidade de ler seus arquivos físicos, esse tipo de leitura, em memória, é muito mais rápida, é o que chamamos de leitura lógica ou logical read.

Caso os dados que você está buscando não estejam no Buffer Pool, o Storage Engine é responsável por iniciar e gerenciar a leitura em disco, nos arquivos de dados, buscando pela informação que você está procurando, obviamente levando em conta o plano de execução, ou o mapa, por assim dizer, que foi passado para ele pela Engine Relacional. Nesse momento, após o IO em disco ser efetuado, o Storage Engine armazena as páginas lidas no Buffer Pool para que possa otimizar as próximas leituras que solicitarem as mesmas páginas.

Nós chamamos esse processo de leitura em disco de Leitura Física dos dados, já que o SQL precisou realizar IO em disco para efetivamente encontrar os seus dados.

Uma vez os dados em memória, ou seja, nessa área de Cache, o Buffer Pool, o SQL Server não precisa mais realizar uma leitura Fïsical e, nesse caso, ele realiza, apenas, a Leitura Lógica, já mencionada

LAZY WRITER

Obviamente, o Buffer Pool, ou essa área de memória que comporta as páginas dos seus bancos de dados é limitada. Vamos imaginar que essa área de memória em um determinado servidor tenha a capacidade de armazenar 4GB e você já está utilizando efetivamente um total de 3.8GB. O que aconteceria se uma nova leitura de 400Mb ocorresse?

Nesse caso, o SQLOS, o sistema operacional do SQL Server, através de um dos seus componentes internos, chamado Resource Monitor, veria que uma pressão interna de memória está ocorrendo, afinal, 200Mb não são suficientes para acomodar os 400Mb. Ele então enviaria uma notificação para que esse pequeno problema de memória no Buffer Pool seja tratado por um agente de gerenciamento de recurso, ou uma tarefa interna. Esse “agente” ou tarefa interna, responsável por gerenciar a memória do Buffer Pool é chamado de Lazy Writer. O Lazy Write então acordaria, e limparia do cache páginas sujas, ou seja, páginas que já foram modificadas e já estão salvas em disco, nos seus arquivos de dados.

Essa tarefa é um pouco diferente do CHECKPOINT.

O CHECKPOINT é uma tarefa responsável por escrever páginas que foram modificadas no Buffer Pool para os arquivos de dados, porém o CHECKPOINT não necessariamente remove essas páginas dessa área de cache, ou seja, a quantidade de dados em memória não diminui após a execução de um CHECKPOINT. Vamos falar um pouco mais sobre CHECKPOINT quando detalharmos o processo de escrita.

Já o Lazy Writer é responsável pela limpeza dessa área de cache, utilizando um algoritimo chamado LRU2 (Least Recently Used), ou seja, as páginas mais antigas, ou as que foram utilizadas menos recentemente, são marcadas como disponíveis para serem reutilizadas por outras alocações.

No meu exemplo então, apesar de eu já ter 200Mb disponíveis, outros 200Mb serão disponibilizados em forma de páginas marcadas como disponíveis, já que elas foram limpas do meu Buffer Pool pelo Lazy Writer.

Essas páginas deixam de existir em memória, porém ainda estão em disco, uma vez que o SQL deve garantir a persistência dos dados.

Se uma nova leitura é realizada e busca por dados que estão nessas páginas que acabaram de ser removidas do Buffer Pool, ele não executa apenas leituras lógicas, como faria anteriormente quando minhas páginas ainda existiam em memória, mas também leitura física nos arquivos de dados em que essas páginas se encontram.
 

CONCLUSÃO

Nesse primeiro vídeo vou falar um pouco mais sobre um dos componentes do SQL Server: O Storage Engine. Este componente possui muitos outros aspectos a serem analisados.
No próximo vídeo vou analisar um pouco mais a fundo o processo de escrita e como o Storage Engine desempenha um papel importante para garantir a consistência e durabilidade dos dados.