quinta-feira, 4 de fevereiro de 2010

Compressão de registro no SQL Server 2008

(Para melhor visualização, baixe AQUI o pdf.)

Compressão de dados não é lá novidade, então vamos brincar de internals...

Uma das novas funcionalidades do SQL Server 2008 é a possibilidade de compactar os registros dentro de uma página, sendo possíveis duas abordagens: registro e página. A compressão de registro transforma as colunas de tamanho fixo (INT, CHAR, ROWVERSION, etc.) em tamanho variável, e a compressão de página, além de aplicar a compressão de registro, irá fazer a compressão de prefixo e dicionário.

A compressão de dados é importante para diminuir a utilização do espaço em disco (você ganha os benefícios colaterais disso), e por outro aspecto muito importante, que às vezes fica relegado a segundo plano. Potencialmente a compressão de dados vai melhorar a eficiência da cache de dados, pois se caberão mais registros em uma página de 8K, então mais registros serão colocados no mesmo espaço em memória e a probabilidade do acerto em cache acontecer também aumenta. Lindo, não?

Pensando um pouco no assunto, seria extremamente ineficiente se o SQL Server 2008 utilizasse o mesmo mecanismo do tipo VARCHAR, pois ele já gasta de largada mais dois bytes em cada registro (valor exato por simplificação). Se fosse um inteiro (4 bytes) acabaríamos somente tendo ganho quando armazenássemos um valor menor que 127 (1 byte), pois depois disso o overhead deixaria o campo variável com o mesmo tamanho da coluna fixa (2 + 2 = 4) e a partir de 3 bytes a compressão iria perder espaço.

Como claramente o mecanismo tem que outro (VARDECIMAL é assim), vamos analisar um pouco a compressão de registro nesse artigo, mais especificamente CD (column descriptor) e a short data region. Primeiramente execute o script abaixo para criar uma tabelinha boba que servirá de exemplo.

Nota: número das páginas, local, distribuição, entre outros, podem variar de exemplo para exemplo, então siga o raciocínio.

Script 01: Tabela para teste

USE Inside
go

IF OBJECT_ID('CompressaoDados') IS NOT NULL
DROP TABLE CompressaoDados
go

CREATE TABLE CompressaoDados
(Codigo BIGINT IDENTITY PRIMARY KEY NOT NULL,
Nome VARCHAR(100) NOT NULL,
Idade BIGINT NOT NULL,
SeloTempo ROWVERSION NOT NULL)
GO

DECLARE @I INT
SET @I = 1
WHILE (@I <>
BEGIN
INSERT INTO CompressaoDados (Nome, Idade) VALUES (CAST(NEWID() AS VARCHAR(40)) + CAST(NEWID() AS VARCHAR(40)), @I % 100)
SET @I = @I + 1
END
go

Com a tabela criada, aproveitamos para olhar os dados e também algumas informações das unidades de alocação (script 02). Aqui podemos notar uma tabela com 473 páginas de dados, sendo a primeira na posição 22 (0x00000016).

Script 02: unidades de alocação

SELECT TOP 100 * FROM CompressaoDados
go

SELECT AU.*
FROM sys.system_internals_allocation_units AS AU
INNER JOIN SYS.Partitions AS P
ON AU.Container_id = P.Partition_id
WHERE Object_ID = object_id('CompressaoDados')
go

A página atual mantém os registros no formato que você já conhece desde o SQL Server 7.0, o FixedVar, conforme podemos notar através do DBCC PAGE (figura 01). Aqui podemos ver algumas coisas importantes, no cabeçalho temos a quantidade de registros por página (m_SlotCnt = 74), o tamanho do registro em 107 bytes e o espaço sendo desperdiçado com o tamanho fixo dos campos bigint e rowversion.



(Figura 01)

Com base no que nós discutimos até agora, pergunto: Essa página irá se beneficiar com compressão de registro? E de página?

R: registro sim, pois temos espaço sendo desperdiçado em colunas de tamanho variável. Já de página, muito provavelmente não. Consegue ver o motivo? Espero que sim, em outro post ou comentário eu volto a esse assunto.

Vamos habilitar compressão de registro e ver o resultado da compressão através da consulta 02, aproveitando para executar um DBCC PAGE na página 648 (0x00000288). Vemos que a tabela ocupa agora 381 páginas ao invés das 473 originais, o número de registros por página aumentou para 93 (figura 02 - que beleza!), o tamanho do primeiro registro é de 85 bytes (contra os antigos 107) e os registros na página estão ocupando pouco espaço (nada de muitos zeros no meio). Viu?


Script 03: compressão e análise

ALTER TABLE CompressaoDados
REBUILD WITH (DATA_COMPRESSION = ROW);
GO

SELECT AU.*
FROM sys.system_internals_allocation_units AS AU
INNER JOIN SYS.Partitions AS P
ON AU.Container_id = P.Partition_id
WHERE Object_ID = object_id('CompressaoDados')
go

DBCC PAGE (8, 1, 648, 3)


(Figura 02)

É interessante notar que agora o formato do registro mudou e cada registro possui um cabeçalho, column descriptor, que fala o tamanho de cada campo (4 bits por campo), uma short data region (para colunas de até 8 bytes) e um long data region (maiores de 8 bytes). No nosso caso não precisamos de ponteiros de 1byte para os clusters de short data, que são compostos por 30 colunas, pois somente temos quatro colunas (o que cabe em um cluster).
No início do registro temos a informação 2104a232, quebrando-a em bytes temos...
21 (0010 e 0001) = header - Significa registro no formato CD, primário e coluna long.
04 = Número de colunas nesse registro.
A2 = Primeira coluna têm um byte, segunda coluna é longa (mais de 8 bytes)
32 = Terceira coluna têm um byte, quarta coluna têm dois bytes.

Então esse registro pode compactar bem as colunas, pois somente estamos com valores pequenos, mas o mesmo se manteria em todos os registros? Claro que não, vamos navegar na cadeia de páginas para a próxima de nosso índice e achar os registros de código 127 e 128. O DBCC Page da página 649 (m_nextPage) mostra claramente quando 1 byte não é mais suficiente para armazenar o código, pulando para 2 bytes a partir desse momento.



(Figura 03)

Aqui vemos o registro passando de 85 para 86 bytes de tamanho e o espaço necessário para a primeira coluna salta de um para dois bytes. Nos quatro bits de descrição vemos a mudança de 2 para 3 (nota: o slotcnt saiu de 93 para 92, como esperado). Interessante, não? Isso vai acontecer novamente para a coluna Código? Sim, quando chegarmos ao 32768 veremos outra alteração no tamanho necessário para o campo código.

Quer ver essa mudança? Então aproveite uma nova função no SQL Server para ver o local físico de cada um dos seus registros. Sys.fn_PhysLocFormatter(%%PHYSLOC%%), encontre o 32768 e DBCC PAGE nele! Exemplo: (1:1060:14) 32768 => arquivo 1, página 1060, slot 14.

SELECT SYS.fn_PhysLocFormatter(%%PHYSLOC%%) AS RID, Codigo
FROM CompressaoDados
WHERE Codigo between 32700 and 32800

Espero que você possa ter entendido um pouco da compressão de registros e consiga fazer melhores análises críticas sobre o uso do recurso no SQL Server 2008. Ainda existem mais detalhes sobre a formação do CD, short data com mais colunas e long data, além da compressão de página que pretendo abordar em um outro artigo.
O SQL Server 2008 R2 trás novidades na compressão UNICODE, o jeito é brincar com ele também para ver as diferenças.

O script está disponível no endereço:
http://cid-e145f7753042d628.skydrive.live.com/self.aspx/Public/BlogPosts/20100204^_RowCompressionInternals.zip

[]s
Luciano Caixeta Moreira - {Luti}
Chief Innovation Officer
Sr. Nimbus Serviços em Tecnologia Ltda
luciano.moreira@srnimbus.com.br
www.twitter.com/luticm

4 comentários:

  1. Luti,

    Não entendi corretamente porque o campo TinyInt consegue armazenar valores maiores que 127 com apenas 1 byte. Ja no caso da compressão não.

    Obrigado.

    DBA Junior

    ResponderExcluir
  2. Luciano, bom dia!

    Eu tenho um blog chamado: www.calopsitar.com.br, omo eu faço para comprimir as paginas, para melhorar o tempo de carregamento. A minha hospedagem é grátis no blogger.
    Desde já agradeço.
    Mauricio
    mauricioluiz@globo.com

    ResponderExcluir
  3. LUTI, muito bom seu blog !! Será que somos primos ?????
    abraços, Mauricio Luti

    ResponderExcluir
  4. Na verdade não Mauricio. Luti é apenas um apelido que uso desde sempre... :-)

    ResponderExcluir