- A Unkey, que oferece um serviço de autenticação de API, migrou de uma arquitetura serverless baseada em Cloudflare Workers para servidores com estado baseados em Go, resolvendo problemas de desempenho e de complexidade arquitetural
- A nova estrutura trouxe respostas 6 vezes mais rápidas e eliminou workarounds complexos de cache e a sobrecarga dos pipelines de dados
- No ambiente serverless, como não há garantia de memória persistente entre chamadas de função, toda leitura de cache exigia uma requisição de rede, gerando latência de cache acima de 30 ms no p99
- Ao migrar de um sistema distribuído para uma arquitetura de aplicação mais simples, a empresa passou a permitir self-hosting, garantiu independência de plataforma e melhorou muito a experiência dos desenvolvedores
- Serverless continua adequado para cargas de trabalho esporádicas ou padrões simples de requisição/resposta, mas, quando é preciso baixa latência consistente ou gerenciamento de estado persistente, servidores com estado são mais eficazes
Limites do serverless e gargalos de desempenho
- Na Unkey, a autenticação de API ficava no caminho crítico das requisições, então latências de poucos milissegundos impactavam diretamente a experiência do usuário
- A distribuição global na edge e o escalonamento automático do Cloudflare Workers eram atraentes, mas a ausência de persistência de cache e a latência das requisições de rede se revelaram um problema
-
Problema de cache
- Funções serverless não têm memória persistente entre invocações, então cada consulta ao cache exigia uma requisição de rede externa
- A latência p99 da consulta ao cache do Cloudflare foi medida em mais de 30 ms
- Mesmo empilhando múltiplas camadas de cache (SWR, Redis etc.), no fundo nada pode ser mais rápido que ‘0 requisições de rede’
- Como resultado, não foi possível atingir a meta de resposta abaixo de 10 ms
-
Problema de acoplamento com SaaS
- Serverless reduz a carga de gerenciamento de infraestrutura, mas, na prática, ferramentas SaaS adicionais acabam sendo indispensáveis
- cache → Redis, processamento em lote → Queue, logs → Durable Objects etc.
- Cada serviço adiciona latência, custo e pontos de falha
- Mesmo combinando Cloudflare Durable Objects, Queues e Workflows, a complexidade real acabou aumentando
-
Problema do pipeline de dados
- Como o ambiente serverless é efêmero, era necessário descarregar os dados imediatamente em toda chamada
- Para tratar eventos, logs e métricas, foi preciso construir manualmente serviços complexos de bufferização e retransmissão
- Exemplo:
- envio em lote de logs para o ClickHouse via
chproxy
- criação de um servidor de buffer separado para contornar limites de taxa ao enviar logs ao Axiom
- Como resultado, até uma funcionalidade simples de analytics passou a ter a complexidade de um sistema distribuído de processamento de eventos
Migração para servidores com estado
- Com a adoção do servidor com estado v2 baseado em Go, passou a ser possível fazer batching em memória e flush periódico
- Não são necessários serviços adicionais nem pipelines complexos
- Manutenção, debugging e testes locais ficaram muito mais simples
-
Resultados de desempenho
- A comparação entre
/v1/keys.verifyKey e /v2/keys.verifyKey mostrou redução de latência em 6 vezes
- Mesmo com menos infraestrutura do que os 300 POPs globais da Cloudflare, a percepção de desempenho do usuário melhorou bastante
Benefícios além do desempenho
-
Self-Hosting
- Como dependia do runtime da Cloudflare, os clientes não conseguiam fazer self-host da Unkey
- Embora o runtime do Workers seja tecnicamente open source, executá-lo localmente (mesmo em modo de desenvolvimento) era extremamente difícil
- Ao migrar para um servidor Go padrão, o self-hosting ficou simples
- basta executar o comando
docker run -p 8080:8080 unkey/api
- sem runtime especial nem configurações complexas
- Agora, os desenvolvedores conseguem rodar toda a stack da Unkey localmente em poucos segundos, o que facilitou muito o debugging e os testes
- A velocidade de desenvolvimento e debugging local melhorou drasticamente
-
Melhoria na experiência do desenvolvedor
- Limitações enfrentadas no serverless:
- necessidade de considerar restrições de execução da função
- dificuldade para manter estado entre chamadas
- complexidade para depurar logs distribuídos
- ambiente de testes locais inconveniente
- A migração para servidores com estado eliminou esse imposto de complexidade (Complexity Tax)
-
Independência de plataforma
- Não há mais dependência do ecossistema Cloudflare
- pode ser implantado em qualquer lugar
- pode usar qualquer banco de dados
- pode integrar serviços de terceiros sem se preocupar com compatibilidade de runtime
Estratégia de migração e lições aprendidas
- A migração foi usada como oportunidade para corrigir problemas de design da API acumulados ao longo do tempo
- A nova API v2 roda junto com a v1 existente, e os clientes podem usar ambas durante o período de descontinuação
- Uma vantagem de manter o serverless é que mesmo quando o uso cai a zero, o custo para manter a API v1 em execução não é alto, garantindo um período de migração gratuito
-
O que foi mantido
- Nem tudo da abordagem serverless foi descartado
- distribuição global na edge: uso do AWS Global Accelerator para manter baixa latência no mundo todo
- auto scaling: o Fargate cuida do escalonamento sem as limitações do serverless
-
Melhorias no rate limiter
- No modelo serverless, era necessário um trade-off significativo entre velocidade, precisão e custo, e, por sua natureza distribuída, era quase impossível alcançar os três ao mesmo tempo
- Com servidores com estado e estado em memória, foi possível criar um rate limiter mais rápido, mais preciso e com menor custo operacional
Quando serverless é adequado (e quando não é)
-
Quando serverless é adequado
- Cargas de trabalho esporádicas: quando não roda continuamente, a economia do scale-to-zero é excelente
- Padrões simples de requisição/resposta: quando não há necessidade de estado persistente nem pipelines de dados complexos
- Arquiteturas orientadas a eventos: excelente para responder a eventos sem gerenciar infraestrutura
-
Quando serverless não é adequado
- Quando é necessária baixa latência consistente: dependências externas de rede degradam o desempenho
- Quando é necessário estado persistente: contornar a ausência de estado aumenta a complexidade
- Cargas de trabalho de alta frequência: o modelo de cobrança por chamada se torna antieconômico
- Quando é necessário controle fino: as abstrações da plataforma podem se tornar uma limitação
-
Custo da complexidade
- A maior lição da migração foi entender o custo de complexidade do trabalho necessário para contornar as limitações da plataforma
-
O que foi construído no serverless
- bibliotecas sofisticadas de cache para contornar a ausência de estado
- vários serviços auxiliares para processamento de dados em lote
- pipelines complexos de logs para coleta de métricas
- workarounds sofisticados para desenvolvimento local
-
Em servidores com estado
- tudo isso desaparece
- houve uma mudança de um sistema distribuído com várias partes móveis para uma arquitetura de aplicação mais simples
- às vezes, a melhor solução é escolher uma base diferente em vez de contornar restrições
Próximos passos
- Atualmente, a aplicação roda no AWS Fargate atrás do Global Accelerator, mas isso é uma solução temporária
- No próximo ano, a empresa pretende lançar sua própria plataforma de implantação, o "Unkey Deploy", para permitir que clientes (e a própria Unkey) executem o Unkey onde quiserem
- A migração para servidores Go com estado é o primeiro passo para tornar a Unkey realmente portátil e apta para self-hosting
- Os detalhes de implementação foram publicados como open source no repositório do GitHub
3 comentários
Eu também já fui quase um devoto de serverless e promovi arquiteturas serverless com bastante entusiasmo, mas hoje em dia prefiro mais uma estrutura feita com uma instância
ec2e umrds. E então vou separando uma coisa de cada vez conforme necessário. Passei a pensar muito mais antes de adotar serverless.Há vários motivos, mas percebi que, se houver ao menos uma pessoa no time sem conhecimento de serverless, os custos de comunicação/manutenção aumentam consideravelmente. E, ao voltar a operar servidores, senti novamente que serverless não era tão barato nem tão prático quanto eu imaginava.
Comentários do Hacker News
Como alguém que usou ambientes serverless por vários anos, principalmente amazon lambda, mas também outras coisas, concordo fortemente com o autor.
Serverless tira trabalho de um lado, mas ao mesmo tempo aumenta o trabalho em outras áreas porque você acaba tendo que resolver “problemas criados artificialmente”.
Um caso meu foi o problema do limite de tamanho de upload.
Quando migramos uma aplicação existente para serverless, achei que bastaria criar um endpoint de API para importar grandes volumes de dados de clientes e anexar um worker em segundo plano.
Mas no “api gateway” (o proxy que chama o código) não dava para fazer upload acima de 100MB, e quando perguntei se era possível mudar o limite, me orientaram simplesmente a pedir para os clientes dividirem o arquivo em partes menores antes de enviar.
Tecnicamente isso pode soar razoável, mas na prática os clientes não vão mudar a forma como fazem upload.
É aquela sensação de “funciona no vácuo”: na teoria parece ótimo, mas quando você coloca em prática, o tempo e o dinheiro que economizou migrando para serverless acabam sendo reinvestidos para resolver problemas específicos de serverless.
Para resolver isso, seria preciso fornecer URLs pré-assinadas do S3.
Dá para integrar fazendo o usuário enviar direto para o S3 e depois informar o resultado do upload, ou usando o nome do arquivo separado por request id, entre outras abordagens.
Como alguém que já usa AWS há muito tempo, isso é bem chato, mas também dá para entender o limite de 100MB, porque abrir uma API de upload arbitrário aumenta muito o risco de ataques DoS.
Ainda assim, considerando como a internet ficou mais rápida hoje em dia, 100MB parece um limite meio defasado.
Mesmo assim, acho que em algum momento um limite sempre vai ser necessário.
Nossa empresa já foi, em certo momento, o cliente representativo da área de certificados SSL da AWS.
Para suportar Vanity URL (domínio personalizado), precisávamos de um certificado SSL por domínio, e eram milhares.
A ferramenta de gerenciamento de certificados da AWS só funcionava de forma confortável até algumas centenas, então levou cerca de 3 meses até esse problema ser resolvido.
Foi surpreendente ver que alguns serviços da AWS não conseguiam responder rápido à demanda de um número muito pequeno de clientes.
No começo, Lambda parecia ter um potencial enorme, então adotamos, mas no fim abandonamos todos os projetos em Lambda e migramos para ambientes com contêineres conforme a necessidade.
No Lambda você ainda precisa atualizar o runtime do Node a cada 1 ou 2 anos, e com contêineres você decide esse ciclo por conta própria, então é bem mais flexível.
O problema mais difícil da ciência da computação é copiar arquivos de um computador para outro.
Deixo uma referência para leitores do futuro.
Se você usar o uploader e endpoint do “tus”, terá recursos como upload em partes e retomada, o que é adequado para contornar esse tipo de limitação.
https://tus.io/
Como o artigo explica, serverless claramente tem seus próprios casos de uso.
Mas acho que não é adequado para a maioria das aplicações.
A menos que seja um caso muito específico, não pretendo usar serverless como infraestrutura central.
Na prática, minha impressão é que o gerenciamento de infraestrutura acaba ficando mais trabalhoso.
Cada plataforma tem exigências diferentes, e a forma de testar e desenvolver também varia, então tudo fica meio nebuloso e até o teste local é difícil.
As camadas de abstração também têm armadilhas em cada plataforma, e não existe um padrão de verdade.
Empacotar um executável como imagem Docker é mais conveniente, e ainda permite abstrair o ambiente de forma razoável, então para mim é um ambiente de desenvolvimento mais natural.
Sinto que o nível mínimo de abstração, no ambiente Linux e no sistema de arquivos, é o mais eficiente.
Se precisar, você pode subir essa imagem como servidor e operá-la sob demanda ou sempre ativa.
Quando olho para várias tendências tecnológicas populares nos últimos 10 anos, vejo muitos casos em que ganha força uma tecnologia que grandes empresas usaram para resolver problemas que, na prática, só existiam na escala delas.
Exemplos disso incluem GraphQL, react, Tailwind, NextJS e várias outras.
Nenhuma ferramenta serve como solução universal para todos os problemas; o importante é escolher com base na experiência e no entendimento da sua própria situação.
Eu quase queria mostrar o quanto está sendo “divertido” tentar rodar um app em amazon lambda localmente agora.
Tentar testar antes do deploy é um desafio por si só.
Knative (Serverless para Kubernetes) aceita contêineres diretamente.
Como é um formato de empacotamento padrão, dá para migrar facilmente entre várias plataformas.
A equipe na verdade estava desenvolvendo uma biblioteca para ser integrada a uma aplicação de servidor com estado, e não a uma aplicação em si.
A vantagem de desempenho também vinha do fato de a autenticação ser processada perto do ambiente do cliente, e não do serverless em si.
Na verdade, toda plataforma “serverless” não aceita imagens Docker?
Pelo que sei, o Cloud Run aceita.
Na real, a preocupação é com o que acontece quando uma “serverless function” abstrai coisa demais.
O ClickHouse não gosta de milhares de inserts pequenos, então agregamos eventos em um serviço Go chamado chproxy e enviamos batches grandes.
Cada Cloudflare Worker envia eventos analíticos para o chproxy, que os acumula e então manda em volume para o ClickHouse.
No caso específico de dados do ClickHouse, em vez de criar um serviço separado só para isso, havia também a funcionalidade de insert assíncrono, então fico curioso por que não usaram isso.
https://clickhouse.com/docs/optimize/asynchronous-inserts
Os desenvolvedores parecem soterrados por ferramentas que supostamente “facilitam tudo”.
Na verdade, a maioria dos problemas pode ser resolvida facilmente com as ferramentas mais básicas: compilador, script bash e bibliotecas.
Essa obsessão por ferramentas às vezes acaba prejudicando tanto a empresa quanto os próprios desenvolvedores.
Na minha opinião, o Docker de 2013 foi a última ferramenta que realmente trouxe uma mudança universal e positiva.
Depois disso, apareceram muitas coisas que até ajudam algumas empresas, mas que, quando aplicadas indiscriminadamente a todas, acabam destruindo a produtividade ou quebrando sistemas.
Hoje, lugares como a Cloudflare empurram v8 isolates como se fossem “o próximo Docker”, mas isso acerta em cheio alguns workloads, não todos.
O padrão de “receber uma imagem docker e colocá-la na internet” é poderoso demais, então acho que em 2040 ainda vai continuar sendo a abordagem dominante.
Outro problema é que está ficando cada vez mais raro encontrar gente que domina o básico.
O trabalho é conduzido como se a maior parte da stack fosse terceirizada para serviços de terceiros, e o que de fato foi construído internamente é só uma parte da aplicação principal.
E até isso pode acabar sendo escrito por IA.
A indústria inteira está cultivando uma espécie de “impotência aprendida”.
Mais gente do que parece não entende bem compiladores, scripts bash e bibliotecas.
AWS lambda é barato até demais.
Mas ajuda em termos de visibilidade profissional!
Nunca vi alguém chegar ao nível Staff escrevendo script bash.
Queria perguntar ao “vercel security checkpoint”:
no iPhone, usando proton VPN com Firefox Focus e saindo por nós da Califórnia e do Canadá, recebo repetidamente o erro code 99 “Failed to verify your browser”.
Qual é o problema?
Esta thread reforça minha opinião.
O próprio termo serverless é tão mal definido que o nome me parece um contrassenso.
No fim, os servidores continuam existindo.
É como dizer “sem eletricidade” para algo que claramente usa eletricidade.
Parece só uma troca de nome que não explica o que realmente está acontecendo.
Não acho que a única conclusão seja “serverless é ruim”.
A lição mais importante é que, se um serviço tem dependências, mover esse serviço para mais perto do cliente sem mover também essas dependências pode deixar a experiência ponta a ponta surpreendentemente lenta.
Inevitalmente, é melhor construir perto das dependências, e se isso não bastar, mover ou sincronizar todas as dependências para perto do cliente, embora na prática isso muitas vezes fique complexo demais.
Depende de como e com que frequência as dependências são usadas, e no caso de banco de dados, se ele deve ficar perto do servidor ou do cliente varia conforme o uso.
Em alguns cenários de uso, resposta rápida é importante; em outros, pode ser lenta sem problema.
Dependendo do que dá para colocar em cache no servidor ou no cliente, também é possível dividir a solução.
Não acho que seja algo para pensar de forma tão binária.
Se você quer a melhor relação custo-desempenho possível, a resposta é configurar suas próprias instâncias e fazer você mesmo o que precisa ser feito.
Não precisa deixar os provedores de nuvem virarem mágicos da cobrança excessiva.
Acho que o “máximo local” atual a que chegamos é usar contêineres Docker como ambiente padrão e artefato de deploy, injetando secrets apenas quando necessário.
Isso facilita o teste local e ainda preserva a maior parte dos principais benefícios, como automação de infraestrutura e reprodutibilidade.
Serverless é exagero para a maioria dos apps, mas combina bem com alguns.
Em especial, pode funcionar melhor para utilitários simples ou serviços sob demanda que não precisam de infraestrutura própria, e para apps stateless de grande escala.
Não quer dizer que serverless sirva apenas para casos simples; acho que existe uma contradição fundamental entre o modelo de “web app tradicional” e as plataformas serverless.
Acho que agora estou pronto para receber o misterio.
https://github.com/daitangio/misterio
É um wrapper simples para cluster Docker stareless.
Começou no meu homelab, mas ganhou tração.
Docker é meio parecido com microservices.
Serve para alguns apps, mas foi exageradamente promovido como padrão da indústria.
Se usado em excesso, o Docker traz custo operacional e de patch de segurança, e se você sair aplicando em todo projeto sem critério, falha na gestão de risco.
Hoje em dia a maioria dos desenvolvedores resolve problemas de dependência simplesmente evitando instalação global.
Docker não é mais tão essencial quanto antes, mas ainda é dominante.
Do ponto de vista de provedores de hospedagem, imagino que a adoção de Docker aumente a margem em pelo menos 10%.
Tenho a impressão de que o “fetiche por ferramentas” apontado abaixo também inclui Docker.
O ponto principal não é que serverless “não funciona”, e sim que os autores não entendiam bem a base sobre a qual estavam construindo.
Colocar uma API sensível a latência em um edge runtime stateless é um erro de principiante, e o sofrimento que veio disso era totalmente previsível.
Pela minha experiência, a maioria dos problemas com serviços de nuvem vem de uso incorreto ou de mal-entendidos de arquitetura.
São problemas humanos que poderiam ter sido evitados com um projeto mais cuidadoso.
O problema é que, na maioria das vezes, os fornecedores de nuvem promovem seus produtos com marketing convincente e escondem os números reais de desempenho.
Você só descobre se Lambdas realmente são rápidas o suficiente para o seu workload, ou se a replicação externa do AWS RDS é adequada, depois de testar.
Aprendi pela experiência que o desempenho real da AWS precisa ser medido diretamente com benchmark.
O ponto não é que os autores não entenderam, e sim que a informação que alguém compartilhou já tem valor por si só.
Não acho que isso possa ser visto apenas como “erro de iniciante”.
Na prática, muitos engenheiros sabem que certa abordagem não é adequada para produção, mas gestores a empurram facilmente porque “é o jeito moderno”.
Ou então, por restrições de tempo e custo, você é forçado a fazer uma escolha pior.
Ou pode mesmo ser que a equipe original não soubesse bem o que estava fazendo; de qualquer forma, acho que compartilhar histórias assim ajuda muito o avanço da comunidade como um todo.
Pela minha experiência, quando você precisa de algo garantido — autoescalonamento rápido, baixa latência, CPU, disco, velocidade de rede etc. — o mais confiável é gerenciar suas próprias instâncias EC2.
Se você entrega o controle esperando ganhar desempenho, acaba esbarrando em gargalos que não consegue corrigir.
No fim, isso é admitir que os autores são “uma das 10.000 pessoas de hoje”.
https://xkcd.com/1053/
Pessoalmente, sou grato por terem compartilhado esse tipo de informação e erro.
Engenharia sempre é uma disputa de custos
No começo, isso é usado para reduzir o tempo de prototipagem ou de construção do negócio,
mais tarde é preciso reduzir os custos otimizando
Um texto desses por si só prova o quanto a pessoa não é engenheira
Isso, isso