Seções iMasters
Banco de Dados + Desenvolvimento + MySQL + Oracle + SQL Server

Desperdiçando espaço em disco

Olá, pessoal. Hoje abordarei um assunto muito importante para quem trabalha com banco de dados de grandes tamanhos: o desperdício de espaço em disco devido ao uso incorreto de tipos de dados.

Como consultor de banco de dados, uma das principais atividades com que me deparo durante o trabalho é analisar o modelo de dados dos sistemas que já estão em produção. Este trabalho requer um conhecimento básico do que o sistema faz e auxilia na identificação das diversas razões e motivos de problemas, principalmente os relacionados ao desempenho do banco de dados e consumo excessivo de recursos.

Nas últimas consultorias que prestei, não pude deixar de notar a presença de uma prática não recomendada que, infelizmente, encontro com mais freqüência do que gostaria em diversos bancos de dados. Esta prática, que explicarei em seguida, está diretamente relacionada com a modelagem do banco de dados e com a cultura geral da falta de planejamento e modelagem adequada. Como conseqüência, diversos problemas aparecem, inclusive o desperdício de espaço em disco e problemas de desempenho.

Explicarei uma situação de desperdício evidente a partir da modelagem do banco de dados. Em situações normais de temperatura e pressão, após a elaboração de requisitos e a especificação do que o sistema deve fazer, a equipe de desenvolvimento monta um diagrama entidade-relacionamento com as tabelas, colunas, tipos de dados, relacionamentos, etc. O problema começa aqui: em muitas situações não se dá a devida atenção aos tipos de dados de certas colunas. Mais especificamente, acaba-se usando tipos de dados superdimensionados para determinadas colunas, o que ocupa mais espaço em disco do que o necessário. Mesmo com a taxa de custo/megabyte diminuindo a cada dia, isso não quer dizer que devemos esbanjar e evitar tomar certos cuidados durante a modelagem dos tipos de dados.

Um dos motivos para este super dimensionamento está relacionado com a falta de previsibilidade do crescimento e volume de dados que serão armazenados. Nem sempre é fácil identificar qual é o melhor tipo de dados para determinada coluna e, por causa disso, o modelo acaba pecando por excesso. Contudo, este pecado vai custar caro um dia devido ao crescimento.

Vou comentar um exemplo prático. Em uma das consultorias que prestei, me deparei com um banco de dados cujo tamanho ocupava cerca de 50 GB, o que é um tamanho considerável de acordo com o hardware, a finalidade da aplicação e do banco de dados neste contexto. Analisando melhor, me deparei com uma tabela que tinha aproximadamente 38 milhões de linhas e que ocupava cerca de 30 GB apenas considerando os dados armazenados. Com certeza o tamanho e volume desta tabela estavam associados aos problemas de desempenho do banco de dados, motivo pelo qual fui contratado para dar a consultoria.

Analisando com calma, identifiquei que esta tabela continha mais de 40 colunas. Sem entrar em detalhes, pude verificar que das 40 colunas, 22 eram do tipo dados inteiro (INT). Até aqui sem problemas. Porém, ao verificar o intervalo mínimo e máximo para cada coluna (com o uso de instrução SELECT MAX(), MIN() FROM TABELA), determinei que, das 22 colunas, 19 armazenavam apenas valores numéricos compreendidos entre 0 e 50.000. Outro detalhe interessante é que não havia sequer um valor negativo para estas 19 colunas no meio das 38 milhões de linhas. Este é um cenário típico onde o tipo de dados INT é utilizado como chave primária junto com uma propriedade de auto-numeração.

Se lembrarmos que o SQL Server possui o tipo de dados INT que ocupa 4 bytes, o tipo de dados SMALLINT ocupa 2 bytes e o tipo de dados TINYINT que ocupa 1 byte podemos ver claramente que o tipos de dados das 19 colunas foram super dimensionados.

Conversando com analistas, desenvolvedores e usuários ele confirmaram que estas colunas não armazenam valores grandes. Por exemplo: colunas com o número de agência bancária, o número da praça, o código do usuário que digitou o documento, a filial da empresa, etc. Com a confirmação de que todas estas colunas poderiam ter utilizado um tipo de dados que ocupa menos espaço, isto é, o SMALLINT ou o TINYINT no lugar do INT, parti para calcular o quando se ganharia de espaço na mudança de tipos.

Aqui vou considerar um cálculo simples de custo de linha. Para informações e mais detalhes sobre o custo por linha, recomendo a leitura dos seguintes artigos publicados anteriormente aqui no iMasters: Verificando a alocação de dados – Parte 1 e Verificando a alocação de dados.

Vamos aos cálculos:

  1. Das 19 colunas com o tipo de dados INT (4 bytes), verifiquei que 8 delas poderiam ser trocadas pelo tipo TINYINT (1 BYTE) e as 11 poderiam ser trocadas pelo tipo SMALLINT (4 bytes). Notem que o intervalo de valores do TINYINT é de 0 a 255, o intervalo de valores do SMALLINT é de -32.768 a 32.767 e o intervalo de valores do INT é de -2.147.483.648 a 2.147.483.647.
  2. Temos 19 colunas do tipo INT por linha. Como o tipo INT ocupa 4 bytes, a tabela ocupava 19×4 = 76 bytes por linha apenas com estas 19 colunas.
  3. Multiplicando 76 bytes pela quantidade de linhas da tabela, 38 milhões, temos: 76*38.000.000 = 28.880.000.00 bytes ou aproximadamente 2,7 GB.
  4. Se utilizarmos o tipo de dados TINYINT em 8 colunas teremos: 8×1 = 8 bytes. Devemos somar também 11 colunas do tipo SMALLINT. Ou seja, teremos (8×1) + (11*2) = 8 + 22 = 30 bytes. Este é o custo da linha para as 19 colunas que serão modificadas.
  5. Multiplicando 30 pela quantidade de linhas teremos 30*38.000.000 = 1.140.000.000 bytes ou aproximadamente 1 GB.

Se fizéssemos a troca de tipos, estaríamos economizando 1,7 GB, pois 2,7 – 1,0 = 1,7 GB. Notem que esta economia é de 63% do espaço ocupado pelas colunas numéricas e de 5,6% do tamanho total da tabela e, por conseqüência, do espaço em disco. Isto apenas para o tamanho ocupado pelos dados, pois teríamos um ganho adicional de espaço se considerarmos que muitas destas 19 colunas fazem partes de índices. Para o leitor ter idéia do que isso representa, faremos uma analogia.

Imagine se 5,6 % do espaço do seu monitor estivesse com um retângulo preto sem uso. Este espaço estaria reservado para você, porém ele nunca é utilizado porque o fabricante imaginou que um dia alguém descobriria um jeito de utilizar este espaço, coisa que nunca aconteceu na realidade. Se alguns designers já reclamam de um ou outro dead pixel em um monitor com resolução de 1280×800 imaginem a falta que 51.000 pixels não faria….

A redução de espaço proporciona outros efeitos colaterais benéficos. Além da redução dos índices, o banco de dados ocuparia menos memória para armazenar as páginas de dados e índices desta tabela. Os joins ficariam mais rápidos de serem executados devido ao tamanho reduzido das chaves primárias, o backup e a replicação seriam executados mais rápido, o processador gastaria menos ciclos de CPU para executar a instrução, etc. Contudo, mensurar estes tipos de ganhos é mais complexo do que simplesmente indicar o ganho de espaço em disco e, em muitas situações, a troca de tipos de dados pode se tornar muito complexa, especialmente com tipos que não armazenam números.

Um fator decisivo para a minha escolha da mudança de tipos nesta consultoria foi facilidade de implementação: em termos práticos não houve muitas dificuldades técnicas para trocar um tipo de dados INT para SMALLINT ou TINYINT. Em primeiro lugar, a conversão dos programas, stored procedures e outros objetos programáveis é automática, pois um valor SMALLINT ou TINYINT pôde ser armazenado em um INT sem problemas. Além disso, foi possível utilizar o comando ALTER TABLE para trocar o tipo de dados sem problemas, com exceção das chaves primárias. Em segundo lugar, valores numéricos, em geral, são indexados e manipulados mais rápido que outros tipos de dados, o que tornou esta manutenção mais rápida do que trocar um char(30) por um varchar(30), por exemplo. Em terceiro lugar, esta manutenção apresentou resultados imediatos, que puderam ser medidos através de diversas maneiras no SQL Server.

Porém isso não quer dizer que a tarefa vai ser sempre simples: há sempre a obrigação de testar o sistema após a troca dos tipos de dados e contar com um backup pronto para ser restaurado em caso de problemas. Além disso, as implicações desta troca de tipos podem afetar funcionalidades já agendadas para desenvolvimento que contavam com um tipo de dado e, na hora da implementação, se depararam com outro tipo de dados.

Agora eu termino este artigo perguntando: você, leitor, já sabe o quanto do seu banco de dados está sendo “desperdiçado” devido ao uso incorreto do tipo de dados de dados nas suas tabelas?

Um grande abraço,

Mensagem do anunciante:

Torne-se um Parceiro de Software Intel®. Filie-se ao Intel® Developer Zone. Intel®Developer Zone

Comente também

6 Comentários

Lucas Vieira Filho

claro que é sempre interessante economizar recursos, mas neste caso essa redução de espaço valeu a pena? imagino que o banco de 50GB passou para 48GB aproximadamente… esta mudança provocou uma melhora sensível na perfomance do banco?

    Mauro Pichiliani

    Lucas,

    Sim, esta prática valeu a pena por que foi aplicada também aos índices e outros tipos de colunas de outras tabelas. A princípio por parecer um ganho pequeno, porém esse é um passo importante para otimizar o sistema como um topo. Nesta coluna citei apenas um caso, porém já obtive diversas melhorias em situaçõe diferentes utilizando esta idéia.

    Mauro Pichiliani

Paulo Roberto Pereira Junior

Mauro, realmente estas mudanças fazem muita diferença. Em uma situação que trabalhei, onde o banco de dados tinha 35 GB, fazendo alteração dos tipos “decimal(x, 0)” para os tipos inteiros adequados e alguns campos char para varchar, o banco foi para 28 GB, além de ficar 60% mais rápido nas consultas mais críticas.

Daniel Schiavini

Bom, se utilizarmos LinqToEntities, o que normalmente acontece para os sites mais novos, precisaremos atualizar todas as referêmcias para os campos no programa também. As vezes vale a pena, mas para projetos mais grandes nem sempre.

Elias Rodrigues

“[…] e as 11 poderiam ser trocadas pelo tipo SMALLINT (4 bytes). […]”

Você quis dizer “2 bytes”.

    Mauro Pichiliani

    Isso Elias, o smallint são 2 bytes. Essa é uma pequena errata do artigo.

Qual a sua opinião?