Motivos para não armazenar arquivos no banco de dados

Infelizmente ainda é muito comum encontrar aplicações que usam o banco de dados para armazenar imagens ou arquivos binários em suas tabelas. Irei apresentar alguns motivos para que você não faça isso em seus próximos projetos e o que pode ser usado como alternativa.

Todos os testes foram realizados utilizando o PostgreSQL 14 em um container Docker em um MacBook Air M1 com 8gb de RAM

Estrutura do banco de dados usado para testes

Estrutura do banco de dados

Espaço utilizado por cada tabela (ambas com aproximadamente 400 registros)

Cada arquivo em um registro da tabela perfil possui aproximadamente 50MB. Disco ocupado pelas tabelas

Desempenho

Vamos comparar alguns operações básicas em ambas as tabelas.

Inserção sem arquivo Tempo para inserção de registro sem arquivo

Inserção com arquivo (50MB) Tempo para inserção de registro com arquivo

Consultando 200 registros da tabela perfil_sem_arquivo obtendo todas as colunas Tempo de consulta de 200 registros sem arquivo

Consultando 200 registros da tabela perfil sem incluir a coluna que armazena o binário Tempo de consulta de 200 registros com arquivo mas sem incluir coluna com o binário

Consultando 1 registro na tabela perfil incluindo a coluna com o arquivo Consultando um registro com a coluna que armazena o binário

Consultando 10 registros incluindo a coluna com o arquivo Consultando 10 registros com arquivo

Removendo 394 registros com arquivo Removendo 394 registros com arquivo

Removendo 393 registros sem arquivo Removendo 393 registros sem arquivo

Como podemos ver, o banco de dados performa muito melhor na tabela que não armazena um arquivo em seus registros.

Financeiro

Vamos usar o Google Cloud SQL e o Cloud Storage como exemplo para entender o custos envolvidos.

Em Julho de 2022 a instância mais barata para utilizar o Cloud SQL é a db-f1-micro em Iowa (us-central1). O custo mensal seria aproximadamente $7.67 (R$ 40,90), a capacidade máxima seria de apenas 3.062 GB, fora o custo do uso de rede, vCPU e memória (que como mostrei nos exemplos anteriores, ao armazenar arquivos no banco esses recursos são mais exigidos e portanto teríamos um custo maior).

Utilizando um serviço como o Cloud Storage o nosso custo cai para $ 0,02 a cada GB por mês + preço de uso de rede + $ 0,05 a cada 10.000 operações.

Com esse serviço, ainda temos a possibilidade de reduzir o valor pago utilizando classes de armazenamento mais baratas (quanto mais barato for, menor é a disponibilidade do arquivo). Dependendo do tipo da aplicação que estamos lidando, esse pode ser um recurso interessante para otimizar os custos com cloud.

Infraestrutura e CDN

Disponibilizar esses arquivos via http exige que consultas sejam realizadas no banco de dados (que no nosso exemplo anterior já consumiria quase 5 segundos da requisição) , que o servidor baixe os dados vindos do banco (gerando mais custos com consumo de rede e ocupando mais memória das instâncias do backend) e o cliente baixe esse arquivo novamente. Além de obviamente aumentar muito o tempo da nossa requisição, ainda nos leva a possíveis problemas com os load balancers que são usados pela infraestrutura da aplicação.

Ao utilizar serviços que são voltados para o armazenamento de arquivos, abrimos a possibilidade para a utilização de CDNs para o cacheamento em diferentes regiões do mundo, possibilitando um download com latência menor (super importante para o carregamento de imagens em sua aplicação).

Em resumo

Não vale a pena sacrificar a escalabilidade da sua aplicação nesse nível. Prefira armazenar apenas URLs dos arquivos em seu banco de dados e faça o uso de um bom serviço de storage. Eu particularmente gosto bastante da AWS S3 e Google Cloud Storage.

E você, já se deparou com algum problema apontado aqui em um dos seus projetos?

Aonde me encontrar

Alternativa caso você esteja iniciando um projeto 100% free

( a estratégia do banco de dados fiat uno )

Essa realidade de tanto dev usando seu banco para guardar arquivos pode ser por causa que as alternativas são serviços e não guias, softwares e outros tipos de ferramentas gratuitos.

Imagina começar um projeto, montando seu React, Node, MySQL e... Onde guardar os arquivos do usuário?

Agora sim vou te dar uma alternativa:

Não pense muito nisso

( você pode mudar mais tarde )

Simples, ao invés de fazer uma tabela perfil_sem_arquivo que segura a url da foto em uma string fotourl, você pode criar uma tabela foto_arquivo que vai segurar o arquivo exatamente como na tabela ERRADA inicial, a diferença é que (desde que você não dê join na tabela foto_arquivo) a velocidade será a mesma se seu arquivo estiver no banco de dados porém fora da sua tabela. (imagens mais a baixo)

Para recuperar a imagem, você pode construir uma rota apenas para a tabela do arquivo, buscando e retornando a imagem de uma vez, separadamente.

Essa estratégia é o equivalente de usar um fiat uno de frete. Geralmente você quer um frete potente, com bastante espaço por ser grande, segurança se possivel e uma equipe para te ajudar.

Porém, como você está começando agora, você só precisa levar uma mochila com roupas e seu PC Gamer até a kitnet no bairro ao lado da casa da sua mãe (sua antiga casa). É por isso que não é muito errado você guardar dados pesados no seu BD, afinal, o nome é banco de dados e não banco de penas. Acontece que hoje em dia, existem serviços tão melhores (e baratos) em distribuir arquivos estáticos que não compensa tentar subir uma startup fazendo isso manualmente.

A estratégia da Rota separada

fiatuno

Quando chegar no momento de contratar um serviço CDN para guardar suas imagens ou arquivos e você for segurar seus arquivos, você vai estar com seu banco já formatado para esse padrão. Será um trabalho de remover a rota (e o controller) antigo de buscar no seu próprio banco, remover a tabela de arquivos (pós transferência) e populando o campo fotourl já existente no seu projeto!

Até quando posso deixar os arquivos dentro do meu banco desta maneira separada?

fordmaverickgt

Até você cansar da performance ruim, mas antes, a performance ruim precisa começar a ser um problema e para isso acontecer, provavelmente sua aplicação já passou da fase de testes e se tornou um MVP ou já está sendo e você está com o investimento necessário para contratar um serviço sério.

um ponto importante é que nos testes do post, mesmo consultas que não envolviam o arquivo, tiverem um desempenho inferior em relação a tabela sem o arquivo.
Não entendi oq vc disse, se você consulta uma tabela com arquivo vai ser inferior mesmo, por isso que eu mostrei como separar o arquivo da tabela, pra solucionar **este** problema
Antigamente, projetei meu banco para armazenar apenas algumas colunas e não um arquivo em si, mas um JSON grande na última coluna de uma tabela, a mais usada/gravada/consultada. Hoje eu estou tendo que reprojetar o banco e usar cache porque o desempenho está sofrível. Não adianta, se for mal projetado, uma hora vai ter de ser refeito.
Realmente, minha solução é apenas para *começar* um projeto até poder solucionar o problema antes de se tornar um.

Ao utilizar um google cloud storage para armazenar os dados recomendo muito a utilização de links de acesso temporario e não a navegação da imagem por toda sua infra de servidor.

Gerando um link atraves das sdks do google voce vai economizar em armazenamento e trafego de rede, visto que o acesso via api e o acesso via link é o mesmo, retirando seu gasto de deslocamento de imagem pelo servidor.

Isso fecha grande maioria dos usos para compartilhamento e uso de imagem da maioria das aplicações de baixa e media complexidade.

Ref: https://cloud.google.com/storage/docs/samples/storage-generate-signed-url-v4?hl=pt-br

Realmente, é gritante a diferença entre armazenar arquivos e não o fazer. Excelente dica!

Muito bom, o conteúdo.

Brabo demais, não tinha ideia que a diferença de eficiencia seria tão grande.

Ótimo artigo, muito obrigado!

Parabéns pelo post. Compartilho da ideia que não armazenar binários no banco de dados é a melhor opção.Lembrando que é necessário um controle deste storage para que outros arquivos não possam ser vistos sem a devida autenticação. Parabéns!!!

Bom dia Andre, esse era um assunto que eu estava com dúvidas. Na verdade ainda estou, me ajuda a esclarecer? Você salvaria as imagens em uma CDN e o caminho no banco, seria isso? Desculpe se não entendi direito.

Bom dia Rodrigo, exatamente como você falou, o ideal é salvar as imagens em uma CDN como a S3 (AWS), B2 (Backblaze), R2 (Cloudflare) ou alguma outra solução de object storage. Após isso é só salvar a URL no banco de dados e utilizar quando precisar.

Tópico muito importante, e bem interessante que você também adicionou a parte dos CDNs, hoje temos acesso fácil a essa funcionalidade incrível que é disponibilizar os dados da nossa aplicação para usuários mais próximos através da localização dos datacenters.

Porque estão dando downvote nesse post?

Inclusive, seria legal que o pessoal que tem uma opinião diferente enriquecesse ainda mais o conteúdo comentando aqui.

Bom dia, Em relação a segurança dos dados, usando uma infraestrutura dedicada não pode aplicar camada para apenas pessoas autorizadas baixar as imagens. Em vez de utilizar um url direto para imagem não seria viavel ter uma API sicronizada com back-end onde troca informações de segurança para permitir acesso ao dados?

Bom dia habbora Olha, eu já ví algo similar ao que você está citando com a Cloudflare Workers, ao acessar certo domínio você passa por uma espécie de middleware diretamente pela cloudflare, eles tem até um exemplo de como fazer uma camada de segurança nas docs dos Workers.

Post muito esclarecedor! Parabéns.

Ótimo artigo, eu tive quase que os mesmos problemas com um projeto pessoal que é em base consumido por imagens, na época eu pensava que seria menos custoso tanto para a minha VPS quanto para o usuário se eu salvasse as imagens em formato de base64 e depois eu convertesse para jpg no front.

E sim, eu estava redondamente enganado, não tem nem comparações utilizar um serviço de Object Storage e por cima botar a Cloudflare como cache e ter uma maior performance, tanto a sua página vai ser renderizada mais rápido para o usuário que vai indo baixando os contaúdos aos poucos, quanto vai ser gerada mais rápida também (caso esteja utilizando uma framework para SSG como o NextJS).

Incrivelmente eu tbm já pensei na mesma coisa, de guardar o texto base64 e converter pra imagem, provavelmente todo iniciante pensa nisso.

Muito top André, essa era uma dúvida que me perseguia desde que aprendi o Back-end. Ficou muito claro o por que não armazenar as imagens diretamente no banco de dados!

Eu lembro que, ao invés de guardar o arquivo direto, eu guardava o resultado do base64 no arquivo. Se isso era uma boa prática ou não, eu não sei, só sei que parei com isso por que dava muito erro.

Boa Andre, excelente post!

Pra exemplificar, na última API que construi em Node, deixei as imagens na pasta /public e no app.js onde estavam as rotas, coloquei um endpoint da seguinte forma:

app.use('/images', express.static('public'));

No banco, havia um campo imageUrl que tinha uma string tipo essa: "/images/heineken_600ml.jpg"

Dessa forma, no front, ficou bem performático e meu banco no back bem enxuto. Mas essa solução só me veio depois de tentar guardar os arquivos dentro da chave 😂

Bacana, Otto. E como você lida com o armazenamento de imagens em diferentes servidores ou não perder os arquivos caso a instância caia?
Vou ser bem sincero contigo Andre, não cheguei a escalar uma aplicação ainda pra mais de um servidor. Vou te dever essa resposta por ora! Mas, estou sempre disposto a aprender novas soluções! Se tiver um bizu, estou anotando!
Serviços para armazenamento de arquivos estáticos pode ser uma boa solução. Fica a dica de alguns que já usei: - Google Cloud Storage; - AWS S3; - Azure blob;
Nesse caso que você citou Otto, é necessário salientar que, o uso de um docker, ou outra solução do mesmo tipo fica debilitada, uma vez que, a cada nova execução, um novo container é criado(e caso sejam múltiplos contêineres balanceados entre si, cada um possui sua própria pasta public, com arquivos diferentes, ao mesmo tempo, o que não seria legal), e assim seriam perdidas todas as imagens, além disso, tem que pensar bem na hora de disponibilizar determinados conteúdos na pasta public, uma vez que, sem a devida autenticação podem ocorrer os casos em que usuários mal intencionados vasculham essas rotas buscando imagens de outros usuários, nesse caso, penso que uma segunda autenticação, ou verificação de propriedade de um usuário sobre um determinado conteúdo antes de ser disponibilizado seria uma boa. Vale a pena testar isso depois, vou ver se faço e publico o resultado por aqui!
Bem pensado Tulio. Eu não cheguei a pensar nisso pois subi o back num container do docker e como o uso era local, não me preocupei com esse detalhe. Mas muito bem colocado, acredito que uma autenticação através de um middleware caia muito bem nesse caso.

Conteúdo excelente! Obrigado por compartilhar esse conhecimento!

Realmente o armazenamento de arquivos na base de dados não é tão correto de se fazer, pela performace Armazenar os arquivos dos usuarios separado da aplicação pode gerar uma certa segurança, pois os arquivos estão separados da aplicação, e só conseguem ser acessados apartir da aplicação Você concentra o foco da segurança mais ali, e já tem a performace de guardar o arquivo separado do banco

Muito bom o post, não tinha feito testes nesse sentido ainda, então agora, depois de ver os dados, ficou tudo mais claro. Eu sou um iniciante e é interessante ver como os frameworks com suas ORMs já nos guiam nesse sentido, recentemente estava em um projeto pessoal e surgiu a necessidade de lidar com upload de arquivos. A primeira coisa que pensei foi justamente armazenar os arquivos no banco, mas consultando a documentação do Django notei que, no que diz respeito a gerenciamento de arquivos, ele indica usar justamente um campo no banco que armazena o caminho para um arquivo em vez de armazenar o arquivo de fato. Quando vi isso foi que comecei a pensar em como seria custoso armazenar os arquivos no banco e implementei o campo com os caminhos para os arquivos. Se eu não utilizasse um framework provavelmente faria uma burrada. Estudar a documentação dos frameworks pode nos fazer aprender muito.

Banco de Dados foi feito para armazenar dados (Id, Nome, E-mail, ...) e não arquivos. O melhor Banco de Dados que você pode ter para manuzear arquivos é o próprio sistema operacional. Ele foi construído para isso 😊!

Você poderia usar base64 para encodar o arquivo em formato texto antes de passar para o banco de dados, e desencodar quando utilizar. Ou essa pratica não rola?

o encode vai gerar 4 bytes para cada set de 3 bytes e adicionado a isso tem o padding + header... então pra armazenar vai sair meio "caro"
Rola, mas fazer o encode e o decode de arquivos de 50mb pode causar outros problemas de performance no lado do backend. Além de que acredito que não teriamos grandes melhorias de performance ao armazenar uma quantidade gigante de texto no banco. Talvez essa técnica funcione bem para pequenos arquivos. Vale o experimento para depois trazer os resultados aqui.

Sem contar a performance em backups e replicações (em caso de escala horizontal). Concordo 100%

Concordo com a sugestão, embora não tenha números para validar minha escolha. Desenvolvemos recentemente uma aplicação web para Ministério do Meio Ambiente, que tem no arquivamento de arquivos sua principal vocação. Optamos por fazer o arquivamento do binário em tabelas Postgres, durante o desenvolvimento, por uma questão de comodidade. Para evitar parte dos problemas de perfomance, mencionados no artigo, os arquivos são armazenados em tabela à parte da tabela de dados, sem uso de chave estrangeira entre elas.

Em produção, optamos pelo arquivamento em um volume de dados na rede interna do órgão. O chaveamento das classes responsáveis por definir a estratégia de arquivamento é feito por meio de variáveis de ambiente, que definem qual a classe concreta que será instanciada em tempo de execução, bem como a real localização do volume de dados que conterá os arquivos.

Excelente conteúdo!

Muito bom seu post, tenho feito um projeto pessoal e vou precisar guardar fotos de perfil também e isso ja da uma boa noção do que preciso fazer a respeito do banco de dados.

Pra citar um exemplo de cloud: Você pode ter acesso ao *Azure* por meio do Github Student Pack. Lá você pode encontrar o Azure Blob Storage e já dá pra brincar um pouco. Ou pode partir pro Cloudinary, que também tem um plano "free" que já dá pra armazenar fotos e até vídeos.
Valeu pela dica, sou desenvolvedor dotnet e estou bem habituado com o ecossistema da azure, vou seguir as dicas que me passou.