ERRORLOG – O básico (Parte 1 de 3)

errorlog2222

Olá,

Estava planejando tem um tempão falar  sobre os recursos que o SQL Server oferece de graça para os usuários do produto (supondo que estejam utilizando a versão Enterprise e/ou que tenham permissão suficiente, é claro) e inevitavelmente me veio a forma mais básica de logging do SQL Server: ERRORLOG. Esse post é o primeiro de uma trilogia, onde vamos comentar o básico e o que realmente precisa ser entendido sobre o funcionamento do mesmo.

O que é?

O ERRORLOG é um dos logs utilizados do SQL Server que registra ocorrências de acordo com o que está configurando para a instância. Embora o nome possa sugerir, ele não loga apenas erros, mas também mensagens informativas.

Como ler?

Existem algumas formas de se realizar a leitura de um ERRORLOG:

  • Através da procedure não-documentada xp_readerrorlog (que é uma Extended Stored Procedure, cujo código está na xpstar.dll (que por sua vez chama outras bibliotecas, como a xplog70.dll).
Exemplo de uso da xp_readerrorlog

Exemplo de uso da xp_readerrorlog

 

  • Através da procedure não-documentada sp_readerrorlog (que chama a xp_readerrorlog por debaixo dos panos):
Exemplo de uso da sp_readerrorlog

Exemplo de uso da sp_readerrorlog

  • Via interface gráfica através do Management Studio:
Acesse a instância > Management > SQL Server Logs

Acesse a instância > Management > SQL Server Logs

 

Interface do Management Studio para os logs

Interface do Management Studio para os logs

 

O Log File Viewer faz interface com vários tipos de log do SQL Server. A realização de leitura do ERRORLOG é a opção “SQL Server”.

De todos os métodos citados acima, iremos focar apenas no sp_readerrorlog (que utiliza praticamente a mesma lógica do xp_readerrorlog, com mínimas diferenças). ]

O que é logado?

Para saber quais mensagens estão marcadas para serem logadas, execute a query a seguir:


select * from sys.messages where is_event_logged = 1
and language_id = 1033

Algumas das mensagens que são logadas no errorlog

Algumas das mensagens que são logadas no errorlog

No próximo post, algumas dicas em relação à sys.messages estarão presentes, e uma delas inclui escolher quais mensagens serão logadas ou não.

Estrutura lógica do ERRORLOG

O ERRORLOG possui três campos:

- LogDate: Data em que o evento foi logado (WHEN);
- ProcessInfo: O que foi logado (WHAT);
- Text: Descrição do evento logado (WHERE, WHO, WHAT (detalhes))

Um exemplo de interpretação:

Tentativa de invasão. Da minha máquina local. Ou seja, sem emoção.

Tentativa de invasão. Da minha máquina local. Ou seja, sem emoção.

No dia 15.10.2014, foi identificado um evento de logon que falhou. Alguém tentou acessar o servidor com um login chamado INVASAO e a pessoa não conseguiu logar porque não existe nenhuma conta (que é um SQL login) com esse nome para ele sequer tentar uma autenticação. A requisição partiu da máquina local.

Note que assim como manda o princípio de logging, existem alguns princípios presentes: Quando foi (When), O que foi (what) e quem foi (Who).

Lógica para geração

O arquivo mais atual se chama ERRORLOG. Toda vez que ocorre um restart¹ de serviço ou a procedure sp_cycle_errorlog é executada, um novo ERRORLOG é criado, e o anterior recebe um versionamento decadente. Por exemplo, o ERRORLOG depois de um cycle agora se chama ERRORLOG.1, o ERRORLOG.1 antigo agora passou a se chamar ERRORLOG.2 e por aí vai…

Caminho dos Errorlogs e exemplo de nomes

Caminho dos Errorlogs e exemplo de nomes

Por padrão, o diretório onde se localiza os LOGS dependem da localização da instalação do SQL Server. Em minha máquina, o caminho é esse:

C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\Log

Por padrão o SQL Server pode criar até seis (6) arquivos de errorlog mais o errorlog atual, totalizando em sete (e o limite pode ser aumentado).

Concorrência

O SQL Server sempre abrirá um handle no ERRORLOG atual para escrita e portanto, editá-lo enquanto o serviço do SQL Server está sendo executado não é possível. Você pode, portanto, abrir o errorlog atual apenas para leitura (no editor de sua preferência) mas não pode editar. O restante dos ERRORLOGS com exceção do atual, você pode abrir por fora para editar e ler.

Objetos relacionados

Os objetos relativos ao ERRORLOG podem ser identificados com o uso da sys.all_objects:

Uma mistura de legado com o não documentado.

Uma mistura de legado com o não documentado.

DBCC ERRORLOG: Gera um novo arquivo de ERRORLOG executando um cycle, versionando os antigos de acordo com o limite de arquivos configurado. Requer permissão de sysadmin para executar. Não é documentada.

SP_CYCLE_ERRORLOG: Executa o DBCC ERRORLOG por debaixo dos panos. Essencialmente são idênticos. Mesmas considerações acima.

XP_ENUMERRORLOGS: Retorna um resumo com algumas informações sobre arquivos. Não é documentada.

- Archive# é o número do arquivo sendo 0 o atual (ERRORLOG) sem numeração.
– Date é a data da última modificação do arquivo (e não é, repito, não é necessáriamente a data em que o último evento foi registrado, isso é tema para próximos post, caso se interesse).
– Log File Size (Byte): Tamanho em bytes que o arquivo em questão está ocupando em disco.

Requer permissão de securityadmin para execução.

Exemplo de saída do comando xp_enumerrorlogs

Exemplo de saída do comando xp_enumerrorlogs

SP_ENUMERRORLOGS: Executa o XP_ENUMERRORLOGS. Essencialmente são a mesma coisa. Mesmas considerações acima.

XP_READERROLOG: Basicamente², realiza a leitura de logs do SQL Server através de alguns parâmetros executando uma biblioteca chamada xpstar.dll.
Um exemplo simples de uso:

exec xp_readerrorlog 0,1,N'login'

Primeiro parâmetro: Número do log que será lido. 0 é o arquivo atual.
Segundo parâmetro: 1 é a identificação dos arquivos de log. Você pode ler outros logs se desejar. Por exemplo, 2 é a identificação dos logs do agent;
Terceiro parâmetro: filtro textual. Em nosso caso, vai retornar todas as linhas do arquivo lido que contenham a palavra login. Seu funcionamento é similar ao comando LIKE ‘%TermoDesejadoAqui%’.

É importante ressaltar que, pelo fato da xp_readerrorlog ser uma procedure extendida não-documentada, e não é de conhecimento público todos os parâmetros possíveis de serem utilizados pela mesma. Até o momento, por exemplo, foi identificado que a procedure pode receber até sete (7) parâmetros diferentes.

Permissão para ser executada: sysadmin e securityadmin.

SP_READERRORLOG: Executa a xp_readerrorlog. Seriam essencialmente a mesma coisa se não fosse por um importante detalhe: a sp_readerrorlog realiza uma chamada na xp_readerrorlog porém a passagem de parâmetros é limitada.
Pela versão da sp, apenas quatro parâmetros são possíveis de serem passados (sendo essa a principal diferença entre ambas). Abaixo o código de chamada das procedures (comprovando a explicação dos parâmetros):

Agora dá pra ver fácilmente como funciona a chamada de uma, e a chamada de outra :)

Agora dá pra ver fácilmente como funciona a chamada de uma, e a chamada de outra :)

O comando abaixo utiliza seis (6) parâmetros e funciona.

xp_readerrorlog 0,1,'login', NULL,'2014-10-10 12:00:00','2014-10-17 09:00:00'

Se você trocar xp por sp, uma exceção será lançada (justamente, por passar de quatro parâmetros possíveis). Outra característica importante entre a XP e SP: a entrada textual da versão SP é VARCHAR enquanto a versão XP é texto unicode.


Conclusão

O intuito do post foi explicar um pouco mais sobre o ERRORLOG. Os dois próximos do assunto abordarão dicas e boas práticas em relação à ERRORLOG e um projeto que coloca em prática os outros dois posts.

Caso tenha alguma sugestão, crítica, dúvida ou qualquer outro comentário, fique à vontade para comentar.

[]’s


Observações

*¹ – Para efeitos práticos, quando falamos em serviços, restart = Stop + Start. Eu sei que parece óbvio, mas é bom evitar qualquer confusão.

*² – Na verdade essa procedure é tão mística que lê até logs que não são do SQL Server, mas esse assunto não será abordado aqui até pela falta de documentação.


Referências

Monitorando os logs de erro – http://msdn.microsoft.com/pt-br/library/ms191202(v=sql.100).aspx

Desafio #1 – Data Compression Labs

Books

Oi,

Antes de começar o próximo post do Compression Labs, tenho um pequeno e humilde desafio pra quem se interessar a responder (e eu até poderia dizer que estou fazendo isso pra ganhar  tempo fazendo de fazer um post direito, mas estaria sendo estupidamente honesto)…Vamos utilizar o script do Compression Labs #1 adaptado para montar o cenário: vamos criar desta vez duas tabelas, sendo que uma delas vai levar compressão de página.

Os testes foram realizados no SQL Server 2008 R2 Enterprise, mas valem também 2012 e 2014 Enterprise e similares (aka Developer e Evaluation).

 

USE MASTER
 
GO
 
IF EXISTS (SELECT NAME FROM SYS.DATABASES WHERE NAME LIKE '%DComp%')
BEGIN
 
ALTER DATABASE DComp SET RESTRICTED_USER;
DROP DATABASE  DComp;
 
END
 
GO
 
CREATE DATABASE DComp
GO
 
USE DComp
GO
 
CREATE TABLE dbo.Pessoa
(
ID INT IDENTITY (1,1) PRIMARY KEY NOT NULL,
NOME VARCHAR(50) NULL DEFAULT 'Nome de alguém',
DESCRICAO CHAR(8000) NULL DEFAULT REPLICATE('A',8000),
IDADE INT NULL
);
 
INSERT INTO DBO.PESSOA (NOME, DESCRICAO, IDADE) VALUES ('Quico','Realizando o teste da compressão',9);
INSERT INTO DBO.PESSOA (NOME, DESCRICAO, IDADE) VALUES ('Seu Madruga','Realizando o teste da compressão',50);
INSERT INTO DBO.PESSOA (NOME, DESCRICAO, IDADE) VALUES ('Chaves','Realizando o teste da compressão',8);
INSERT INTO DBO.PESSOA (NOME, DESCRICAO, IDADE) VALUES ('Chiquinha','Realizando o teste da compressão',7);
INSERT INTO DBO.PESSOA (NOME, DESCRICAO, IDADE) VALUES ('Bruxa do 71','Realizando o teste da compressão',50);
 
CREATE TABLE dbo.Pessoa2
(
ID INT IDENTITY (1,1) PRIMARY KEY NOT NULL,
NOME VARCHAR(50) NULL DEFAULT 'Nome de alguém',
DESCRICAO CHAR(8000) NULL DEFAULT REPLICATE('A',8000),
IDADE INT NULL
);
 
INSERT INTO DBO.PESSOA2 (NOME, DESCRICAO, IDADE) VALUES ('Quico','Realizando o teste da compressão',9);
INSERT INTO DBO.PESSOA2 (NOME, DESCRICAO, IDADE) VALUES ('Seu Madruga','Realizando o teste da compressão',50);
INSERT INTO DBO.PESSOA2 (NOME, DESCRICAO, IDADE) VALUES ('Chaves','Realizando o teste da compressão',8);
INSERT INTO DBO.PESSOA2 (NOME, DESCRICAO, IDADE) VALUES ('Chiquinha','Realizando o teste da compressão',7);
INSERT INTO DBO.PESSOA2 (NOME, DESCRICAO, IDADE) VALUES ('Bruxa do 71','Realizando o teste da compressão',50);
 
ALTER TABLE dbo.Pessoa2 REBUILD WITH (DATA_COMPRESSION=PAGE);

Tem uma DMF  bem interessante que verifica as propriedades físicas de um índice, a sys.dm_db_index_physical_stats, o que inclui não apenas informações sobre fragmentação, mas também sobre quantidade de páginas, e pra ser mais específico, também nos informa a quantidade de páginas comprimidas, através da coluna compressed_page_count. Segue informações do BOL.

Compressed1

Sabemos que a DMF possui três modos: LIMITED, SAMPLED E DETAILED. Vamos usar DETAILED pra apresentar algumas informações bem específicas dos índices, das duas tabelas.

Segue consulta:


SELECT
OBJECT_NAME(object_id) AS [Tabela],
INDEX_ID as indid,
INDEX_TYPE_DESC,
INDEX_DEPTH,
INDEX_LEVEL,
AVG_FRAGMENTATION_IN_PERCENT as [AVG_FRAG_IN_%],
PAGE_COUNT AS [PAGES],
AVG_PAGE_SPACE_USED_IN_PERCENT as [AVG_PAGE_SPACE_USED_IN_%],
RECORD_COUNT as [RECORDS],
-- MIN_RECORD_SIZE_IN_BYTES,
MAX_RECORD_SIZE_IN_BYTES,
-- AVG_RECORD_SIZE_IN_BYTES,
COMPRESSED_PAGE_COUNT

FROM sys.dm_db_index_physical_stats
(DB_ID(), null, NULL, NULL , 'DETAILED')
WHERE OBJECT_NAME(OBJECT_ID) IN ('Pessoa','Pessoa2')

Resultado:

ResultadoIndex

As perguntas:

1) Se nossa tabela é um índice cluster, porque temos apenas dois níveis na tabela Pessoa, e, mais estranho ainda, um nível na tabela Pessoa2? Que b+tree é essa que não tem raíz, nível intermediário e folha?

2)Porque a compression_page_count não retorna absolutamente nada pra tabela Pessoa2 sendo que temos página comprimida?

As perguntas não são difíceis (principalmente a primeira), mas também não estão “de graça” (acho, rs).
Desafio lançado está e até a próxima.

[]’s

Data Compression Labs #1 – Tipagem inteligente e páginas zumbis

Open books

Olá,

Compressão no SQL Server é de longe uma das features Enterprise mais importantes do produto presentes desde o SQL Server 2008.

Quem já implantou, viu (inúmeras) vantagens e quem não implantou tem vontade ou interesse. Já quem não gosta…caso patológico. Brincadeira, não conheço quem não tenha gostado.

Quando digo compressão  me refiro ao termo em geral, e o SQL Server entrega várias tecnologias de compressão. Os principais são  Backup Compression (abordado em um post passado) e Data Compression.

O post de hoje é sobre Data Compression.

Presumo que você já saiba o que é Data Compression e sabe a diferença entre Row Compression e Page Compression.

Caso contrário, recomendo a seção “Recomendações de Leitura” no final do post.

A ideia aqui é explorar um pouco mais os detalhes internos de dados comprimidos e eliminar alguns mitos que giram em torno da compressão de dados, além de comprovar algumas informações óbvias sobre a compressão.  Estou fazendo alguns testes com compressão e gostaria de aproveitar o momento e compartilhar alguns dos resultados aqui.

0.  Script do post

Segue script inicial para o post:


USE MASTER

GO

IF EXISTS (SELECT NAME FROM SYS.DATABASES WHERE NAME LIKE '%DComp%')

BEGIN

ALTER DATABASE DComp SET RESTRICTED_USER;

DROP DATABASE  DComp;

END

GO

CREATE DATABASE DComp

GO

USE DComp

GO

CREATE TABLE dbo.Pessoa

(

ID INT IDENTITY (1,1) PRIMARY KEY NOT NULL,

NOME VARCHAR(50) NULL DEFAULT 'Nome de alguém',

DESCRICAO CHAR(8000) NULL DEFAULT REPLICATE('A',8000),

IDADE INT NULL

);

INSERT INTO DBO.PESSOA (NOME, DESCRICAO, IDADE) VALUES ('Quico','Realizando o teste da compressão',9)

INSERT INTO DBO.PESSOA (NOME, DESCRICAO, IDADE) VALUES ('Seu Madruga','Realizando o teste da compressão',50)

INSERT INTO DBO.PESSOA (NOME, DESCRICAO, IDADE) VALUES ('Chaves','Realizando o teste da compressão',8)

INSERT INTO DBO.PESSOA (NOME, DESCRICAO, IDADE) VALUES ('Chiquinha','Realizando o teste da compressão',7)

INSERT INTO DBO.PESSOA (NOME, DESCRICAO, IDADE) VALUES ('Bruxa do 71','Realizando o teste da compressão',50)

INSERT INTO DBO.PESSOA (NOME, DESCRICAO, IDADE) VALUES ('Bruxa do 71','TATATATATATATATATATATATATATATATATATATATATATAKKKKKKTATATATATATATATATATATATATATATAKKKKK',50)

Até então, o script não é difícil de entender. A ideia é que essa tabela seja utilizada como exemplo para essa e demais postagens sobre o tema. Da forma como foi modelada, teremos um registro por página (8K). É a situação ideal para nosso primeiro tópico.

1. Tipagem eficiente do Row Compression na prática e páginas “zumbis “

Vamos usar o comado não-documentado (e provavelmente um dos mais “documentados” dentre os DBCC, hehe)  DBCC PAGE, e para enxergar a saída deste comando, vamos precisar direcionar a saída para um output acessível:


DBCC TRACEON (3604,-1)

DBCC TRACEON é um velho conhecido dentre os DBCC’s documentados. É uma das formas de se ativar traceflags no SQL Server. O TF 3604 faz com que determinados comandos entreguem seu resultado em forma textual em uma janela de resultados, o que é exatamente o que iremos fazer de agora em diante. Em resumo: Sem o 3604, sem brincadeiras (a não ser que você use o DBCC PAGE xxx WITH TABLERESULTS, mas isso é outra história). O -1 significa que estou ativando o TF em escopo global, e não em escopo de sessão. Estou fazendo isso em uma instância de treinamento por motivos de praticidade, não é obrigatório se você utilizar a mesma sessão caso queira realizar os testes.

Agora, para capturar o número das páginas para explorá-las, vamos utilizar a função  fn_PhysLocCracker, que retorna de modo amigável a localização física de um registro. É uma das várias formas de se obter a localização física de um registro. Notamos algo interessante na imagem a seguir, e que foi intencional: cada registro está em uma página diferente (ou seja, não coube mais de um registro por página).

SELECT * FROM dbo.Pessoa AS P CROSS APPLY sys.fn_PhysLocCracker(%%physloc%%) AS FPLC
ORDER BY FPLC.file_id, FPLC.page_id, FPLC.slot_id;

Informações das páginas

Usamos a query acima por dois motivos: o primeiro era realizar o carregamento das páginas para o Data Cache (continue acompanhando, já explicarei o motivo) e o segundo é obter as informações que vamos precisar passar para o DBCC PAGE. A estrutura do DBCC PAGE é:


DBCC PAGE('database_name ou database_id',file_id,page_id,@param_visualizacao)

O primeiro parâmetro é para identificar a base, o segundo é o file_id (sem usar o PhysLocCracker é intuitivo que estamos falando do file_id = 1 em nosso exemplo por se tratar de um data file que também é o filegroup primário (já que não criamos outros arquivos no código de exemplo). O terceiro é o page_id, e esse sim é o motivo de usarmos a função: para obter a localização física (entenda-se página de dados, de 8K) do registro. O último parâmetro dita o modo de visualização que o DBCC PAGE oferece. O parâmetro 3 provê informações interessantes sobre as linhas (pra ser mais específico, linha por linha), então vamos usá-lo.

Como exemplo, vamos dar uma olhada na página onde o registro “Chaves” está localizado (página 93)

O conteúdo a seguir é a saída integral (sem cortes) do comando que executei.


DBCC PAGE('DComp',1,93,3)

Segue resultado da página.  Coloquei no blog para fins de documentação, caso você queira ignorar e pular para o essencial, postei uma imagem logo em seguida da saída. A ideia é mostrar e estrutura de uma página antes e depois da compressão.


DBCC execution completed. If DBCC printed error messages, contact your system administrator.

PAGE: (1:93)

BUFFER:

BUF @0x000000008BFA87C0

bpage = 0x000000008B16A000           bhash = 0x0000000000000000           bpageno = (1:93)

bdbid = 25                           breferences = 0                      bcputicks = 0

bsampleCount = 0                     bUse1 = 5047                         bstat = 0xc0010b

blog = 0x9a2159bb                    bnext = 0x0000000000000000

PAGE HEADER:

Page @0x000000008B16A000

m_pageId = (1:93)                    m_headerVersion = 1                  m_type = 1

m_typeFlagBits = 0x4                 m_level = 0                          m_flagBits = 0x0

m_objId (AllocUnitId.idObj) = 29     m_indexId (AllocUnitId.idInd) = 256

Metadata: AllocUnitId = 72057594039828480

Metadata: PartitionId = 72057594038779904                                 Metadata: IndexId = 1

Metadata: ObjectId = 2105058535      m_prevPage = (1:90)                  m_nextPage = (1:94)

pminlen = 8012                       m_slotCnt = 1                        m_freeCnt = 69

m_freeData = 8121                    m_reservedCnt = 0                    m_lsn = (24:200:8)

m_xactReserved = 0                   m_xdesId = (0:0)                     m_ghostRecCnt = 0

m_tornBits = 0

Allocation Status

GAM (1:2) = ALLOCATED                SGAM (1:3) = NOT ALLOCATED

PFS (1:1) = 0x60 MIXED_EXT ALLOCATED   0_PCT_FULL                         DIFF (1:6) = CHANGED

ML (1:7) = NOT MIN_LOGGED

Slot 0 Offset 0x60 Length 8025

Record Type = PRIMARY_RECORD         Record Attributes =  NULL_BITMAP VARIABLE_COLUMNS

Record Size = 8025

Memory Dump @0x000000000E23A060

0000000000000000:   30004c1f 03000000 5265616c 697a616e †0.L…..Realizan

0000000000000010:   646f206f 20746573 74652064 6120636f †do o teste da co

0000000000000020:   6d707265 7373e36f 20202020 20202020 †mpressão

0000000000000030:   20202020 20202020 20202020 20202020 †

0000000000000040:   20202020 20202020 20202020 20202020 †

0000000000000050:   20202020 20202020 20202020 20202020 †

0000000000000060:   20202020 20202020 20202020 20202020 †

0000000000000070:   20202020 20202020 20202020 20202020 †

0000000000000080:   20202020 20202020 20202020 20202020 †

0000000000000090:   20202020 20202020 20202020 20202020 †

00000000000000A0:   20202020 20202020 20202020 20202020 †

00000000000000B0:   20202020 20202020 20202020 20202020 †

00000000000000C0:   20202020 20202020 20202020 20202020 †

00000000000000D0:   20202020 20202020 20202020 20202020 †

00000000000000E0:   20202020 20202020 20202020 20202020 †

00000000000000F0:   20202020 20202020 20202020 20202020 †

0000000000000100:   20202020 20202020 20202020 20202020 †

0000000000000110:   20202020 20202020 20202020 20202020 †

0000000000000120:   20202020 20202020 20202020 20202020 †

0000000000000130:   20202020 20202020 20202020 20202020 †

0000000000000140:   20202020 20202020 20202020 20202020 †

0000000000000150:   20202020 20202020 20202020 20202020 †

0000000000000160:   20202020 20202020 20202020 20202020 †

0000000000000170:   20202020 20202020 20202020 20202020 †

0000000000000180:   20202020 20202020 20202020 20202020 †

0000000000000190:   20202020 20202020 20202020 20202020 †

00000000000001A0:   20202020 20202020 20202020 20202020 †

00000000000001B0:   20202020 20202020 20202020 20202020 †

00000000000001C0:   20202020 20202020 20202020 20202020 †

00000000000001D0:   20202020 20202020 20202020 20202020 †

00000000000001E0:   20202020 20202020 20202020 20202020 †

00000000000001F0:   20202020 20202020 20202020 20202020 †

0000000000000200:   20202020 20202020 20202020 20202020 †

0000000000000210:   20202020 20202020 20202020 20202020 †

0000000000000220:   20202020 20202020 20202020 20202020 †

0000000000000230:   20202020 20202020 20202020 20202020 †

0000000000000240:   20202020 20202020 20202020 20202020 †

0000000000000250:   20202020 20202020 20202020 20202020 †

0000000000000260:   20202020 20202020 20202020 20202020 †

0000000000000270:   20202020 20202020 20202020 20202020 †

0000000000000280:   20202020 20202020 20202020 20202020 †

0000000000000290:   20202020 20202020 20202020 20202020 †

00000000000002A0:   20202020 20202020 20202020 20202020 †

00000000000002B0:   20202020 20202020 20202020 20202020 †

00000000000002C0:   20202020 20202020 20202020 20202020 †

00000000000002D0:   20202020 20202020 20202020 20202020 †

00000000000002E0:   20202020 20202020 20202020 20202020 †

00000000000002F0:   20202020 20202020 20202020 20202020 †

0000000000000300:   20202020 20202020 20202020 20202020 †

0000000000000310:   20202020 20202020 20202020 20202020 †

0000000000000320:   20202020 20202020 20202020 20202020 †

0000000000000330:   20202020 20202020 20202020 20202020 †

0000000000000340:   20202020 20202020 20202020 20202020 †

0000000000000350:   20202020 20202020 20202020 20202020 †

0000000000000360:   20202020 20202020 20202020 20202020 †

0000000000000370:   20202020 20202020 20202020 20202020 †

0000000000000380:   20202020 20202020 20202020 20202020 †

0000000000000390:   20202020 20202020 20202020 20202020 †

00000000000003A0:   20202020 20202020 20202020 20202020 †

00000000000003B0:   20202020 20202020 20202020 20202020 †

00000000000003C0:   20202020 20202020 20202020 20202020 †

00000000000003D0:   20202020 20202020 20202020 20202020 †

00000000000003E0:   20202020 20202020 20202020 20202020 †

00000000000003F0:   20202020 20202020 20202020 20202020 †

0000000000000400:   20202020 20202020 20202020 20202020 †

0000000000000410:   20202020 20202020 20202020 20202020 †

0000000000000420:   20202020 20202020 20202020 20202020 †

0000000000000430:   20202020 20202020 20202020 20202020 †

0000000000000440:   20202020 20202020 20202020 20202020 †

0000000000000450:   20202020 20202020 20202020 20202020 †

0000000000000460:   20202020 20202020 20202020 20202020 †

0000000000000470:   20202020 20202020 20202020 20202020 †

0000000000000480:   20202020 20202020 20202020 20202020 †

0000000000000490:   20202020 20202020 20202020 20202020 †

00000000000004A0:   20202020 20202020 20202020 20202020 †

00000000000004B0:   20202020 20202020 20202020 20202020 †

00000000000004C0:   20202020 20202020 20202020 20202020 †

00000000000004D0:   20202020 20202020 20202020 20202020 †

00000000000004E0:   20202020 20202020 20202020 20202020 †

00000000000004F0:   20202020 20202020 20202020 20202020 †

0000000000000500:   20202020 20202020 20202020 20202020 †

0000000000000510:   20202020 20202020 20202020 20202020 †

0000000000000520:   20202020 20202020 20202020 20202020 †

0000000000000530:   20202020 20202020 20202020 20202020 †

0000000000000540:   20202020 20202020 20202020 20202020 †

0000000000000550:   20202020 20202020 20202020 20202020 †

0000000000000560:   20202020 20202020 20202020 20202020 †

0000000000000570:   20202020 20202020 20202020 20202020 †

0000000000000580:   20202020 20202020 20202020 20202020 †

0000000000000590:   20202020 20202020 20202020 20202020 †

00000000000005A0:   20202020 20202020 20202020 20202020 †

00000000000005B0:   20202020 20202020 20202020 20202020 †

00000000000005C0:   20202020 20202020 20202020 20202020 †

00000000000005D0:   20202020 20202020 20202020 20202020 †

00000000000005E0:   20202020 20202020 20202020 20202020 †

00000000000005F0:   20202020 20202020 20202020 20202020 †

0000000000000600:   20202020 20202020 20202020 20202020 †

0000000000000610:   20202020 20202020 20202020 20202020 †

0000000000000620:   20202020 20202020 20202020 20202020 †

0000000000000630:   20202020 20202020 20202020 20202020 †

0000000000000640:   20202020 20202020 20202020 20202020 †

0000000000000650:   20202020 20202020 20202020 20202020 †

0000000000000660:   20202020 20202020 20202020 20202020 †

0000000000000670:   20202020 20202020 20202020 20202020 †

0000000000000680:   20202020 20202020 20202020 20202020 †

0000000000000690:   20202020 20202020 20202020 20202020 †

00000000000006A0:   20202020 20202020 20202020 20202020 †

00000000000006B0:   20202020 20202020 20202020 20202020 †

00000000000006C0:   20202020 20202020 20202020 20202020 †

00000000000006D0:   20202020 20202020 20202020 20202020 †

00000000000006E0:   20202020 20202020 20202020 20202020 †

00000000000006F0:   20202020 20202020 20202020 20202020 †

0000000000000700:   20202020 20202020 20202020 20202020 †

0000000000000710:   20202020 20202020 20202020 20202020 †

0000000000000720:   20202020 20202020 20202020 20202020 †

0000000000000730:   20202020 20202020 20202020 20202020 †

0000000000000740:   20202020 20202020 20202020 20202020 †

0000000000000750:   20202020 20202020 20202020 20202020 †

0000000000000760:   20202020 20202020 20202020 20202020 †

0000000000000770:   20202020 20202020 20202020 20202020 †

0000000000000780:   20202020 20202020 20202020 20202020 †

0000000000000790:   20202020 20202020 20202020 20202020 †

00000000000007A0:   20202020 20202020 20202020 20202020 †

00000000000007B0:   20202020 20202020 20202020 20202020 †

00000000000007C0:   20202020 20202020 20202020 20202020 †

00000000000007D0:   20202020 20202020 20202020 20202020 †

00000000000007E0:   20202020 20202020 20202020 20202020 †

00000000000007F0:   20202020 20202020 20202020 20202020 †

0000000000000800:   20202020 20202020 20202020 20202020 †

0000000000000810:   20202020 20202020 20202020 20202020 †

0000000000000820:   20202020 20202020 20202020 20202020 †

0000000000000830:   20202020 20202020 20202020 20202020 †

0000000000000840:   20202020 20202020 20202020 20202020 †

0000000000000850:   20202020 20202020 20202020 20202020 †

0000000000000860:   20202020 20202020 20202020 20202020 †

0000000000000870:   20202020 20202020 20202020 20202020 †

0000000000000880:   20202020 20202020 20202020 20202020 †

0000000000000890:   20202020 20202020 20202020 20202020 †

00000000000008A0:   20202020 20202020 20202020 20202020 †

00000000000008B0:   20202020 20202020 20202020 20202020 †

00000000000008C0:   20202020 20202020 20202020 20202020 †

00000000000008D0:   20202020 20202020 20202020 20202020 †

00000000000008E0:   20202020 20202020 20202020 20202020 †

00000000000008F0:   20202020 20202020 20202020 20202020 †

0000000000000900:   20202020 20202020 20202020 20202020 †

0000000000000910:   20202020 20202020 20202020 20202020 †

0000000000000920:   20202020 20202020 20202020 20202020 †

0000000000000930:   20202020 20202020 20202020 20202020 †

0000000000000940:   20202020 20202020 20202020 20202020 †

0000000000000950:   20202020 20202020 20202020 20202020 †

0000000000000960:   20202020 20202020 20202020 20202020 †

0000000000000970:   20202020 20202020 20202020 20202020 †

0000000000000980:   20202020 20202020 20202020 20202020 †

0000000000000990:   20202020 20202020 20202020 20202020 †

00000000000009A0:   20202020 20202020 20202020 20202020 †

00000000000009B0:   20202020 20202020 20202020 20202020 †

00000000000009C0:   20202020 20202020 20202020 20202020 †

00000000000009D0:   20202020 20202020 20202020 20202020 †

00000000000009E0:   20202020 20202020 20202020 20202020 †

00000000000009F0:   20202020 20202020 20202020 20202020 †

0000000000000A00:   20202020 20202020 20202020 20202020 †

0000000000000A10:   20202020 20202020 20202020 20202020 †

0000000000000A20:   20202020 20202020 20202020 20202020 †

0000000000000A30:   20202020 20202020 20202020 20202020 †

0000000000000A40:   20202020 20202020 20202020 20202020 †

0000000000000A50:   20202020 20202020 20202020 20202020 †

0000000000000A60:   20202020 20202020 20202020 20202020 †

0000000000000A70:   20202020 20202020 20202020 20202020 †

0000000000000A80:   20202020 20202020 20202020 20202020 †

0000000000000A90:   20202020 20202020 20202020 20202020 †

0000000000000AA0:   20202020 20202020 20202020 20202020 †

0000000000000AB0:   20202020 20202020 20202020 20202020 †

0000000000000AC0:   20202020 20202020 20202020 20202020 †

0000000000000AD0:   20202020 20202020 20202020 20202020 †

0000000000000AE0:   20202020 20202020 20202020 20202020 †

0000000000000AF0:   20202020 20202020 20202020 20202020 †

0000000000000B00:   20202020 20202020 20202020 20202020 †

0000000000000B10:   20202020 20202020 20202020 20202020 †

0000000000000B20:   20202020 20202020 20202020 20202020 †

0000000000000B30:   20202020 20202020 20202020 20202020 †

0000000000000B40:   20202020 20202020 20202020 20202020 †

0000000000000B50:   20202020 20202020 20202020 20202020 †

0000000000000B60:   20202020 20202020 20202020 20202020 †

0000000000000B70:   20202020 20202020 20202020 20202020 †

0000000000000B80:   20202020 20202020 20202020 20202020 †

0000000000000B90:   20202020 20202020 20202020 20202020 †

0000000000000BA0:   20202020 20202020 20202020 20202020 †

0000000000000BB0:   20202020 20202020 20202020 20202020 †

0000000000000BC0:   20202020 20202020 20202020 20202020 †

0000000000000BD0:   20202020 20202020 20202020 20202020 †

0000000000000BE0:   20202020 20202020 20202020 20202020 †

0000000000000BF0:   20202020 20202020 20202020 20202020 †

0000000000000C00:   20202020 20202020 20202020 20202020 †

0000000000000C10:   20202020 20202020 20202020 20202020 †

0000000000000C20:   20202020 20202020 20202020 20202020 †

0000000000000C30:   20202020 20202020 20202020 20202020 †

0000000000000C40:   20202020 20202020 20202020 20202020 †

0000000000000C50:   20202020 20202020 20202020 20202020 †

0000000000000C60:   20202020 20202020 20202020 20202020 †

0000000000000C70:   20202020 20202020 20202020 20202020 †

0000000000000C80:   20202020 20202020 20202020 20202020 †

0000000000000C90:   20202020 20202020 20202020 20202020 †

0000000000000CA0:   20202020 20202020 20202020 20202020 †

0000000000000CB0:   20202020 20202020 20202020 20202020 †

0000000000000CC0:   20202020 20202020 20202020 20202020 †

0000000000000CD0:   20202020 20202020 20202020 20202020 †

0000000000000CE0:   20202020 20202020 20202020 20202020 †

0000000000000CF0:   20202020 20202020 20202020 20202020 †

0000000000000D00:   20202020 20202020 20202020 20202020 †

0000000000000D10:   20202020 20202020 20202020 20202020 †

0000000000000D20:   20202020 20202020 20202020 20202020 †

0000000000000D30:   20202020 20202020 20202020 20202020 †

0000000000000D40:   20202020 20202020 20202020 20202020 †

0000000000000D50:   20202020 20202020 20202020 20202020 †

0000000000000D60:   20202020 20202020 20202020 20202020 †

0000000000000D70:   20202020 20202020 20202020 20202020 †

0000000000000D80:   20202020 20202020 20202020 20202020 †

0000000000000D90:   20202020 20202020 20202020 20202020 †

0000000000000DA0:   20202020 20202020 20202020 20202020 †

0000000000000DB0:   20202020 20202020 20202020 20202020 †

0000000000000DC0:   20202020 20202020 20202020 20202020 †

0000000000000DD0:   20202020 20202020 20202020 20202020 †

0000000000000DE0:   20202020 20202020 20202020 20202020 †

0000000000000DF0:   20202020 20202020 20202020 20202020 †

0000000000000E00:   20202020 20202020 20202020 20202020 †

0000000000000E10:   20202020 20202020 20202020 20202020 †

0000000000000E20:   20202020 20202020 20202020 20202020 †

0000000000000E30:   20202020 20202020 20202020 20202020 †

0000000000000E40:   20202020 20202020 20202020 20202020 †

0000000000000E50:   20202020 20202020 20202020 20202020 †

0000000000000E60:   20202020 20202020 20202020 20202020 †

0000000000000E70:   20202020 20202020 20202020 20202020 †

0000000000000E80:   20202020 20202020 20202020 20202020 †

0000000000000E90:   20202020 20202020 20202020 20202020 †

0000000000000EA0:   20202020 20202020 20202020 20202020 †

0000000000000EB0:   20202020 20202020 20202020 20202020 †

0000000000000EC0:   20202020 20202020 20202020 20202020 †

0000000000000ED0:   20202020 20202020 20202020 20202020 †

0000000000000EE0:   20202020 20202020 20202020 20202020 †

0000000000000EF0:   20202020 20202020 20202020 20202020 †

0000000000000F00:   20202020 20202020 20202020 20202020 †

0000000000000F10:   20202020 20202020 20202020 20202020 †

0000000000000F20:   20202020 20202020 20202020 20202020 †

0000000000000F30:   20202020 20202020 20202020 20202020 †

0000000000000F40:   20202020 20202020 20202020 20202020 †

0000000000000F50:   20202020 20202020 20202020 20202020 †

0000000000000F60:   20202020 20202020 20202020 20202020 †

0000000000000F70:   20202020 20202020 20202020 20202020 †

0000000000000F80:   20202020 20202020 20202020 20202020 †

0000000000000F90:   20202020 20202020 20202020 20202020 †

0000000000000FA0:   20202020 20202020 20202020 20202020 †

0000000000000FB0:   20202020 20202020 20202020 20202020 †

0000000000000FC0:   20202020 20202020 20202020 20202020 †

0000000000000FD0:   20202020 20202020 20202020 20202020 †

0000000000000FE0:   20202020 20202020 20202020 20202020 †

0000000000000FF0:   20202020 20202020 20202020 20202020 †

0000000000001000:   20202020 20202020 20202020 20202020 †

0000000000001010:   20202020 20202020 20202020 20202020 †

0000000000001020:   20202020 20202020 20202020 20202020 †

0000000000001030:   20202020 20202020 20202020 20202020 †

0000000000001040:   20202020 20202020 20202020 20202020 †

0000000000001050:   20202020 20202020 20202020 20202020 †

0000000000001060:   20202020 20202020 20202020 20202020 †

0000000000001070:   20202020 20202020 20202020 20202020 †

0000000000001080:   20202020 20202020 20202020 20202020 †

0000000000001090:   20202020 20202020 20202020 20202020 †

00000000000010A0:   20202020 20202020 20202020 20202020 †

00000000000010B0:   20202020 20202020 20202020 20202020 †

00000000000010C0:   20202020 20202020 20202020 20202020 †

00000000000010D0:   20202020 20202020 20202020 20202020 †

00000000000010E0:   20202020 20202020 20202020 20202020 †

00000000000010F0:   20202020 20202020 20202020 20202020 †

0000000000001100:   20202020 20202020 20202020 20202020 †

0000000000001110:   20202020 20202020 20202020 20202020 †

0000000000001120:   20202020 20202020 20202020 20202020 †

0000000000001130:   20202020 20202020 20202020 20202020 †

0000000000001140:   20202020 20202020 20202020 20202020 †

0000000000001150:   20202020 20202020 20202020 20202020 †

0000000000001160:   20202020 20202020 20202020 20202020 †

0000000000001170:   20202020 20202020 20202020 20202020 †

0000000000001180:   20202020 20202020 20202020 20202020 †

0000000000001190:   20202020 20202020 20202020 20202020 †

00000000000011A0:   20202020 20202020 20202020 20202020 †

00000000000011B0:   20202020 20202020 20202020 20202020 †

00000000000011C0:   20202020 20202020 20202020 20202020 †

00000000000011D0:   20202020 20202020 20202020 20202020 †

00000000000011E0:   20202020 20202020 20202020 20202020 †

00000000000011F0:   20202020 20202020 20202020 20202020 †

0000000000001200:   20202020 20202020 20202020 20202020 †

0000000000001210:   20202020 20202020 20202020 20202020 †

0000000000001220:   20202020 20202020 20202020 20202020 †

0000000000001230:   20202020 20202020 20202020 20202020 †

0000000000001240:   20202020 20202020 20202020 20202020 †

0000000000001250:   20202020 20202020 20202020 20202020 †

0000000000001260:   20202020 20202020 20202020 20202020 †

0000000000001270:   20202020 20202020 20202020 20202020 †

0000000000001280:   20202020 20202020 20202020 20202020 †

0000000000001290:   20202020 20202020 20202020 20202020 †

00000000000012A0:   20202020 20202020 20202020 20202020 †

00000000000012B0:   20202020 20202020 20202020 20202020 †

00000000000012C0:   20202020 20202020 20202020 20202020 †

00000000000012D0:   20202020 20202020 20202020 20202020 †

00000000000012E0:   20202020 20202020 20202020 20202020 †

00000000000012F0:   20202020 20202020 20202020 20202020 †

0000000000001300:   20202020 20202020 20202020 20202020 †

0000000000001310:   20202020 20202020 20202020 20202020 †

0000000000001320:   20202020 20202020 20202020 20202020 †

0000000000001330:   20202020 20202020 20202020 20202020 †

0000000000001340:   20202020 20202020 20202020 20202020 †

0000000000001350:   20202020 20202020 20202020 20202020 †

0000000000001360:   20202020 20202020 20202020 20202020 †

0000000000001370:   20202020 20202020 20202020 20202020 †

0000000000001380:   20202020 20202020 20202020 20202020 †

0000000000001390:   20202020 20202020 20202020 20202020 †

00000000000013A0:   20202020 20202020 20202020 20202020 †

00000000000013B0:   20202020 20202020 20202020 20202020 †

00000000000013C0:   20202020 20202020 20202020 20202020 †

00000000000013D0:   20202020 20202020 20202020 20202020 †

00000000000013E0:   20202020 20202020 20202020 20202020 †

00000000000013F0:   20202020 20202020 20202020 20202020 †

0000000000001400:   20202020 20202020 20202020 20202020 †

0000000000001410:   20202020 20202020 20202020 20202020 †

0000000000001420:   20202020 20202020 20202020 20202020 †

0000000000001430:   20202020 20202020 20202020 20202020 †

0000000000001440:   20202020 20202020 20202020 20202020 †

0000000000001450:   20202020 20202020 20202020 20202020 †

0000000000001460:   20202020 20202020 20202020 20202020 †

0000000000001470:   20202020 20202020 20202020 20202020 †

0000000000001480:   20202020 20202020 20202020 20202020 †

0000000000001490:   20202020 20202020 20202020 20202020 †

00000000000014A0:   20202020 20202020 20202020 20202020 †

00000000000014B0:   20202020 20202020 20202020 20202020 †

00000000000014C0:   20202020 20202020 20202020 20202020 †

00000000000014D0:   20202020 20202020 20202020 20202020 †

00000000000014E0:   20202020 20202020 20202020 20202020 †

00000000000014F0:   20202020 20202020 20202020 20202020 †

0000000000001500:   20202020 20202020 20202020 20202020 †

0000000000001510:   20202020 20202020 20202020 20202020 †

0000000000001520:   20202020 20202020 20202020 20202020 †

0000000000001530:   20202020 20202020 20202020 20202020 †

0000000000001540:   20202020 20202020 20202020 20202020 †

0000000000001550:   20202020 20202020 20202020 20202020 †

0000000000001560:   20202020 20202020 20202020 20202020 †

0000000000001570:   20202020 20202020 20202020 20202020 †

0000000000001580:   20202020 20202020 20202020 20202020 †

0000000000001590:   20202020 20202020 20202020 20202020 †

00000000000015A0:   20202020 20202020 20202020 20202020 †

00000000000015B0:   20202020 20202020 20202020 20202020 †

00000000000015C0:   20202020 20202020 20202020 20202020 †

00000000000015D0:   20202020 20202020 20202020 20202020 †

00000000000015E0:   20202020 20202020 20202020 20202020 †

00000000000015F0:   20202020 20202020 20202020 20202020 †

0000000000001600:   20202020 20202020 20202020 20202020 †

0000000000001610:   20202020 20202020 20202020 20202020 †

0000000000001620:   20202020 20202020 20202020 20202020 †

0000000000001630:   20202020 20202020 20202020 20202020 †

0000000000001640:   20202020 20202020 20202020 20202020 †

0000000000001650:   20202020 20202020 20202020 20202020 †

0000000000001660:   20202020 20202020 20202020 20202020 †

0000000000001670:   20202020 20202020 20202020 20202020 †

0000000000001680:   20202020 20202020 20202020 20202020 †

0000000000001690:   20202020 20202020 20202020 20202020 †

00000000000016A0:   20202020 20202020 20202020 20202020 †

00000000000016B0:   20202020 20202020 20202020 20202020 †

00000000000016C0:   20202020 20202020 20202020 20202020 †

00000000000016D0:   20202020 20202020 20202020 20202020 †

00000000000016E0:   20202020 20202020 20202020 20202020 †

00000000000016F0:   20202020 20202020 20202020 20202020 †

0000000000001700:   20202020 20202020 20202020 20202020 †

0000000000001710:   20202020 20202020 20202020 20202020 †

0000000000001720:   20202020 20202020 20202020 20202020 †

0000000000001730:   20202020 20202020 20202020 20202020 †

0000000000001740:   20202020 20202020 20202020 20202020 †

0000000000001750:   20202020 20202020 20202020 20202020 †

0000000000001760:   20202020 20202020 20202020 20202020 †

0000000000001770:   20202020 20202020 20202020 20202020 †

0000000000001780:   20202020 20202020 20202020 20202020 †

0000000000001790:   20202020 20202020 20202020 20202020 †

00000000000017A0:   20202020 20202020 20202020 20202020 †

00000000000017B0:   20202020 20202020 20202020 20202020 †

00000000000017C0:   20202020 20202020 20202020 20202020 †

00000000000017D0:   20202020 20202020 20202020 20202020 †

00000000000017E0:   20202020 20202020 20202020 20202020 †

00000000000017F0:   20202020 20202020 20202020 20202020 †

0000000000001800:   20202020 20202020 20202020 20202020 †

0000000000001810:   20202020 20202020 20202020 20202020 †

0000000000001820:   20202020 20202020 20202020 20202020 †

0000000000001830:   20202020 20202020 20202020 20202020 †

0000000000001840:   20202020 20202020 20202020 20202020 †

0000000000001850:   20202020 20202020 20202020 20202020 †

0000000000001860:   20202020 20202020 20202020 20202020 †

0000000000001870:   20202020 20202020 20202020 20202020 †

0000000000001880:   20202020 20202020 20202020 20202020 †

0000000000001890:   20202020 20202020 20202020 20202020 †

00000000000018A0:   20202020 20202020 20202020 20202020 †

00000000000018B0:   20202020 20202020 20202020 20202020 †

00000000000018C0:   20202020 20202020 20202020 20202020 †

00000000000018D0:   20202020 20202020 20202020 20202020 †

00000000000018E0:   20202020 20202020 20202020 20202020 †

00000000000018F0:   20202020 20202020 20202020 20202020 †

0000000000001900:   20202020 20202020 20202020 20202020 †

0000000000001910:   20202020 20202020 20202020 20202020 †

0000000000001920:   20202020 20202020 20202020 20202020 †

0000000000001930:   20202020 20202020 20202020 20202020 †

0000000000001940:   20202020 20202020 20202020 20202020 †

0000000000001950:   20202020 20202020 20202020 20202020 †

0000000000001960:   20202020 20202020 20202020 20202020 †

0000000000001970:   20202020 20202020 20202020 20202020 †

0000000000001980:   20202020 20202020 20202020 20202020 †

0000000000001990:   20202020 20202020 20202020 20202020 †

00000000000019A0:   20202020 20202020 20202020 20202020 †

00000000000019B0:   20202020 20202020 20202020 20202020 †

00000000000019C0:   20202020 20202020 20202020 20202020 †

00000000000019D0:   20202020 20202020 20202020 20202020 †

00000000000019E0:   20202020 20202020 20202020 20202020 †

00000000000019F0:   20202020 20202020 20202020 20202020 †

0000000000001A00:   20202020 20202020 20202020 20202020 †

0000000000001A10:   20202020 20202020 20202020 20202020 †

0000000000001A20:   20202020 20202020 20202020 20202020 †

0000000000001A30:   20202020 20202020 20202020 20202020 †

0000000000001A40:   20202020 20202020 20202020 20202020 †

0000000000001A50:   20202020 20202020 20202020 20202020 †

0000000000001A60:   20202020 20202020 20202020 20202020 †

0000000000001A70:   20202020 20202020 20202020 20202020 †

0000000000001A80:   20202020 20202020 20202020 20202020 †

0000000000001A90:   20202020 20202020 20202020 20202020 †

0000000000001AA0:   20202020 20202020 20202020 20202020 †

0000000000001AB0:   20202020 20202020 20202020 20202020 †

0000000000001AC0:   20202020 20202020 20202020 20202020 †

0000000000001AD0:   20202020 20202020 20202020 20202020 †

0000000000001AE0:   20202020 20202020 20202020 20202020 †

0000000000001AF0:   20202020 20202020 20202020 20202020 †

0000000000001B00:   20202020 20202020 20202020 20202020 †

0000000000001B10:   20202020 20202020 20202020 20202020 †

0000000000001B20:   20202020 20202020 20202020 20202020 †

0000000000001B30:   20202020 20202020 20202020 20202020 †

0000000000001B40:   20202020 20202020 20202020 20202020 †

0000000000001B50:   20202020 20202020 20202020 20202020 †

0000000000001B60:   20202020 20202020 20202020 20202020 †

0000000000001B70:   20202020 20202020 20202020 20202020 †

0000000000001B80:   20202020 20202020 20202020 20202020 †

0000000000001B90:   20202020 20202020 20202020 20202020 †

0000000000001BA0:   20202020 20202020 20202020 20202020 †

0000000000001BB0:   20202020 20202020 20202020 20202020 †

0000000000001BC0:   20202020 20202020 20202020 20202020 †

0000000000001BD0:   20202020 20202020 20202020 20202020 †

0000000000001BE0:   20202020 20202020 20202020 20202020 †

0000000000001BF0:   20202020 20202020 20202020 20202020 †

0000000000001C00:   20202020 20202020 20202020 20202020 †

0000000000001C10:   20202020 20202020 20202020 20202020 †

0000000000001C20:   20202020 20202020 20202020 20202020 †

0000000000001C30:   20202020 20202020 20202020 20202020 †

0000000000001C40:   20202020 20202020 20202020 20202020 †

0000000000001C50:   20202020 20202020 20202020 20202020 †

0000000000001C60:   20202020 20202020 20202020 20202020 †

0000000000001C70:   20202020 20202020 20202020 20202020 †

0000000000001C80:   20202020 20202020 20202020 20202020 †

0000000000001C90:   20202020 20202020 20202020 20202020 †

0000000000001CA0:   20202020 20202020 20202020 20202020 †

0000000000001CB0:   20202020 20202020 20202020 20202020 †

0000000000001CC0:   20202020 20202020 20202020 20202020 †

0000000000001CD0:   20202020 20202020 20202020 20202020 †

0000000000001CE0:   20202020 20202020 20202020 20202020 †

0000000000001CF0:   20202020 20202020 20202020 20202020 †

0000000000001D00:   20202020 20202020 20202020 20202020 †

0000000000001D10:   20202020 20202020 20202020 20202020 †

0000000000001D20:   20202020 20202020 20202020 20202020 †

0000000000001D30:   20202020 20202020 20202020 20202020 †

0000000000001D40:   20202020 20202020 20202020 20202020 †

0000000000001D50:   20202020 20202020 20202020 20202020 †

0000000000001D60:   20202020 20202020 20202020 20202020 †

0000000000001D70:   20202020 20202020 20202020 20202020 †

0000000000001D80:   20202020 20202020 20202020 20202020 †

0000000000001D90:   20202020 20202020 20202020 20202020 †

0000000000001DA0:   20202020 20202020 20202020 20202020 †

0000000000001DB0:   20202020 20202020 20202020 20202020 †

0000000000001DC0:   20202020 20202020 20202020 20202020 †

0000000000001DD0:   20202020 20202020 20202020 20202020 †

0000000000001DE0:   20202020 20202020 20202020 20202020 †

0000000000001DF0:   20202020 20202020 20202020 20202020 †

0000000000001E00:   20202020 20202020 20202020 20202020 †

0000000000001E10:   20202020 20202020 20202020 20202020 †

0000000000001E20:   20202020 20202020 20202020 20202020 †

0000000000001E30:   20202020 20202020 20202020 20202020 †

0000000000001E40:   20202020 20202020 20202020 20202020 †

0000000000001E50:   20202020 20202020 20202020 20202020 †

0000000000001E60:   20202020 20202020 20202020 20202020 †

0000000000001E70:   20202020 20202020 20202020 20202020 †

0000000000001E80:   20202020 20202020 20202020 20202020 †

0000000000001E90:   20202020 20202020 20202020 20202020 †

0000000000001EA0:   20202020 20202020 20202020 20202020 †

0000000000001EB0:   20202020 20202020 20202020 20202020 †

0000000000001EC0:   20202020 20202020 20202020 20202020 †

0000000000001ED0:   20202020 20202020 20202020 20202020 †

0000000000001EE0:   20202020 20202020 20202020 20202020 †

0000000000001EF0:   20202020 20202020 20202020 20202020 †

0000000000001F00:   20202020 20202020 20202020 20202020 †

0000000000001F10:   20202020 20202020 20202020 20202020 †

0000000000001F20:   20202020 20202020 20202020 20202020 †

0000000000001F30:   20202020 20202020 20202020 20202020 †

0000000000001F40:   20202020 20202020 08000000 04000001 †        ……..

0000000000001F50:   00591f43 68617665 73†††††††††††††††††.Y.Chaves

Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4

ID = 3

Slot 0 Column 2 Offset 0x1f53 Length 6 Length (physical) 6

NOME = Chaves

Slot 0 Column 3 Offset 0x8 Length 8000 Length (physical) 8000

DESCRICAO = [Error converting to string (length 8000 bytes)]

Slot 0 Column 4 Offset 0x1f48 Length 4 Length (physical) 4

IDADE = 8

Slot 0 Offset 0x0 Length 0 Length (physical) 0

KeyHashValue = (98ec012aa510)

DBCC execution completed. If DBCC printed error messages, contact your system administrator.


Muita informação acima, mas o que realmente precisamos para o post é só isso: mostrar um desperdício de espaço que uma modelagem ineficiente pode causar:

Compression2

O motivo é que o espaço desperdiçado se refere à coluna Descricao que é  CHAR(8000), e como já sabemos é um tipo de dado com tamanho fixo. Se em determinado registro, a coluna tem apenas 100 caracteres, na página, ele vai ocupar 8000 bytes. Se tiver 300 caracteres, os mesmos 8000. Bem, e a compressão ajudaria neste caso? Vamos comprimir e observar então….


ALTER TABLE dbo.Pessoa REBUILD WITH (DATA_COMPRESSION=PAGE)

Usei, através do REBUILD, PAGE COMPRESSION, que tem uma importante característica: realiza uma otimização de tipagem e uso real dos dados. Apenas para contextualizar: PAGE COMPRESSION implementa várias compressões (Dictionary, Row e Prefix), e sem dúvida a principal delas é a ROW Compression. Para o fim desta postagem, row ou page compression atingiriam o mesmo objetivo. Por motivos de praticidade (e por causa de outros exemplos que futuramente postarei), usei PAGE.

Row Compression, se fosse gente, ia bater com a mão na table e dizer: “Pô gente olha o desperdício aí. Bora fazer o seguinte: Campos fixo agora são variáveis e digo mais, a coluna vai pagar apenas o que de fato está utilizando. Se for nulo ou zero então…aí aí que a gente comprime mesmo”. Essa otimização de metadados (tipagem) e consumo é tão importante que está presente na PAGE e não é por acaso.

Vamos usar aquele método do fn_PhysLocCracker novamente pra comprovar uma coisa interessante:

Compression3

OH! Todos os registros couberam numa página (e com folga, percebemos que cabe muito mais que isso). Vamos ver como ficou o conteúdo internamente na página com compressão?


PAGE: (1:114)

BUFFER:

BUF @0x0000000086FA72C0

bpage = 0x0000000086132000           bhash = 0x0000000000000000           bpageno = (1:114)

bdbid = 25                           breferences = 0                      bcputicks = 0

bsampleCount = 0                     bUse1 = 6221                         bstat = 0xc0010b

blog = 0x1212121b                    bnext = 0x0000000000000000

PAGE HEADER:

Page @0x0000000086132000

m_pageId = (1:114)                   m_headerVersion = 1                  m_type = 1

m_typeFlagBits = 0x0                 m_level = 0                          m_flagBits = 0x0

m_objId (AllocUnitId.idObj) = 31     m_indexId (AllocUnitId.idInd) = 256

Metadata: AllocUnitId = 72057594039959552

Metadata: PartitionId = 72057594038845440                                 Metadata: IndexId = 1

Metadata: ObjectId = 2105058535      m_prevPage = (0:0)                   m_nextPage = (0:0)

pminlen = 4                          m_slotCnt = 6                        m_freeCnt = 7742

m_freeData = 438                     m_reservedCnt = 0                    m_lsn = (24:280:23)

m_xactReserved = 0                   m_xdesId = (0:0)                     m_ghostRecCnt = 0

m_tornBits = 0

Allocation Status

GAM (1:2) = ALLOCATED                SGAM (1:3) = ALLOCATED

PFS (1:1) = 0x60 MIXED_EXT ALLOCATED   0_PCT_FULL                         DIFF (1:6) = CHANGED

ML (1:7) = NOT MIN_LOGGED

Slot 0 Offset 0x60 Length 48

Record Type = (COMPRESSED) PRIMARY_RECORD                                 Record attributes =  LONG DATA REGION

Record size = 48

CD Array

CD array entry = Column 1 (cluster 0, CD array offset 0): 0x02 (ONE_BYTE_SHORT)

CD array entry = Column 2 (cluster 0, CD array offset 0): 0x06 (FIVE_BYTE_SHORT)

CD array entry = Column 3 (cluster 0, CD array offset 1): 0x0a (LONG)

CD array entry = Column 4 (cluster 0, CD array offset 1): 0x02 (ONE_BYTE_SHORT)

Record Memory Dump

000000000B47A060:   2104622a 81517569 636f8901 01002000 †!.b*.Quico‰… .

000000000B47A070:   5265616c 697a616e 646f206f 20746573 †Realizando o tes

000000000B47A080:   74652064 6120636f 6d707265 7373e36f †te da compressão

Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 1

ID = 1

Slot 0 Column 2 Offset 0x5 Length 5 Length (physical) 5

NOME = Quico

Slot 0 Column 3 Offset 0x10 Length 8000 Length (physical) 32

DESCRICAO = [Error converting to string (length 8000 bytes)]

Slot 0 Column 4 Offset 0xa Length 4 Length (physical) 1

IDADE = 9

Slot 0 Offset 0x0 Length 0 Length (physical) 0

KeyHashValue = (0de2a6f75d1a)

Slot 1 Offset 0x90 Length 56

Record Type = (COMPRESSED) PRIMARY_RECORD                                 Record attributes =  LONG DATA REGION

Record size = 56

CD Array

CD array entry = Column 1 (cluster 0, CD array offset 0): 0x02 (ONE_BYTE_SHORT)

CD array entry = Column 2 (cluster 0, CD array offset 0): 0x0a (LONG)

CD array entry = Column 3 (cluster 0, CD array offset 1): 0x0a (LONG)

CD array entry = Column 4 (cluster 0, CD array offset 1): 0x02 (ONE_BYTE_SHORT)

Record Memory Dump

000000000B47A090:   2104a22a 82b20102 000b002b 00536575 †!.¢*‚²…..+.Seu

000000000B47A0A0:   204d6164 72756761 5265616c 697a616e † MadrugaRealizan

000000000B47A0B0:   646f206f 20746573 74652064 6120636f †do o teste da co

000000000B47A0C0:   6d707265 7373e36f †††††††††††††††††††mpressão

Slot 1 Column 1 Offset 0x4 Length 4 Length (physical) 1

ID = 2

Slot 1 Column 2 Offset 0xd Length 11 Length (physical) 11

NOME = Seu Madruga

Slot 1 Column 3 Offset 0x18 Length 8000 Length (physical) 32

DESCRICAO = [Error converting to string (length 8000 bytes)]

Slot 1 Column 4 Offset 0x5 Length 4 Length (physical) 1

IDADE = 50

Slot 1 Offset 0x0 Length 0 Length (physical) 0

KeyHashValue = (31c013ac63e0)

Slot 2 Offset 0xc8 Length 49

Record Type = (COMPRESSED) PRIMARY_RECORD                                 Record attributes =  LONG DATA REGION

Record size = 49

CD Array

CD array entry = Column 1 (cluster 0, CD array offset 0): 0x02 (ONE_BYTE_SHORT)

CD array entry = Column 2 (cluster 0, CD array offset 0): 0x07 (SIX_BYTE_SHORT)

CD array entry = Column 3 (cluster 0, CD array offset 1): 0x0a (LONG)

CD array entry = Column 4 (cluster 0, CD array offset 1): 0x02 (ONE_BYTE_SHORT)

Record Memory Dump

000000000B47A0C8:   2104722a 83436861 76657388 01010020 †!.r*ƒChaves….

000000000B47A0D8:   00526561 6c697a61 6e646f20 6f207465 †.Realizando o te

000000000B47A0E8:   73746520 64612063 6f6d7072 657373e3 †ste da compressã

000000000B47A0F8:   6f†††††††††††††††††††††††††††††††††††o

Slot 2 Column 1 Offset 0x4 Length 4 Length (physical) 1

ID = 3

Slot 2 Column 2 Offset 0x5 Length 6 Length (physical) 6

NOME = Chaves

Slot 2 Column 3 Offset 0x11 Length 8000 Length (physical) 32

DESCRICAO = [Error converting to string (length 8000 bytes)]

Slot 2 Column 4 Offset 0xb Length 4 Length (physical) 1

IDADE = 8

Slot 2 Offset 0x0 Length 0 Length (physical) 0

KeyHashValue = (da21809a8949)

Slot 3 Offset 0xf9 Length 54

Record Type = (COMPRESSED) PRIMARY_RECORD                                 Record attributes =  LONG DATA REGION

Record size = 54

CD Array

CD array entry = Column 1 (cluster 0, CD array offset 0): 0x02 (ONE_BYTE_SHORT)

CD array entry = Column 2 (cluster 0, CD array offset 0): 0x0a (LONG)

CD array entry = Column 3 (cluster 0, CD array offset 1): 0x0a (LONG)

CD array entry = Column 4 (cluster 0, CD array offset 1): 0x02 (ONE_BYTE_SHORT)

Record Memory Dump

000000000B47A0F9:   2104a22a 84870102 00090029 00436869 †!.¢*„‡…        .).Chi

000000000B47A109:   7175696e 68615265 616c697a 616e646f †quinhaRealizando

000000000B47A119:   206f2074 65737465 20646120 636f6d70 † o teste da comp

000000000B47A129:   72657373 e36f††††††††††††††††††††††††ressão

Slot 3 Column 1 Offset 0x4 Length 4 Length (physical) 1

ID = 4

Slot 3 Column 2 Offset 0xd Length 9 Length (physical) 9

NOME = Chiquinha

Slot 3 Column 3 Offset 0x16 Length 8000 Length (physical) 32

DESCRICAO = [Error converting to string (length 8000 bytes)]

Slot 3 Column 4 Offset 0x5 Length 4 Length (physical) 1

IDADE = 7

Slot 3 Offset 0x0 Length 0 Length (physical) 0

KeyHashValue = (a365ea2df5bd)

Slot 4 Offset 0x12f Length 56

Record Type = (COMPRESSED) PRIMARY_RECORD                                 Record attributes =  LONG DATA REGION

Record size = 56

CD Array

CD array entry = Column 1 (cluster 0, CD array offset 0): 0x02 (ONE_BYTE_SHORT)

CD array entry = Column 2 (cluster 0, CD array offset 0): 0x0a (LONG)

CD array entry = Column 3 (cluster 0, CD array offset 1): 0x0a (LONG)

CD array entry = Column 4 (cluster 0, CD array offset 1): 0x02 (ONE_BYTE_SHORT)

Record Memory Dump

000000000B47A12F:   2104a22a 85b20102 000b002b 00427275 †!.¢*…²…..+.Bru

000000000B47A13F:   78612064 6f203731 5265616c 697a616e †xa do 71Realizan

000000000B47A14F:   646f206f 20746573 74652064 6120636f †do o teste da co

000000000B47A15F:   6d707265 7373e36f †††††††††††††††††††mpressão

Slot 4 Column 1 Offset 0x4 Length 4 Length (physical) 1

ID = 5

Slot 4 Column 2 Offset 0xd Length 11 Length (physical) 11

NOME = Bruxa do 71

Slot 4 Column 3 Offset 0x18 Length 8000 Length (physical) 32

DESCRICAO = [Error converting to string (length 8000 bytes)]

Slot 4 Column 4 Offset 0x5 Length 4 Length (physical) 1

IDADE = 50

Slot 4 Offset 0x0 Length 0 Length (physical) 0

KeyHashValue = (4884791b1f14)

Slot 5 Offset 0x167 Length 79

Record Type = (COMPRESSED) PRIMARY_RECORD                                 Record attributes =  LONG DATA REGION

Record size = 79

CD Array

CD array entry = Column 1 (cluster 0, CD array offset 0): 0x02 (ONE_BYTE_SHORT)

CD array entry = Column 2 (cluster 0, CD array offset 0): 0x0a (LONG)

CD array entry = Column 3 (cluster 0, CD array offset 1): 0x0a (LONG)

CD array entry = Column 4 (cluster 0, CD array offset 1): 0x02 (ONE_BYTE_SHORT)

Record Memory Dump

000000000B47A167:   2104a22a 86b20102 000b0042 00427275 †!.¢*†²…..B.Bru

000000000B47A177:   78612064 6f203731 54415441 54415441 †xa do 71TATATATA

000000000B47A187:   54415441 54415441 54415441 54415441 †TATATATATATATATA

000000000B47A197:   54415441 54415441 54415441 54415441 †TATATATATATATATA

000000000B47A1A7:   54415441 4b4b4b4b 4b4b5441 544154††††TATAKKKKKKTATAT

Slot 5 Column 1 Offset 0x4 Length 4 Length (physical) 1

ID = 6

Slot 5 Column 2 Offset 0xd Length 11 Length (physical) 11

NOME = Bruxa do 71

Slot 5 Column 3 Offset 0x18 Length 8000 Length (physical) 55

DESCRICAO = [Error converting to string (length 8000 bytes)]

Slot 5 Column 4 Offset 0x5 Length 4 Length (physical) 1

IDADE = 50

Slot 5 Offset 0x0 Length 0 Length (physical) 0

KeyHashValue = (74a6cc4021ee)

DBCC execution completed. If DBCC printed error messages, contact your system administrator.


Algumas das informações mais importantes abaixo:

Compression4

Coisas que chamam a atenção: a coluna de Descricao CHAR(8000) que antes era fixa agora é armazenada em página com tamanho variável e isso pode ser visto na prática. Essa informação é facilmente comprovada no destaque em verde (Length -( Physical) 32 (significa que os dados efetivamente ocupam 32 bytes). Trata-se de uma melhoria na tipagem, e o mesmo ocorre com outras colunas, como por exemplo, a coluna ID (que é um INT (4 bytes), mas como o valor 1 pode ser convertido em um tipo que use apenas 1 byte (comprove a informação acima na linha CD array entry = Column 1 ….. (ONE_BYTE_SHORT), a tipagem na página é alterada. Esse comportamento é observando tanto utilizando ROW como PAGE Compression.

E aqui vai um teste interessante…O que está em memória? As páginas comprimidas ou as páginas sem compressão? Bem, as páginas sem compressão foram carregadas primeiro. Será que foram substituídas pró-ativamente no Data Cache? Agora que as páginas sem compressão teoricamente são inúteis, Vamos comprovar na prática. Vamos utilizar uma das mil DMV’s disponíveis para verificar as páginas que estão em memória.


select * from sys.dm_os_buffer_descriptors where database_id = db_id('DComp') and file_id = 1 and page_id in (93,114)

Compression5

Lembrando que a página 93 é a pré-compressão e a 114 = 114 pós-compressão.

Comentando algumas informações destacadas.

Row_count:

Detalhe em vermelho –  Antes cabia um registro por página. Agora couberam 6, ou seja, toda a tabela, em uma só página (e caberia muito, mas muito mais páginas). Sabe o que isso significa? Menos páginas em memória, menos páginas sendo recuperadas do disco (que é um recurso lento), menos páginas pra uma query usar, e logo, mais velocidade….Em resumo, ótimo ganho. Muita gente pensa em economia de espaço mas nem todas pensam em economia de memória e relacionados. Claro que isso vai exigir uma descompressão na leitura das páginas e pra isso operações adicionais de CPU serão utilizadas. No geral, o tradeoff vale a pena. A regra de ouro é: teste em seu ambiente. Se você identificar que seu ambiente possui problemas com CPU e usar compressão pode ser arriscado, não use.

Free space in bytes:

Detalhe em azul – Mostra que coube pra página 93 apenas um registro na página e só sobraram 69 bytes na mesma. Muito pouco…. No caso da página 114, temos 7742 bytes livres. Matemática básica, mas levando em conta que o tamanho de uma página é de 8192 bytes…Qual seu ponto de vista: página meio cheia ou página meio vazia? =p

Is_modified:

Detalhe em verde – Mostra que trata-se de uma página suja (dirty page), ou seja, a versão da página  que está em  memória é diferente da que está em disco. As duas páginas estão sujas. Nenhuma novidade até então…

Você acha estranho a página sem compressão estar em memória? Se ela não vai ser mais utilizada….

Pode parecer estranho, pois:

  • Não existe pressão de memória no ambiente em questão. Logo, o Lazy Writer não foi executado (um dos processos que sincroniza memória com disco e libera páginas do data cache).
  • Nenhum CHECKPOINT foi executado (e mesmo se fosse, a página que não é mais necessária lá permaneceria, embora fossem marcadas como CLEAN PAGES (is_modified=0)….)
  • Não existe (até onde eu sei) nenhuma thread do SQL Server destinada a remover proativamente páginas do cache. Ou seja, não temos nenhum proactive writer da vida pra fazer isso (entendedores entenderão…).

Então, vamos ver se o CHECKPOINT tira a página da memória?

CHECKPOINT
select * from sys.dm_os_buffer_descriptors where database_id = db_id('DComp') and file_id = 1 and page_id in (93,114)

Aqui não tirou :)

Então primeiro ponto: Usou compression, não necessariamente a antiga página sairá do cache (não de imediato), nem com checkpoint. Faz sentido que ela suma com o Lazy Writer…Vamos ver isso também em outras postagens…

Próximo Lab…Lazy Writer e Compressão Seletiva

O segundo ponto é: temos uma tabela com compressão. O que acontece se eu adicionar registros nela? Eles serão comprimidos em tempo real ou serão adicionados em uma página sem  compressão, de modo que sejam comprimidos apenas no próximo rebuild com data compression?

Como criamos apenas uma tabela (que é um índice cluster),  observamos que a compressão ocorre em tempo real. Caso você tivesse além de uma tabela clusterizada, um índice não-cluster SEM compressão, os dados seriam inseridos comprimidos no índice cluster e sem compressão no índice não-cluster. Essa granularidade da compressão é bem interessante e permite soluções diversificadas.

Caso tenha alguma observação,  comentário, correção (principalmente), fique à vontade para comentar :)

[]’s

Recomendações de leitura

Data Compression: Strategy, Capacity Planning and Best Practices

http://technet.microsoft.com/en-us/library/dd894051(v=sql.100).aspx

Utilidades pro cotidiano – Image Resizer

1299817302678_f

(Disclaimer: Esse post não tem relação com Banco de dados e/ou SQL Server. Trata-se de recomendações gerais de aplicativos para a plataforma Windows).

Oi povo,

Sabe aquelas fotos gigantes que você tira por aí com uma câmera de alta resolução e quer reduzir o tamanho do arquivo sem perder qualidade? Das várias formas de se resolver um problema, apresento pra vocês o Image Resizer, que pode ser baixado direto do site do Codeplex (portal com iniaciativa de compartilhar softwares de código aberto).

Na maioria das vezes (pra não dizer quase sempre), imagens geralmente não precisam estar em tamanhos gigantes. Compartilhamento de imagens por Whatsapp, upload em redes sociais ou postagens de blog são um ótimos candidatos no no geral onde o Resizer é bem vindo, já que imagens mais leves e/ou menores são as melhores escolhas. Se você tem essa possibilidade, porque não dimensionar?

Como fazer

Botão direito na imagem.

1- Botão direito na imagem.

2 - Escolha uma opção para comprimir a página

2- Escolha um tamanho para redimensionar sua imagem (Medium é o padrão). Quando definir o tamanho desejado, clique em resize.

3 – Como exemplo, coloquei os arquivos lado a lado para comparação.

Fácil, rápido e útil. Pra terminar, três observações:

  • O Resizer vai manter as proporções da imagem original independente do tamanho escolhido (exceto Custom).
  • Você pode fazer o mesmo processo com mais de uma imagem ao mesmo tempo, basta selecionar as que você deseja e repetir o processo acima.
  • Fique à vontade para comentar se te ajudou, se você possui alternativas em relação ao Resizer, etc.

[]’s

DBCC LOGINFO e o mistério do FSeqNo

marmota presa num bueiro. Total random.

Esse post faz parte da série: pequenas curiosidades que (quase) ninguém se importa, tipo a notícia que o G1 divulgou da marmota que ficou presa numa tampa de bueiro nos EUA.

O comando DBCC LOGINFO é um comando não-documentado pela Microsoft. Mas existe. E tá aí firme e forte (e disponível) até hoje no código do SQL Server. Pode comprovar caso queira no SQL Server 2014. O que não quer dizer que estou recomendando o seu uso :)

Abaixo, resultado do comando exatamente depois que eu criei uma base qualquer.

SORRIA

 

 

 

 

 

 

 

 

E pra que ele é utilizado? Com ele, é possível visualizar de modo amigável os VLF’s do Log de Transação da base que está sendo utilizada no momento e descobrir duas informações importantes:

  1. Tamanho e quantidade dos VLF’s: Pelas linhas retornadas, você identifica se tem muitos VLF’s naquele T-log em específico. Pela coluna FileSize, identifica o tamanho. Esse é o caminho pra se identificar a famosa fragmentação no T-log.
  2. Status: 0 indica que aquele VLF está sem uso, 2 indica uso. Pra estudos, ela é ótima. Pra vida real mesmo, talvez seja útil para algum troubleshooting no arquivo de log.

Uma coluna que é bem interessante (mas por si só não diz muita coisa) é o FSeqNo, que é um número dinâmico que vai mudando de acordo com o crescimento e a movimentação circular nos VLF’s. Contudo, como o próprio nome sugere, deveria ser um número sequencial, certo?

Porque será que o único VLF utilizado (e ativado) começou do 89 e não de algum número como 1, por exemplo?

A resposta está na…..

Modelo caiModel :D

A Model é aquela base de sistema que ninguém acha importante mas sem ela muita coisa não acontece, e uma delas é a criação de uma base. Uma das coisas que você pode ter pensado é a seguinte: – Será que esse número 89 veio da model?

Dá pra descobrir isso fazendo um DBCC LOGINFO na model (só pra tira-teima):

88OH! O último VLF da model estava com o FSeqNo 88, e quando eu criei uma base nova, como todo filho deve ser, puxou os traços da mãe, e acrescentou um +1 no número da sequência. Em outras palavras, o FSeqNo da base recém-criada se baseia na model.

Curiosidade resolvida e documentada :p

Agora, três perguntas que podem estar sendo feitas:

  • Porque o FSeqNo dessa model tá “tão alto”? Aqui na minha máquina deu diferente.

Porque eu tava fazendo uns testes nela que logaram algumas transações e aí a sequência chegou nesse número aí.

    • Essa informação realmente é útil pra alguma coisa?

Bem, como esse número de sequência é mais de interesse do SQL do que nosso, não, não é de fato uma informação realmente útil. Mas é uma curiosidade legal pra quem está observando um pouco do T-log e alguns comandos relacionados. E é legal a gente pegar um caso assim, onde detalhes que muitos acharam randômicos, fazem algum sentido. MSSQL tá cheio de coisa nesse naipe.

  • Então porque você postou algo assim?

Porque eu me divirto bastante postando e fiquei um tempão sem postar (e não quero perder isso). Postar pra retornar ao hábito. E por mais improvável que seja, talvez alguém faça esse questionamento. E se você uma vez se perguntar, lembra deste post. My 0,5 cent :D

[]’s

 

 

 

Roles do SQL Server – Sysadmin

Superman-Unchained

Olá,

Conforme prometido, o post de hoje será sobre a última server role que faltava pra humilde série aqui do blog: sysadmin! Na verdade é mais um post sobre CONTROL SERVER do que sysadmin, mas anyway…

Você conhece quem é sysadmin em seu ambiente e até onde ele pode chegar?
Sysadmin tem o privilégio máximo dentro de uma instância, podendo realizar qualquer ação, sem restrições. Uma das definições mais sinceras que eu já vi sobre o tema:

youReadMeRightSysadmin

Tradução tosca e livre: Sim, você leu corretamente. Sysadmin é sysadmin (administrador do sistema) e tem poderes divinos na instância.

Basicamente, ele possui dois poderes bem intuitivos:

-  Pode realizar qualquer atividade no servidor;
–  IGNORA qualquer checagem de segurança realizada pelo SQL Server (Ato conhecido como Bypass);

Permission Check Algorithm e Bypass

Antes de explicar o que é o bypass, é importante citar o algoritmo de checagem de permissão (Permission Check Algorithm), utilizado quando qualquer comando é executado no servidor.  Não citarei detalhes nesse post, mas uma série de verificações é realizada quando qualquer comando é disparado na instância…Uma das primeiras verificações que o algoritmo faz no comando é localizar nas informações de permissionamento se há algum  DENY que esteja relacionado com os logins  envolvidos. Se houver algum DENY, o acesso é mal-sucedido

E porque o algoritmo procura o primeiro DENY que seja de seu interesse?

Porque o DENY (Negação) SEMPRE sobreescreve qualquer outra permissão. Essa é uma verdade para toda entidade no SQL Server, exceto uma certa role de servidor, e adivinha qual é? O Sysadmin. é claro! Não importa qual ação realize dentro de determinada instância, se naquele contexto de execução pertencer a um sysadmin, automaticamente o servidor diz: – “Ok, você pode fazer tudo aqui, não preciso sequer checar as suas permissões”. Já viu um empregado barrar o patrão da casa? É tipo isso.

E aí ocorre o que é chamado de Bypass, que em tradução livre, significa ignorar, e é literalmente o que acontece quando a execução é realizada no contexto de um sysadmin: ele ignora o algoritmo de checagem de permissão. É mais ou menos o que acontece quando um carro do DETRAN ou da PM (em exercício de sua profissão) faz um cavalo de pau e sobe no meio fio  acelerando. Não tem problema (pra eles) eles agirem assim. Contudo, seres normais normalmente seriam checados e barrados/multados, pois não possuem permissão pra fazer esse tipo de coisa.

Como o SQL Server não precisa realizar as checagens, internamente, é uma etapa a menos no ciclo de vida de um comando. Isso durante algum tempo levantou o seguinte argumento: “todo comando executado pelo sysadmin é mais rápido”.  Não é mito, é uma verdade por causa do bypass que ocorre entre o comando e as checagens de segurança do SQL Server, mas o ganho de desempenho na minha opinião não é justificável e logo tal argumento é irrelevante no quesito de performance.

Sysadmin pode tudo?

Sim, sysadmin faz tudo o que todas as roles faz e muito mais (como por exemplo, uso da conexão DAC, uso de procedures como xp_cmdshell, etc).  O SQL Server entende que, os logins inseridos nesta role são extremamente confiáveis e que não precisam ser verificados em momento algum.

Parece simples entender o quão poderosa é a role, mas ainda assim é comum encontrar perguntas como:

- Como eu proibo o sysadmin de fazer backup?
– Como eu proibo o sysadmin de logar no SQL Server?
– Como eu impeço o sysadmin de dropar login?

Uma resposta pobre (mas resolve o problema, rs): tire do sysadmin. Existem mil implementações malucas para impedir ações de sysadmin mas todas elas são ignoradas devido ao Bypass. Um bom exemplo são triggers: elas podem até restringir a atuação de um sysadmin, MAS, são frágeis podendo ser desabilitadas e/ou até alteradas pela suposta vítima.

Agora, uma resposta sensata: Talvez CONTROL SERVER seja a solução.

CONTROL SERVER

Existe uma permissão que foi criada com o intuito de ser uma alternativa  ao sysadmin chamada CONTROL SERVER. Essa permissão tem direito,  a fazer praticamente tudo o que um sysadmin faria. Como o proprio nome sugere, essa permissão concede o controle inteiro do servidor, e logo, imagina-se que seja algo perto do termo irrestrito.

Pergunta óbvia: Ué, então qual é a diferença entre essa permissão e a role sysadmin?

A resposta é simples! Como é uma permissão e não uma role, O SQL Server vai executar o algoritmo de checagem de permissões normalmente, sem realizar o bypass e vai considerar todas as etapas,  inclusive a etapa em que DENY é verificado! Ou seja, um DENY pode barrar quem possui um CONTROL SERVER.
Em outras palavras, é possível você ter um login com CONTROL SERVER mas que não possa criar bases (concedendo um DENY ALTER ANY DATABASE) por exemplo. Esse exemplo não se aplica para o sysadmin (é impossível negar que o sysadmin seja impedido de qualquer ação dentro determinada instância, conforme já vimos aqui). Contudo, um login com CONTROL SERVER já tem a faca e o queijo na mão: mesmo que ele tenha permissões negadas, existem N formas de ele mesmo recuperar as suas permissões através de escalação de privilégios. Vale lembrar também que um securable  com CONTROL SERVER pode ser propagado para outros logins mesmo que o WITH GRANT OPTION não tenha sido utilizado no ato da concessão do privilégio.

Outra observação: CONTROL SERVER apesar de ser a permissão mais elevada de todas não pode lidar diretamente com  server roles (por exemplo, incluir ou excluir alguém da role). Significa que um login com CONTROL SERVER não pode acrescentar outro em qualquer server role, e muito menos alterar ou remover logins dentre os sysadmin…

Securityadmin e Control Server, existe uma relação entre os dois?

Em várias documentações, artigos e etc, inclusive da Microsoft, é dito que você deve tratar o securityadmin como equivalente à sysadmin. Essa é uma declaração um tanto quanto séria, certo? Vimos que tecnicamente securityadmin tem permissões importantes porém diferentes e nem  tantas quanto um sysadmin tem, como foi visto no post sobre essa role, na mesma série.

Veja esse truque: mesmo que ele mesmo não possua a permissão CONTROL SERVER, o securityadmin pode conceder essa permissão para outro login. É um conceito similar ao de “escalação de privilégios”, o que vai possibilitar, de fato, se aproximar de um privilégio similar ao sysadmin.

Lembram do post onde o securityadmin conseguiu burlar a restriçao de criação de bases criando outro login?
A mesma lógica é utilizada no exemplo abaixo:


CREATE LOGIN guardinha WITH PASSWORD = '', CHECK_POLICY=OFF

EXEC SP_ADDSRVROLEMEMBER 'guardinha','securityadmin'

2) Logue como guardinha. Tente dropar uma base, e você verá que ocorrerá um erro, assim como aconteceu no post passado. Ainda como guardinha, crie um outro login e atribua a permissão de CONTROL SERVER pra ele.


CREATE LOGIN darthVader WITH PASSWORD ='',CHECK_POLICY=OFF;
 GO
 GRANT CONTROL SERVER TO darthVader ;

Pronto. Basta logar como DarthVader e ter a força, pra fazer quase tudo o que um sysadmin faria.

Conclusão: É correto dizer que securityadmin equivale a um sysadmin? NÃO, pois primeiramente é necessário escalar privilégios de outros logins (e isso é evitável). E ainda assim, existem coisas que só o sysadmin pode fazer.

Sysadmin Only

Nem tudo que o Sysadmin faz pode ser realizado por um login com CONTROL SERVER, pois caso algum código verifique:

  • Se o login está dentro de alguma SERVER ROLE, como por exemplo a função  IS_SRVROLEMEMBER();
  • Se determinado login está especificamente dentro da server role sysadmin (Via IF’s e similares);

Vamos sair da teoria e partir pra um exemplo prático? Esse é o código da SP_READERRORLOG, que é uma procedure utilizada para ler o errorlog (duh!), cuja concepção foi realizada pensando nos securityadmins.

Xp_readerrorlog De cara vamos uma verificação específica: se o login e/ou seu contexto de execução de quem tiver executando não for securityadmin, aborte a operação e mostre um erro. Um detalhe importante que vale citar aqui: como o sysadmin possui permissão total, ele passa em qualquer verificação da função IS_SRVROLEMEMBER.

É por isso que a única role que pode executar essa procedure além do securityadmin, é o sysadmin (pois ele retorna TRUE no valor de 1 em qualquer verificação realizada pela função IS_SRVROLEMEMBER). E justamente por causa dessa verificação, um login com CONTROL SERVER se não estiver em nenhuma das server roles citadas acima, não vai conseguir executar a procedure (simplesmente por causa da verificação que é bem específica). Curioso, não?

Existem vários workarounds pra resolver o problema, e alguns são óbvios e bastante fáceis. Não serão abordados aqui, mas são possíveis :)

Existem outras operações que também realizam a verificação específica de server roles, algumas especificamente o sysadmin.  Por exemplo, operações do SQL Server Agent, uso de conexão via DAC, dentre outros.
Quem deve ser Sysadmin?

A resposta é clara e simples: pessoas na qual você pode confiar a instância inteira do SQL Server.

E lembre-se da regra sagrada do milagre da multiplicação (e por um milagre divino muita gente não sabe disso): Um sysadmin pode atribuir o mesmo privilégio para outros.

Tenha cautela com o sysadmin que não sabe o poder que tem e acaba descobrindo! Sysadmin que acaba de descobrir que tem superpoderes é igual a um mutante dos X-men que acabou de descobrir que tem superpoderes mas não tem muita noção de como controlá-lo. E geralmente um poder mal controlado e mal aplicado pode resultar em desgraça desastres.

Embora CONTROL SERVER seja uma permissão extremamente poderosa e possui milagre da multiplicação (aka pode propagar sua permissão para outros), ainda é inferior ao sysadmin e pode ser uma opção a ser considerada.


E assim chega ao fim a série sobre server roles! Foi divertido escrevê-la.

Alguma observação? Elogio, crítica?

Fique à vontade para comentar.


Referências

Why CONTROL SERVER doesn’t cut it  (Bom exemplo sobre um caso onde o CONTROL SERVER não é suficiente)

http://www.sqlservercentral.com/blogs/brian_kelley/2010/10/26/why-control-server-doesn-t-cut-it/

Permissions (Leia mais sobre o Permission Check Algorithm)

http://msdn.microsoft.com/en-us/library/ms191291.aspx

Executar como usuário diferente : Runas com ShellRunas.exe

Imagem

Olá,

Dica rápida de hoje: Às vezes, você precisa executar algum programa sob o contexto de outro usuário do Windows. Geralmente esse objetivo é alcançado quando se segura SHIFT + Botão Direito para localizar a opção “Executar como usuário diferente” (opção em português)  ou Run as different User (em inglês), popularmente conhecida como Runas.

Um exemplo:

Imagem

Porém, podemos topar com programas que não possuem o Runas em seu contexto. No exemplo, vamos usar o Excel 2010.

Imagem

Existem diversas soluções pra resolver a falta desta opção e apenas uma delas  é abordada neste post: se não temos um runas, vamos criá-lo com a ajuda de um utilitário da suite sysinternals chamado ShellRunas.

O processo de instalação é bem simples:

1) Saiba onde encontrar o instalador do ShellRunes (ou copie ele para algum diretório que você saberá alcançar via cmd. No exemplo usei o C: raíz);

2) Abra o cmd (Winkey + R) ;

3) Navegue até o diretório onde se encontra o executável (cd… cd.. opa, chegou :D) e digite ShellRunas.exe /reg. Você deve receber uma mensagem de confirmação dizendo que a opção foi registrada no menu de contexto.

Imagem

Pronto, agora se você tinha algum problema ao executar programas que não tinham o Runas em seu contexto, foi resolvido:

Imagem

Caso você queira tirar a opção, faça a mesma coisa do tutorial porém use o parâmetro /unreg


Espero que a dica tenha servido para alguém, pois aí valeu a postagem.

Até o próximo post!

AlwaysOn no SQL Server 2014 e novidades lunares que talvez te interessem!

Imagem

“Esse cara pode ser um san admin futuramente” – Fonte: NASA

Boa noite!
Tá vendo aquela lua que brilha lá no céu?
A lua tecnicamente não brilha por si só, apenas reflete a luz do sol. Legal, né?
Quero compartilhar duas notícias (na verdade mais importantes do que realmente parecem) que sairam recentemente e vi na internet. Links do Olhar Digital:

Fantástico!

E o que isso tem relação com o SQL Server 2014?
Quero compartilhar com vocês três assuntos diferentes:

1) HA e DR com SQL Server 2014 AlwaysOn Availability Groups;
2) Quando a zueira disponibilidade não tem limites, nem na terra nem na lua;
3) O que podemos esperar daqui pra frente?;

O que  é AlwaysOn?

Introduzida no SQL Server 2012, uma nova tecnologia de alta disponibilidade (High Availability, aka HA) e Recuperação de Desastre (Disaster Recovery, aka DR) chegou atraindo bastante atenção no mercado. Estamos falando do AlwaysOn (que agora virou uma buzzword/categoria também), mais especificamente do AlwaysOn Availability Groups (Grupos de disponibilidade) que repetidamente será escrito como AG, neste post, que permite a replicação de um grupo de bases de dados.

Veja bem, isso é uma solução inédita e não se trata apenas da evolução de outras features do SQL Server, embora constantemente ela seja bastante associada com o Mirroring, com uma certa razão, embora muito superior a este , já que o mirroring possibilita criar réplicas mas sempre 1:1 enquanto Grupos de disponibilidade replicam, grupos de várias bases para N réplicas, onde o valor de N dependerá da versão do SQL Server utilizada.

Nem vou abordar outras tecnologias de Alta Disponibilidade e DR nesse post, pois não é o foco. Quero falar (e apenas um  pouco) do AG.

Creio que pela imagem abaixo, é fácil entender como funciona essa solução: Crie réplicas de grupos de banco de dados. Mas não apenas! Aproveite as suas réplicas.

 

Imagem

Printei do Introducing SQL Server 2012 – Ross Mistry e Stacia Misner

A grande maestria deste esquema é que você pode utilizar as réplicas para aliviar a carga de trabalho (workload) que as suas bases principais (que por razões de nomenclatura é chamada de réplica principal) possuem normalmente com operações bastante comuns, como por exemplo, Backup e realização de relatórios (e aqui pode incluir também aquele “SELECT simples” que envolve umas quinze tabelas de setenta mil linhas, direto em produção, kkkkk) além de, é claro, contar com uma excelente solução de Disaster Recovery (na minha opinião é a melhor tecnologia de DR do produto), enquanto também conta com Alta Disponibilidade caso seja necessário.

A replicação da réplica principal para a(s) secundária(s) pode ser síncrona (garantindo que a réplica secundária sempre terá o mesmo dado da principal: isso custa performance em troca desse tipo de segurança) e a assíncrona (bem mais performática, garantindo que a secundária terá o mesmo dado da principal, embora demore um pouquinho mais (e bem, bem pouco) que modo síncrono pra que haja essa igualdade).

Você pode configurar suas réplicas para terem preferências de atividades (por exemplo, uma réplica preferencial para leituras e outra pra retirada de backups, por exemplo) para melhor aproveitamento do seu ambiente! E como se já não fosse legal o suficiente, você pode contar com até quatro (4) réplicas secundárias.
Para maiores informações, verifique as referências do post.

E o AlwaysOn Availability Groups no SQL Server 2014?

Toda vez que leio algo sobre o 2014 sinceramente fico feliz em como ele traz boas surpresas, mesmo que talvez eu nunca vá utilizar em produção :D
Depois do Hekaton um grande destaque foi a melhoria realizada no AG.
Sente o drama:
– No 2012, era possível ter quatro réplicas secundárias. No 2014, esse número dobrou. Oito réplicas agora!
– Possibilidade de se ter secundários no Azure (!!!)

E pode não parecer tão importante assim (porque muita gente sabe que o Azure é legal mas por algum motivo que desconheço é bastante ignorado) mas traz um conceito bastante interessante quando falamos de Disaster Recovery: Replicação Geográfica (Geo-replication).

Geo-Replication?

Em resumo, Replicação Geográfica é uma abordagem utilizada pelo Windows Azure para manter sua estrutura extremamente estável, para garantir que não haja de forma alguma perda de dados. Existem vários Datacenters do Azure pelo mundo e os dados são replicados entre eles justamente para garantir tudo isso.

Em outras palavras: no momento que você envia uma réplica pro Azure,  de modo transparente a mesma  será replicada para outros Datacenters ao redor do mundo, garantindo uma confiança maior ainda na segurança dos seus dados (no sentido de não perdê-los em caso de desastre).

Já imaginou ter seus dados replicados para um site de DR fora de seu ambiente (justamente por estar na nuvem) e que tem redundância por ele mesmo mesmo que você não precise?

Isso é muito bacana da Cloud Computing inteligente e está aí disponível no SQL Server 2014 utilizando o Azure.

Tem também o armazenamento de backups do SQL Server para o Azure, mas esse assunto, embora seja muito interessante e relacionado com o tema Disaster Recovery, não será abordado aqui :)

Alguns vídeos caso tenha interesse sobre o Windows Azure (e seus humildes datacenters):

Mas bem, sabemos que temos vários Datacenters na terra. Existe alguma chance de termos fora dela?

Tá, e de onde você tirou a ideia de reunir lua, alwaysOn e SQL Server num post só?

O primeiro motivo é óbvio: tava muito tempo sem postar e PAM! Me aparecem essa duas notícias. Achei legal compartilhar.
O segundo motivo: falar um pouco (quase nada, mas já é alguma coisa) sobre SQL Server 2014.
O terceiro motivo é pra interligar um relato engraçado sobre tudo isso.

Que relato?

Era uma vez alguns prezados dentro de um carro em movimento, conversando exatamente sobre essa nova habilidade do AG no SQL Server 2014 onde ter réplicas no Azure é uma possibilidade. Até que o prezado eu#### comentou que se um meteoro cair na terra, ainda assim ia ter réplicas de pé pra de alguma forma manter a continuidade do negócio (vale lembrar que tal meteoro em momento algum foi citado como “suficiente pra destruir a terra inteira”).

O final dessa conversa foi bastante polêmica, pois existem várias versões sobre ela onde cada um entendeu uma coisa diferente (e pra deixar a brincadeira rolar, não vou contar a original, pois vão me chamar de tendencioso, obviamente. Stakeholders do assunto, fique à vontade para usar os comentários  *risos*) mas a conclusão que grande parte dos envolvidos saiu é que: é bem provável que todas as soluções de HA e DR não saiam do nosso planeta mesmo que se fosse pra algo perto, tipo a lua. Lembro como se tivesse escutado hoje as palavras “imagina a latência disso aí kkkkkk” e veja bem…voltamos ao começo do post com as notícias onde empresas já visam locais mais seguros que a terra para armazenar arquivos de extrema importância e agora, ficamos sabendo de um link wi-fi criado pela NASA e MIT que dá um banho na maioria das conexões existentes no Brasil.

Lembro que li isso rindo:

Imagem

Vou deixar esse dilema para os conspiracionistas de plantão.

Sendo direto: Existe alguém que acredita que a lua seja um lugar extremamente seguro pra guardar ativos que considera importante. Ou não é tão seguro, mas pelo menos tá longe da Terra (sacou o pensamento de contigência?).

Será que os grandes players do mercado de SGBD’s possuem interesse em criar alguma solução  por lá também? A partir do momento que for rentável, não duvidaria dessa possibilidade. Vamos acompanhar….

Alguém chuta em qual versão do SQL Server (20XX) teremos réplicas lunares?

Teremos edições com nome mais sugestivos, tipo Interplanetary Edition?

Tem algum trocadilho engraçado, opinião, crítica, elogio, piada, versão do relato do meteoro pra compartilhar?

[]’s

 

Referências:

Visão Geral AlwaysOn

Novidades no AlwaysOn no SQL Server 2014

 

Rápidas palavras sobre o Backup Compression

illformed-depth-of-field

Olá,

Existem várias técnicas e features no SQL Server relacionadas à compressão. Hoje vamos falar sobre compressão nativa de backup (não confundir com Data Compression).

O termo compressão na área de informática é o ato de reduzir o tamanho de um arquivo aplicando uma série de algoritmos, sendo  alguns deles destinados à retirar redundâncias após identificar padrões de dados repetidos(principalmente caracteres).

No final, o arquivo  que sofreu compressão possui menos bytes que sua forma original, e ainda assim, consegue representar o mesmo conteúdo quando for restaurado, pois assim como existe um processo para “encolher” os bytes de um formato normal para um formato comprimido, também existe um processo que realiza o inverso (traduz os bytes comprimidos para o formato original). Note que alguém precisa aplicar os algoritmos de compressão e fazer uma série de cálculos emcima deles. Alguém tem que trabalhar, não é mesmo? Esse alguém é a CPU, que realiza essa atividade tendo uma carga de trabalho maior que o normal.

Vantagens

O que podemos alcançar com a compressão? Menor espaço em disco ocupado pelos arquivos que sofreram compressão e ganho de desempenho em vários casos, seja na geração do arquivo ou na transmissão do mesmo via rede (se ele está menor, certamente demorará menos para ser copiado, por exemplo). Você já deve imaginar que um menor tamanho do arquivo nos traz uma vantagem inestimável: ganho em operações I/O, e a vantagem disso é refletida de várias formas, como por exemplo, a transferência via rede aumenta consideravelmente, menor uso de memória e menor espaço ocupado em disco.

A grande sacada da compressão de backup é que, no ato em que um BACKUP DATABASE, aquele backup está sofrendo compressão no ato da sua execução. O SQL Server não espera realizar o backup do arquivo para depois aplicar compressão. O arquivo em questão já “toca” o disco com tamanho reduzido.  E de quebra seu backup poderá ser realizado mais rápido.
Veja um exemplo de uma base depois de um COMPRESSION:

Exemplo de base depois de compact...não pera.

Exemplo de base depois de compact…não pera.

 

Errei a imagem…

Dois backups. Um com compressão e o outro não.

Dois backups. Um com compressão e o outro não.

 

“Desvantagens”

O uso da compressão exige um trabalho extra da CPU, e portanto, deve ser utilizada cuidadosamente, dependendo do cenário. Por exemplo, se você faz backup em horário de rush onde o servidor está com alto processamento de CPU, talvez não seja interessante tirar um backup comprimido, pois a transação do backup pode impactar em outras, gerando concorrência e atrasos nelas.

Na minha opinião, considerar isso como “desvantagem” é extremamente relativo. Prefiro chamar de “o preço que se paga”. Soa mais justo pois nada nesse mundo é de graça e não existe mágica sem truque. E com a compressão, não é diferente.

"Todo almoço tem seu preço, meu filho" - JOBS, Steve

“Todo almoço tem seu preço, meu filho” – JOBS, Bill

Se você tem esse trade-off onde você ganha economia de espaço e operações de I/O mais rápidas (bom pro disco, memória e rede) e em compensação precisa pagar por um pouco mais de processamento na CPU, será que não vale a pena? Na maioria dos casos onde o uso de CPU não é intensivo, será mesmo que existe desvantagem nessa troca?
Já trabalhei em um cenário onde backups compactados (FULL na Quarta e Domingo, DIFF nos demais dias) eram tirados toda noite por volta de 1h. Janela tranquila, CPU mais mansa que peixe beta, o uso deste recurso se mostrou bastante compensatório, principalmente porque no final os backups eram estocados em fita. Em resumo: a causa de maior medo ao utilizar o recurso que é o aumento de trabalho de CPU foi algo a ser desconsiderado nesse caso. E creio que existem inúmeros cenários similares.

Você pode utilizar compressão nos arquivos de backup FULL, DIFERENCIAL e LOG.

Restrições

As duas principais restrições envolvendo compressão de backup:

1) Quando você guarda seus backups em um mediaset (ou seja, mais de um backup por arquivo), todos devem ser comprimidos ou nenhum. Não se pode ter no mesmo mediaset um backup comprimido e outro não.

2) Restrições de versões/edições do SQL Server (ver o tópico Compatibilidade)

Para mais informações, verifique as referências no final do post.

 

Compatibilidade

O recurso foi introduzido no SQL Server 2008 Enterprise (Developer e Trial herdam também) e desde o SQL Server 2008 R2 está disponível em todas as edições do produto. Um detalhe importante é que se você tentar restaurar um backup compactado tirado duma instância 2008 Enterprise e tenta restaurar em uma instância 2008 Standard, um erro ocorrerá no Management Studio.

No  SQL Server 2005 e anteriores, não existe solução nativa para compressão de backups. A solução neste caso seria o uso de ferramentas de terceiros e o uso de criatividade: Uma das formas mais conhecidas é montar uma rotina em cmd shell pra realizar compressão utilizando algum utilitário, como o Winzip, para aplicar compressão DEPOIS que o backup já está em disco. Embora tais métodos não sejam tão práticos por não serem nativos, são possibilidades.

DEMO

Para ilustrar as diversas formas de compressão de backup, montei um cenário em ambiente de teste e tirei dois backups, um com compressão e outro sem compressão (modo normal).

Primeiramente, print do tamanho da base:

Prova Tamanho

 

 

 

 

 

E os backups realizados:

 

Script

 

Com esse simples teste, você já percebe algo surpreendente sobre a performance do backup com compressão pelos resultados comentados: ele demorou praticamente metade do tempo e consequentemente, realizou a operação de I/O com quase o dobro de eficiência. A parte divertida do negócio, além da performance incrível na realização do backup

 

Isso por si só (performance na parte de I/O) já é bastante significativa. Mas….e o tamanho do arquivo final?

Dois backups. Um com compressão e o outro não.

 

E tem como eu estimar o ganho que terei com a compressão de backup? Não existe para este caso nenhuma procedure (até o momento) nativa que realize essa estimativa, principalmente porque o ganho vai depender bastante do banco de dados em questão. A ideia é que você faça um backup utilizando compressão e analise os resultados. No banco de sistema MSDB, é possível verificar o ganho da base de exemplo:


SELECT
backup_set_id,
database_name,
backup_size,
compressed_backup_size,
backup_size/compressed_backup_size as [Ganho]
FROM msdb.dbo.backupset
WHERE database_name = 'DBIG'

O resultado:

Ganho

 

Sem fazer um código mais detalhado (com os devidos tratamentos), a última coluna revela que o backup comprimido é praticamente 6x menor que o tamanho original do backup (praticamente 83% menor).

 

Note também que no primeiro backup não utilizei compressão. O valor inserido na coluna compressed_backup_size é exatamente o valor do backup_size. Já no segundo caso, temos o tamanho correto da base comprimida. Exatamente por isso, que não é possível realizar estimativas (exatamente porque não existe um valor pra servir de base).

É importante mencionar também que, caso a base esteja utilizando TDE (Transparent Data Encryption), o ganho na compressão do backup é irrisória ou perto de zero.

EDIT: Se sua base já tiver compressão de dados, você pode utilizar compressão de backup também e pode se surpreender com a quantidade de ganhos que isso traz. Vários profissionais já fizeram o teste e comprovaram que o grau de compressão é ainda maior, e faz sentido. Imagine que a compressão de dados cria dicionários (utilizados pelos algoritmos de compressão) baseado em páginas e a compressão de backup cria um dicionário para a base inteira…

Como fazer?

Existem várias formas de realizar um backup compactado:

1. Uso de cláusula WITH COMPRESSION

Forma mais simples.

Compress

 

 

2.  Via SP_CONFIGURE

Permite que você mude a configuração padrão fazendo com que todo backup utilize compressão por padrão (mesmo sem declarar no corpo do backup a cláusula COMPRESSION nos parâmetros via WITH):

 

Run Value

 

 

 

 

 

 

Apenas ative essa opção se você estiver totalmente seguro que sabe o que está fazendo e o que será impactado.

3.  Via Management Studio

Sem tirar nem pôr faz a mesma coisa que a segunda opção (seta o default pra compressão e reconfigure), a diferença é que isso é feito de modo gráfico. Botão direito na instância, Propriedades, Database Settings e pronto:

Sem tirar nem por

 

Bônus: Para quem  tiver curiosidade, existe um trace flag que trabalhando junto com a compressão aumenta ainda mais a transferência de I/O em disco. O motivo disso é uma alteração no comportamento de estimativa de espaço que o SQL Server deve reservar pro backup que está sendo realizado. Caso se sinta curioso(a), veja o link nas referências [3].

 

E os restores?

EDIT: Depois de publicar o post, me perguntaram em relação à performance do RESTORE, se o ganho é tão perceptível quanto o BACKUP. Fiz uns testes rápidos na mesma máquina (e sob as mesmas condições de uso) e os resultados foram:

 

– Restore backup comprimido

RESTORE DATABASE successfully processed 484625 pages in 114.127 seconds (33.174 MB/sec).

– Restore backup normal

RESTORE DATABASE successfully processed 484627 pages in 133.096 seconds (28.446 MB/sec).

Mais um ponto pro backup comprimido =)

Finalizando
Compressão é sinônimo de economia. Em vários casos, essa economia pode ser tão compensatória em relação ao preço que se paga que podemos até dizer que é um recurso que economiza espaço de graça.

E aí, você já usou ou está pensando em usar compressão nativa do SQL Server nos backups?

Conhece ou usa alguma ferramenta de terceiros? Alguma observação sobre isso?

Quer relatar algum resultado de testes realizados?

Acha que é cilada?

É boa, pode usar sim?

Vai ter copa?

Fique à vontade para comentar.


 

Referências:

[1] Compressão - http://pt.wikipedia.org/wiki/Compress%C3%A3o_de_dados

[2] Backup Compression - http://technet.microsoft.com/pt-br/library/bb964719.aspx

[3] Trace Flag 3042http://support.microsoft.com/kb/2001026/pt-br

Vamos estudar SQL Server 2014?

Imagem

Oi,

Pra variar, é um dos posts que eu escrevo quando o pão já esfriou \o/

O SQL Server 2014 saiu oficialmente tem pouco tempo (1° de Abril) e muito tem se falado sobre ele.

Várias features melhoradas, abraça a nuvem como uma mãe abraça um recém-nascido e que tudo tá muito rápido, tão rápido que todo mundo que fala sobre ele se emociona/empolga. Agora temos dados não apenas em memória, mas designados para a memória. É outra história. É algo alucinadamente mais rápido que o convencional já nos traz. É outro paradigma que já fez o 2014  se tornar um marco, e que será algo ainda maior…

Porém, poucas pessoas de fato estão explorando as novidades e caso esse seja o nosso seu caso, que tal começar agora?


 

Mas e as referências? O que temos?

Download: Baixe a versão Trial aqui

EDIT: O Gustavo mencionou o Cumulative Update 1, que contém um bucado de fixes. Muito bem lembrado!

Documentação: Books online for the win.

Livro: Como manda a tradição, saiu uma nova versão do SQL Server, sai um livro. Procure por Introducing SQL Server 2014

Bases pra teste: se você deu um sorriso e pensou em AdventureWorks2014, ainda não. Mas tem a versão 2012 que pode ser o rato de laboratório enquanto ele não é atualizado.

O time de produto disponibilizou um script para transformar algumas tabelas do AW2012 em tabelas in-memory. Baixe aqui.

Novidades: Nada melhor do que o What’s New pra começar a ver o que tem de bom. Tem muita coisa que eleva o SQL Server em outro nível (Feature que tinha um nome daora, Hekaton, que agora se chama In-Memory OLTP), outras que podem auxiliar em determinados cenários onde se tem discos rápidos (exemplo: SSD’s mas não somente) como o Buffer Pool Extension e pra quem já é sortudo o bastante para trabalhar com AlwaysOn, houveram diversas melhorias, como a integração com o Azure, fazendo com que poucas coisas possam dificultar a continuidade do negócio, como por exemplo, um meteoro que destrua a terra inteira.

Pra conferir TODOS os demais recursos , clique aqui (referência completa e extensa).

Um compilado de várias séries realizadas sobre o assunto (MSFT Blogs)

Temos também vários artigos na internet, blogueiros, tuiteros e algumas pessoas da comunidade técnica falando sobre isso vez ou outra (e algumas falam sobre o 2014 desde 2013 inclusive, com conteúdo top!), e não é difícil encontrar tais conteúdos. Eu vou fazer minha parte e de vez em quando postar (humildemente) alguma coisa sobre isso também.

 


 

E bem, ainda quero terminar meu post sobre sysadmin da série Roles do SQL Server, vida e obra.

Em breve…