Executando verificação de segurança...
1

Otimizador de Imagens Dinâmico com Rust & AWS: Redução de Custos e Aumento de Performance

Github: https://github.com/SMCodesP/image-rust

O que o projeto oferece:
Redimensionamento e Conversão Dinâmica → Processamento sob demanda com suporte a múltiplos formatos, como WebP, AVIF e outros, e redimensionamente de imagem para qualquer resolução.

Arquitetura Escalável → Integração com AWS CloudFront & Functions, Lambda e S3, garantindo distribuição global e baixa latência

Otimização de Custos → Processamento serverless, reduzindo custos operacionais e melhorando a performance.

Tecnologias de PontaRust para máxima eficiência e segurança, junto com uma infraestrutura robusta na AWS.


Estou animado em compartilhar meu novo projeto um otimizador de imagem dinâmico. Essa solução foi desenvolvida com foco em alta performance e eficiência, utilizando o poder do Rust aliado aos serviços da AWS para redimensionamento e conversão de imagens.

Sou um programador, principalmente frontend, utilizo muito o componente Image do next acho incrivel, porém junto com a facilidade dessa ferramenta vem o custo, um custo relativamente bem alto, pelo menos quando se trata de aplicações "em que o foco são as imagens, onde facilmente teria 12.000 imagens.

O image legacy da vercel, tem uma cobrança antiga na qual custa 5 dólares para cada 1.000 imagens, 60 dólares no mínimo mais de 300 reais em otimizações de imagens, a eficiencia de conversão não era muito boa, e era bem cara quando escalar isso a nível projetos cheios de imagens.

Há 2 anos atrás estava utilizando esse componente em um projeto onde tinha vários produtos e todos esses produtos tinham um carrossel com imagens, ou seja logo na pagina inicial acredito que carregava cerca de 20 imagens, após isso na pesquisa de produtos listava mais diversos, isso acarretou muitas otimizações de imagens, e no plano Pro da Vercel de 5.000 imagens, facilmente em ambiente de desenvolvimento e testes atingi o limite de otimizações.

Enfrentando esse problema, testei diversas formas, fazer uma pré-otimização de imagens, gerar tamanhos pré-definidos; porém, simplesmente não gostei desse método, embora seja uma solução aceitável, já que elimina todos os problema de performance de um otimizador dinâmico de imagens.

Utilizei por muito tempo image-optimization da AWS Samples, um projeto simples e funcional, me fez criar toda uma estrutura de otimização de imagens facilmente, um projeto de exemplo que pode ser usado facilmente em produção, usando simplesmente Lambda, Cloudfront e S3.

Porém temos um problema com o image-optimization, ele tem um problema na base dele que é o cold start, muitas empresas que adotaram uma arquitetura serverless (Lambda), estão com o objetivo de otimizar essa estrutura tanto para uma eficiencia maior quanto para uma performance melhor, no serverless baixa performance é cobrada.

Durante esse tempo observei empresas como a Vercel otimizando diversos componentes da infraestrutura serverless deles como o proprio Image, as rotas, o lançamento do fluid compute, criando diversas métodos para tentar diminuir cold start e tempo de processamento usando técnicas como gerar byte codes já otimizados e webassembly na Rede Edge por exemplo.

Como o image-optimization é um projeto apenas de exemplo da AWS temos algumas limitações diretamente na base, uma delas é o NodeJS, que causa um cold start maior, e em alguns casos poderíamos ter uma melhora na velocidade de conversão e redimensionamento das imagens, o projeto da AWS utiliza o sharp para manipular imagens, uma boa biblioteca, muito conceituada, e boa para esse propósito.

Com isso decidi testar alguns conceitos, então fiz esse projeto, criei um sistema para manipular as imagens usando Rust, continuei utilizando a mesma estrutura do image-optimization, utilizei a mesmo Cloudfront Function que o projeto da AWS utiliza, e o único detalhe que troquei foi a linguagem para uma linguagem compilada, e que vai nos gerar um binário no final.

Considerei utilizar também golang, mais parecido com a sintaxe de um javascript, e que também teria um resultado de um binário, porém quis me aventurar no Rust, nunca utilizei Rust, então pesquisei e testes diversas possibilidade.

O primeiro desafio foi redimensionar as imagens de maneira eficiente, pois os resultados não estavam me agradando, no começo, achei que seria aquilo mesmo, porém ao condensar toda a request de transformações o meu script estava mais lento que o da AWS, no começo estava utilizando a biblioteca image do rust para manipular tudo sobre imagem, então comecei a testar outras ferramentas para redimensionamento encontrei uma ideal chamada fast_image_resize, onde me alivo muito o tempo geral da requisição e só aí já estava comparável e melhor que a ferramenta da AWS.

O segundo desafio foi o conversor para Aviff, um tipo de arquivo lento para processar porém muito pequeno, e tive bons resultados 4x mais rápido que a ferramenta da AWS.

Último problema, depois de todos esses testes notei algumas descobri o que no fundo já imaginava, o maior gargalo dessas otimizações não era o processamento de imagens, considerando o formato webp o ganho de eficiencia não foi tão grande ao ponto de necessitar trocar todos os meus atuais projetos para essa otimização (mas troquei), notei que o maior gargalo não foi a linguagem, mas sim a internet, o tempo para abrir conexão com o Cloudfront, ele fazer o rewrite da url usando cloudfront function, ele procurar em cache não achar, depois no S3 de cache não achar, e depois chamar a lambda, esperar a lambda criar o cliente S3 baixar o arquivo original do S3 e o cliente final baixar a imagem otimizada esse processo é onde fica a maior parte do gargalo, observe abaixo que a maior parte do gargalo está em baixar a imagem

Tempo para baixar imagem original do S3 (acredito que isso poderia melhorar, talvez utilizando fast download do s3, ou baixando diretamente de um cloudfront): 80 ms

Tempo para carregar imagem em memória, decodificar imagem em memória (acredito que isso poderia melhorar): 35 ms

Tempo para redimensionar imagem: 14 ms

Tempo para codificar imagem: 6 ms


📊 Benchmark Comparativo :

Fiz um comparativo com a minha ferramenta de otimização, em comparação com a ferrmenta que a AWS Samples disponibiliza o image-optimization para os formatos (WEBP e AVIF).

Todos testes foram realizados utilizando imagens de 1000-1200 pixels de largura, ou seja 200 imagens de mesmo tamanho, para garantir comparabilidade justa.


Formato WEBP

Benchmark formato webp

Note que a maior diferença é a consistência não tive muito disparo de tempo máximo na versão de rust, provavelmente os tempos máximos da versão da AWS foi onde precisou levantar novas instâncias que não estavam aquecidas.

Formato AVIF

Benchmark formato avif

Note que aqui ocorre muito mais essa variação do tempo máximo, não possuí consistência, e a média baixou consideravelmente

💰 Otimização de Custos com ARM (Graviton)

Outra vantagem muito relevante da minha implementação é que, além dos ganhos de performance, o Rust permite a compilação nativa para arquiteturas ARM, que é justamente a recomendação atual da AWS com os processadores Graviton.

A AWS oferece um incentivo claro para essa arquitetura: 25% de custo menor na execução em comparação com instâncias baseadas em x86.

Nos testes, ambas as soluções foram configuradas com 3008 MB de memória, o que permite um bom balanceamento entre custo e performance. Ao calcular o custo de 1.000.000 de requisições, considerando tempo de execução e preço por GB/s, o resultado foi o seguinte:

Implementação Tempo Médio Custo (1M de requisições)

🦀 Minha (Rust + ARM) 325 ms US$ 12,73

AWS Image-Optimization 414 ms US$ 20,28

A diferença é significativa. E esse impacto se torna ainda mais relevante em cenários de escalabilidade, onde milhões (ou dezenas de milhões) de requisições são processadas. Quanto maior a escala, maior a economia — tanto em tempo quanto em dinheiro.

Atualização Importante sobre Custos na AWS Lambda:
Recentemente, a AWS anunciou uma mudança muito relevante na cobrança do AWS Lambda, descrita no artigo AWS Lambda standardizes billing for INIT Phase.

Na prática, isso significa que agora a AWS também cobra o tempo de execução durante a fase de INIT, que é justamente o momento do cold start — quando a função Lambda precisa inicializar o ambiente antes de processar a requisição, quando fiz as estimativas e cálculos de custo não havia essa cobrança, o que torna a minha ferramenta ainda mais vantajosa.

Essa mudança impacta diretamente aplicações que não são nativas da arquitetura serverless ou que possuem cold starts mais elevados — como funções baseadas em linguagens interpretadas (Node.js, Python, etc.) ou com grandes dependências.

💡 No meu projeto de otimização de imagens, essa mudança reforça ainda mais a vantagem de usar Rust, que gera um binário extremamente leve e de inicialização muito rápida, reduzindo drasticamente o impacto do cold start — tanto em performance quanto em custo.

Comparado à solução image-optimization da própria AWS (baseada em Node.js + Sharp), minha implementação apresenta tempos de inicialização muito menores e, consequentemente, um custo mais otimizado no modelo atual da AWS.

Próximos desafios:

Otimização ao decodificar a imagem me memória acredito que o uso da biblioteca Image está limitando um pouco disso.

Otimizar baixar no S3 (tentei via S3 Transfer Accelerate, porém não obtive tempo de resposta menores).

Adicionar uma configuração infraestrutura via codigo para facilitar o deploy.

Adicionar suporte a múltiplos S3 em um único deploy.

Github: https://github.com/SMCodesP/image-rust

Carregando publicação patrocinada...
1