Blog

#3 Como aperfeiçoar a velocidade de seu site: banco de dados

Como aperfeiçoar a velocidade de seu site

Acesse os tópicos

Nos posts anteriores, falei sobre como melhorar a performance do seu site diminuindo a latência, otimizando o front-end e “esmerilhando” o back-end. Hoje, vamos falar de algo que muita gente usa, mas pouca gente entende de verdade: banco de dados.

Tenho certeza que existem coisas básicas sobre performance de consultas no banco de dados que você aprendeu em algum lugar, geralmente em um curso, que deve usar extensivamente, acreditando que todos os seus problemas estão resolvidos. Eu gostaria de estar errado, mas como professor de faculdade vejo uma situação muito precária quando o assunto é a performance do banco de dados das aplicações de meus alunos.

Vou tentar não “chover no molhado” aqui e não ficar falando para sempre fechar a conexão depois de abrir. Vou tentar seguir pelo caminho contraintuitivo, que costuma gerar os melhores resultados. O fato é que não existe uma “bala de prata”, uma solução para todos os problemas do seu banco de dados. Dependendo do fabricante que você escolheu, do tipo de banco que optou e até mesmo do uso dos dados que sua aplicação faz, muitas outras dicas seriam benéficas.

Como alguém que já criou e gerenciou bancos de dados em sistemas usados por meio milhão de usuários, tenho várias dicas para compartilhar, que, embora não cubram todos os casos, podem passar ideias bem interessantes.

Dica 1: Escolha o banco certo

Como a ideia é chacoalhar os seus neurônios com dicas contraintuitivas, resolvi começar pela pior delas!

Você conhece a história: Joãozinho faz curso de programação e aprende o banco de dados X. Pro resto da vida Joãozinho cria seus projetos usando o banco de dados X. Mesmo quando tudo está dando errado, aponta a culpa para o banco.

O mundo é muito maior do que um fabricante de banco de dados ou mesmo do que os bancos relacionais que a imensa maioria está acostumado. O Joãozinho tem que abrir os olhos para o mercado, se quiser progredir como profissional!

Então, você precisa armazenar dados gigantes não relacionados ou que podem ser aninhados? Porque está usando SQL Server ao invés de MongoDB?
Dados efêmeros sendo criados e destruídos o tempo todo no seu MySQL, com performance sofrível? Isso não deveria ser tarefa pro Redis?

O seu site precisa guardar poucos dados e você criou um banco Oracle para isso, pois foi o que viu na faculdade? Já ouviu falar em XML? Ah, não gosta de arquivos, mas e SQLite?

Grafos e relacionamentos extremamente complicados em um PostgreSQL, quando temos o Neo4J especializado nisso? Ou então séries de dados baseados em tempo, especialidade do InfluxDB, ou então dados em tempo-real, coisa facílima de fazer com RethinkDB, ou então.. .acho que você entendeu o que eu quis dizer!
Cada banco de dados existe por uma razão de existir, e eu mesmo não sou um especialista no assunto para ficar dizendo o que cada um deve usar. Cada sistema tem as suas características e é papel do analista ou arquiteto do sistema definir a opção ideal. Como a maioria dos projetos tem só programadores, é você que está lendo esse post que vai ter de aprender a escolher o banco certo. Se ferrou! 🙂

O que quero colocar na sua cabeça é que você até pode tentar usar os bancos relacionais mais comuns como SQL Server e MySQL para todos os seus projetos. Mas que, dificilmente, eles serão as escolhas mais adequadas para todas as situações que vemos na web de hoje e que os programadores teimam em tentar encaixá-los. É uma dica um tanto radical? É. Mas, se nenhuma das demais dicas desse post ajudar, pode ser um ótimo sinal de que você chegou no limite do modelo relacional para o seu problema e deveria procurar outras alternativas, geralmente no mundo NoSQL.

Dica 2: Crie as tabelas do jeito certo

Esse problema é tão comum, mas tão comum, que só não coloquei como primeira dica para causar mais impacto com a que deixei naquela posição.

Os bancos de dados modernos possuem dezenas de tipos de dados, várias constraints e vários índices. Mas, os programadores teimam em mudar seus hábitos arcaicos e preguiçosos de colocar todo número como integer, todo texto como varchar(max), uma chave primária autoincremental e no máximo umas chaves estrangeiras para fazer os relacionamentos básicos.

Isso funciona? Sim. Quando você está desenvolvendo. Ou quando seu sistema vai ser tão pouco usado que não faz diferença alguma se usou um integer ou um smallint.

Deixarei para falar de índices e relacionamentos mais pra frente, vou me focar nessa dica nos tipos de dados (colunas) do banco.

Assim como mencionei no post anterior quando entrei na dica sobre tipos de variáveis, os tipos das suas colunas impactam em a) no tamanho total do seu banco de dados e b) na velocidade das suas operações de leitura e escrita. Vou falar melhor de cada um, calma!

Cada tipo de dado ocupa uma quantidade de bytes no(s) arquivo(s) do banco, e quanto menor o tamanho do banco, melhor por diversas razões, mas a mais óbvia é custo de disco, embora não seja o maior problema atualmente. Usando um integer em uma coluna, cada registro da tabela em questão consumirá 4 bytes e permitirá números entre -2.5B e +2.5B, aproximadamente. Agora, se você optar por um smallint essa mesma coluna vai permitir números entre -32k e +32k, ocupando apenas 2 bytes e geralmente oferecendo números suficientes. Tinyint vai mais longe ainda, consumindo apenas 1 byte e dando 256 números possíveis. E para chutar o balde, alguns bancos ainda tem o bit (ou boolean) que fornece apenas dois números/opções e consome apenas 1 bit de espaço em disco por registro!

O mesmo vale para as colunas com ponto flutuante, que variam de banco para banco, mas que, geralmente, quanto maior a precisão (número de casas depois da vírgula), mais espaço ocupam no banco.

Com colunas textuais acontece o mesmo, existem diferentes tipos de dados, sendo que os mais comuns são char e varchar e, em ambos, devemos especificar o limite de caracteres permitidos, consumindo em torno de 2 bytes por caractere, dependendo do collation/idioma do seu banco (mais um “overhead-zinho” de 1 bytes para cada 255 caracteres). Qual a diferença então?

Um char(11) sempre ocupará 11 ‘espaços’ em cada registro da tabela, independente se você enviou uma palavra com menos caracteres. Já um varchar(11) ocupa sempre o necessário para a palavra que for inserida, até o limite de 11 caracteres. Varchar é sempre melhor, então? Definitivamente não. Este tamanho ‘variável’ (que é o ‘var’ no nome) cobra seu preço nas operações realizadas no banco, uma vez que o tamanho fixo do char permite cálculos mais rápidos para alocar espaço em disco e retornar dados. Ou seja, se a sua coluna precisa de tamanho variável, use varchar, mas se a string que você vai salvar sempre possui o mesmo tamanho, use char.

Varchar(max), quando disponível, é mais nocivo ainda, pois gera overflows de dados, mais trabalho pro banco e não permite que o campo seja indexável, sempre gerando scans no banco de dados quando a dita coluna é incluída em um select. Se não entendeu tudo o que eu disse, apenas não use. 🙂

Também temos os tipos de datas que vão de date, para time, para datetime e outras variações, dependendo do banco. E temos tipos de dados geométricos, tipos de dados para geolocalização, para guardar arrays de bytes (BLOBs, arghhhh) e muitos outros que você deveria dar uma estudada a fundo focado na tecnologia de banco que irá utilizar. É óbvio que ninguém acerta na modelagem do banco logo de cara, mas planejar um pouco mais antes de sair criando tabelas pode ser a chave para ter menos dor de cabeça no futuro, quando tudo estiver em produção e downtimes tiverem de ser evitados ao máximo.

Dica 3: Mantenha seus amigos por perto, e seu banco de dados mais perto ainda!

Acredito que deva ser um tanto óbvio falar que o banco de dados deve estar o mais perto possível da sua aplicação, mas não custa repetir. O que geralmente não é tão óbvio é o estrago que um banco de dados distante pode causar na performance e que não importa que tudo mais esteja otimizado, a latência das conexões com o banco serão o gargalo da sua aplicação.

Latência? Onde ouvi falar dela antes? Ah sim, no primeiro post dessa série. Corre lá se ainda não leu! 😀
Se o mundo fosse um lugar perfeito, o banco de dados ficaria no mesmo servidor da aplicação, para ter latência próxima de zero. Como isso, na maioria das vezes, não é possível ou não é recomendável, por questões de segurança/escala/produtividade/coloque-seu-motivo-aqui, temos de manter o banco na mesma LAN, ele tem de ser acessível pela rede local, jamais pela Internet.

Independente das suas preocupações com segurança, que fugiriam do escopo desse post, a maioria das bibliotecas de conexão com bancos de dados, especialmente os relacionais, não foram projetadas para uso através da Internet, sendo um dos principais motivos pelos quais plataformas móveis não se conectam diretamente a SGBDs.

Resumindo, não basta ter toda a sua infraestrutura no mesmo continente ou país, como algumas empresas vendem por aí, o seu banco de dados PRECISA estar na mesma rede (quiçá mesmo servidor) que a sua aplicação. Ponto.

Dica 4: Só pegue o que vai consumir

Lembra quando você era criança (supondo que nenhum menino prodígio esteja lendo esse post) e sua mãe dizia: “Só põe no prato o que vai comer!”, o que nas entrelinhas queria dizer “seu couro vai ficar ardendo se deixar comida no prato”? Pois é, em banco de dados vale a mesma dica: só pegue o que vai consumir.
Como assim? Calma, eu explico!

Toda vez que você for no banco de dados para fazer uma consulta, pegue apenas os dados que vá consumir. Cada consulta que você faz no banco de dados gera um ‘download’, caso ainda não tenha parado para refletir. Quanto mais dados você trouxer, maior será esse ‘download’ e se você não for usar esses dados na aplicação, você apenas consumiu mais tráfego e principalmente, mais tempo do usuário que está no seu sistema/site!

Geralmente as pessoas simplificam essa dica dizendo para jamais usar “SELECT * ” nas suas consultas, mas vai além disso. A ideia é jamais pegar mais dados do que realmente necessita.

Dica 5: Só observe

Depois que você colocou as quatro dicas anteriores em prática, é hora de parar um pouco de otimizar e observar.

É muito comum acontecer excesso de engenharia (over-engineering) ou otimizações prematuras quando o assunto é banco de dados. Então pare de mexer no seu banco por um momento e passe a analisar seu comportamento em produção.

Periodicamente use os relatórios fornecidos pelo próprio banco de dados para saber quais tabelas estão sendo mais acessadas, quais consultas estão sendo mais utilizadas e quais estão lentas, qual tabela está crescendo mais rápido que as demais, etc. Com essas informações você poderá traçar um plano de ação do que deve ser otimizado de facto.

Outra dica nesta linha de ‘just watch’ é, uma vez identificadas as consultas que mais são utilizados/mais consomem o banco, analisar os execution plans delas, o que pode ser feito geralmente através de ferramentas visuais de gerenciamento do banco de dados, como o SQL Server Management Studio. No execution plan você conseguirá ver qual parte da consulta é o gargalo, qual parte pode ser otimizada com um índice e por aí vai.

Só depois que observar o seu banco “em ação” por um tempo é que você deve partir para as próximas dicas.

Dica 6: Tenha recursos suficientes

Uma vez que tenha observado o seu banco de dados funcionando por algum tempo, especialmente nos horários de pico, você conseguirá saber se você possui recursos suficientes. Eu sei, essa não é a “fórmula mágica” para a escala do banco de dados que você esperava, mas sinto informar que, se essa fórmula existe, eu nunca encontrei.

Você precisa basicamente verificar quatro recursos básicos: CPU, memória RAM, espaço em disco e IO de disco. Geralmente o gargalo dos BDs são memória RAM e IO de disco, então fique atento a eles, e atente às limitações da versão de banco que estiver usando (máximo 1GB RAM para SQL Express, por exemplo), restrições do seu provedor de hospedagem (se for o caso) e por aí vai.

Garanta que, no mínimo, seu banco de dados tenha uma “folga” de 20% de teto de uso dos recursos de hardware e aumente essa folga em eventos sazonais que afetem seu negócio, como o Natal para donos de ecommerce, por exemplo.

Dica 7: Desnormalize suas tabelas (!!!)

Calma, não atire suas pedras em mim antes de ler esta dica por completo! Eu sei, todos aprendemos na faculdade que devemos manter nossos bancos sob as Formas Normais, 100% normalizado, bonitinho, como manda o figurino.

Mas…

Assim como tudo na vida, existem exceções. Observando o seu banco em produção durante algum tempo (dica 5) e garantindo que há recursos suficientes para ele operar com máxima eficiência (dica 6) você saberá se o modelo ER que construiu foi o correto e, dependendo do seu negócio, uma “desnormalização” de algumas tabelas pode vir a calhar.

Como assim?

Às vezes, um ER 100% normalizado não é performático. Na verdade, na maioria das vezes ele não é. As Formas Normais que aprendemos na faculdade, não são voltadas para performance, são voltadas para garantir partes do acrônimo ACID: atomicidade, consistência, integridade e durabilidade. Isso não é necessariamente ruim, apenas que pode não ser o que você deseja em alguns casos.

Algumas vezes, uma chave natural é melhor que constraints. Algumas vezes, repetir alguns dados pequenos e simples, mas que são muito requisitados, é melhor do que criar relacionamentos com chaves estrangeiras que te levarão a fazer JOINs toda hora (na verdade essa dica poderia se chamar “não use JOINs demais”). Algumas vezes, usar o bom senso é muito melhor do que usar os livros didáticos. Pense nisso.

Desnormalizando algumas tabelas muito requisitadas geralmente reduzem a complexidade de queries que temos de executar o tempo todo, e queries menos complexas geralmente nos levam a execution plans melhores. Óbvio que você terá de pesar os prós e contras, mas esse é o seu trabalho, não é mesmo? Se modelar bancos de dados eficientes e eficazes fosse uma ciência exata, não precisaríamos de pessoas trabalhando nestas posições e você não estaria lendo esse artigo, não é mesmo?

Dica 8: Crie índices para as consultas mais comuns

Novamente, observando o seu banco de dados em funcionamento e, principalmente, as consultas mais comuns, é fácil notar onde deveriam existir índices. Um parênteses: note que não é a primeira vez que cito ‘observar seu banco de dados’ nas últimas dicas. Você deve fazer isso constantemente se quiser manter seu banco performando bem.

É impressionante como muitos, mas muitos sistemas, não possuem índice algum. Em alguns SGBDs como o SQL Server, um índice clusterizado é automaticamente criado quando você define a chave primária de sua tabela, mas isso é uma exceção. Não é incomum ver bancos sofrendo para fazer consultas básicas pois quem modelou o banco não conhecia como criar índices ou se esqueceu. Isso inclusive em sistemas comerciais famosos que não vou mencionar aqui.
Os mais incautos podem pensar: “se índice é algo bom, vou criar em todas colunas!”. Tudo na vida tem seu preço, e índices não são exceção. A cada índice criado, você aumenta o espaço em disco alocado para o seu banco de dados e uma vez que um índice é uma constraint (restrição), ele será verificado em diferentes situações de escrita, o que pode causar redução da performance nessas situações.

Algumas dicas gerais (não gosto muito de dar dicas gerais, mas vamos lá…) incluem:

  • criar índices nas chaves estrangeiras que você usa para ordenação ou em JOINs;
  • criar índices compostos para consultas que usem ORDER BYs compostos ou WHEREs compostos;
  • criar views indexadas para consultas muito complexas (isso consome muito espaço em disco e diminui a velocidade dos INSERTs, mas é insanamente veloz).

Dica 9: SSD não resolve o seu problema, estudar sim!

Velocidade na computação e uso abusivo dos recursos são uma eterna corrida de gato e rato. Minha mãe dizia: “quando a cabeça não pensa, o corpo paga” ou algo parecido com isso. Na empresa a frase poderia ser “quando o programador não pensa, a empresa paga”. Citei o SSD no título da dica mas isso vale para qualquer tecnologia que você pense que vai resolver todos os seus problemas de banco de dados: SSD, RAID, SCSI, SAS e até mesmo bancos in-memory. Esqueça. Só quem pode resolver os problemas do seu banco de dados é você.

Claro que ter um hardware adequado, como mencionei antes, nesse mesmo artigo, ajuda e muito. Mas antes de sair culpando o seu chefe que não quer “dar” um SSD pra pôr no servidor de banco, pergunte a si mesmo se você não está tentando apenas se livrar da responsabilidade de otimizar o seu banco e o seu sistema. Se você não tem os conhecimentos necessários, não há problema algum, vá atrás de livros, tutoriais, vídeos, etc e aprenda.

Não quero encerrar este post com um tom de “gerente tirano que é contra comprar hardware”, mas eu realmente acho que boa parte da “graça” de ser um programador é justamente resolver os desafios diários que a carreira com software nos proporciona. Se você tirar da equação a constante análise e otimização dos sistemas que desenvolve, em prol de gastar cada vez mais com hardware, será que estará fazendo um bom trabalho?

Pense nisso e sucesso! 😉

Confira os outros posts da série:

Compartilhe esse conteúdo

Termos de Serviço  Política de Uso  Política de Privacidade