Skip to main content
backend4 de janeiro de 202612 min de leitura

Cloud Functions vs Backend Tradicional: Quando Serverless Faz Sentido

Uma comparação real de funções serverless e backends tradicionais — cobrindo custo, latência, experiência do desenvolvedor e quando cada abordagem vence.

serverlesscloud-functionsbackend
Cloud Functions vs Backend Tradicional: Quando Serverless Faz Sentido

Todo projeto greenfield que começo força a mesma decisão: subo um servidor tradicional ou escrevo cloud functions? Depois de entregar sistemas em produção em ambos os lados — Firebase Functions para workflows orientados a eventos, AWS Lambda para endpoints de API, e servidores Express/Fastify para tudo no meio — tenho uma opinião bastante nuançada sobre quando cada abordagem realmente vence. A resposta, previsivelmente, é "depende", mas os detalhes do que depende valem a pena explorar em profundidade.

Definindo as Duas Abordagens

Antes de comparar, vou ser preciso sobre o que quero dizer com cada uma.

Backend tradicional se refere a um processo de servidor de longa duração — uma aplicação Node.js com Express, um servidor Python com FastAPI, um servidor HTTP em Go — deployado em uma VM, contêiner ou plataforma de computação gerenciada. O processo inicia, permanece rodando e lida com requisições conforme chegam. Alvos de deploy incluem EC2, Cloud Run, ECS, Railway, Fly.io ou um VPS simples.

Cloud functions (funções serverless) são handlers de funções individuais que executam em resposta a eventos. A plataforma gerencia a infraestrutura de computação inteiramente. Seu código roda, retorna uma resposta, e o ambiente de execução pode ou não persistir para a próxima invocação. Provedores incluem AWS Lambda, Google Cloud Functions, Firebase Functions, Azure Functions e Cloudflare Workers.

A distinção não é sobre a linguagem ou framework — é sobre o modelo de execução. Um backend tradicional é dono do ciclo de vida do seu processo. Uma cloud function não é.

A Realidade do Cold Start

Cold starts são a limitação serverless mais discutida, e a realidade em 2026 é mais nuançada do que o discurso sugere.

O Que Realmente Acontece Durante um Cold Start

Quando uma cloud function não foi invocada recentemente (ou quando invocações concorrentes excedem as instâncias warm disponíveis), a plataforma precisa:

  1. Provisionar um novo ambiente de execução (microVM ou contêiner)
  2. Baixar e extrair seu pacote de deploy
  3. Inicializar o runtime (Node.js, Python, etc.)
  4. Executar seu código de inicialização (imports de módulos, configuração de SDK, conexões com BD)
  5. Executar o handler da função propriamente dito

Os passos 1-3 são overhead da plataforma. O passo 4 é onde suas escolhas de código importam enormemente.

Números de Cold Start na Prática

Aqui estão os tempos de cold start que medi em diferentes plataformas e configurações em projetos reais:

Plataforma Runtime Tamanho do Bundle Cold Start
AWS Lambda Node.js 20 5 MB 250-400ms
AWS Lambda Node.js 20 50 MB 800-1200ms
AWS Lambda Python 3.12 10 MB 400-600ms
Firebase Functions v2 Node.js 20 15 MB 600-1000ms
Cloudflare Workers JavaScript 1 MB 0-5ms
Google Cloud Functions v2 Node.js 20 10 MB 300-500ms

Cloudflare Workers são a exceção porque usam V8 isolates em vez de contêineres, o que elimina a penalidade de startup do contêiner inteiramente. O tradeoff é um ambiente de runtime mais restrito.

Mitigando Cold Starts

Existem várias estratégias que genuinamente ajudam:

Provisioned concurrency (Lambda): Mantém N instâncias warm o tempo todo. Custa US$ 0,0000041667 por GB-segundo de capacidade provisionada. Para uma função de 256MB, são cerca de US$ 3,20/mês por instância warm. Vale a pena para endpoints sensíveis a latência.

Minimum instances (Cloud Run, Firebase Functions v2): Mesmo conceito, nomenclatura diferente. Cloud Run cobra por instâncias ociosas a uma taxa reduzida.

Redução do tamanho do bundle: Tem a maior relação impacto-esforço. Tree-shaking, excluir dependências de dev e usar clientes SDK mais leves reduzem dramaticamente o tempo de inicialização.

// Bad: imports the entire AWS SDK (adds 40MB+ to bundle)
import AWS from 'aws-sdk';

// Good: imports only the S3 client (adds ~3MB)
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';

Inicialização lazy: Não estabeleça conexões com banco de dados ou carregue recursos pesados no escopo do módulo. Inicialize-os na primeira invocação e reutilize entre invocações warm.

import { Pool } from 'pg';

let pool: Pool | null = null;

function getPool(): Pool {
  if (!pool) {
    pool = new Pool({
      connectionString: process.env.DATABASE_URL,
      max: 1, // Single connection per Lambda instance
    });
  }
  return pool;
}

export async function handler(event: APIGatewayEvent) {
  const db = getPool();
  const result = await db.query('SELECT * FROM users WHERE id = $1', [event.pathParameters?.id]);
  return { statusCode: 200, body: JSON.stringify(result.rows[0]) };
}

Comparação de Custos em Diferentes Escalas

Custo é onde a conversa fica interessante, porque a economia se inverte conforme o tráfego escala.

Baixo Tráfego (1.000 - 50.000 requisições/dia)

Nessa escala, serverless é quase sempre mais barato. Muitas startups e projetos paralelos se encaixam aqui.

Custo Lambda para 30.000 requisições/dia, 200ms de duração média, 256MB de memória:

  • Cobranças por requisição: 900K requisições/mês x US$ 0,20/milhão = US$ 0,18
  • Cobranças de computação: 900K x 0,2s x 0,25GB x US$ 0,0000166667 = US$ 0,75
  • Total: ~US$ 1/mês

Servidor tradicional equivalente (t4g.small na AWS):

  • On-demand: US$ 12,26/mês
  • Com Savings Plan de 1 ano: ~US$ 8/mês

Com baixo tráfego, você está pagando por um servidor sempre ligado que está 95% ocioso.

Tráfego Médio (100.000 - 1.000.000 requisições/dia)

Esta é a zona de cruzamento onde a comparação fica apertada.

Custo Lambda para 500.000 requisições/dia, 200ms de média, 256MB:

  • Cobranças por requisição: 15M/mês x US$ 0,20/milhão = US$ 3,00
  • Cobranças de computação: 15M x 0,2s x 0,25GB x US$ 0,0000166667 = US$ 12,50
  • Total: ~US$ 15,50/mês

t4g.medium (2 vCPU, 4GB RAM):

  • On-demand: US$ 24,53/mês
  • Com Savings Plan: ~US$ 16/mês

Os custos são comparáveis, mas o servidor tradicional lida com esse tráfego com margem significativa. Se seu tráfego é estável, o servidor é levemente mais barato. Se seu tráfego é com picos (mais pesado em dias úteis, explosões orientadas a eventos), a cobrança por invocação do Lambda significa que você não paga por períodos tranquilos.

Alto Tráfego (5.000.000+ requisições/dia)

Nessa escala, serverless é quase sempre mais caro para workloads sustentados.

Custo Lambda para 5.000.000 requisições/dia, 200ms de média, 512MB:

  • Cobranças por requisição: 150M/mês x US$ 0,20/milhão = US$ 30
  • Cobranças de computação: 150M x 0,2s x 0,5GB x US$ 0,0000166667 = US$ 250
  • Total: ~US$ 280/mês

c6g.large (2 vCPU, 4GB RAM) com auto-scaling group:

  • 2 instâncias com Savings Plan: ~US$ 60/mês
  • Com load balancer: ~US$ 16/mês
  • Total: ~US$ 76/mês

A abordagem tradicional é 3,7x mais barata nessa escala, e a diferença aumenta conforme o tráfego cresce.

Custos Ocultos a Considerar

A cobrança do Lambda não é o quadro completo. Adicione API Gateway (US$ 1-3,50 por milhão de requisições), CloudWatch Logs (US$ 0,50/GB ingerido) e X-Ray tracing se você usar. No lado tradicional, adicione custos de load balancer, monitoramento e o tempo de ops para gerenciar instâncias e deploys.

Diferenças na Experiência do Desenvolvedor

DX Serverless

Vantagens:

  • Nenhuma infraestrutura para gerenciar (sem patches, sem configuração de escalabilidade)
  • Deploy por função significa menor raio de explosão
  • Desenvolvimento local com emuladores (Firebase Emulator Suite, SAM CLI, Serverless Framework offline)
  • Observabilidade embutida através do logging e tracing da plataforma

Pontos de dor:

  • Desenvolvimento local nunca corresponde perfeitamente ao comportamento de produção
  • Debugar chamadas distribuídas entre funções é mais difícil que percorrer um monolito
  • Limites de tamanho de deploy (Lambda: 250MB descompactado) restringem escolhas de dependências
  • Configuração de IAM e permissões é verbosa e fácil de errar
  • Cold starts tornam loops de iteração de desenvolvimento mais lentos ao testar contra funções deployadas

DX Backend Tradicional

Vantagens:

  • O ambiente de desenvolvimento local é o ambiente de produção (mesmo processo, mesmo estado)
  • Debugging é direto — conecte um debugger, coloque breakpoints, percorra o código
  • Sem limites de tamanho de deploy
  • Padrões de middleware, hooks de ciclo de vida de requisição e tratamento global de erros são naturais
  • Suporte a WebSocket, processos de longa duração e background jobs funcionam sem serviços extras

Pontos de dor:

  • Você é dono do ciclo de vida da infraestrutura (atualizações, escalabilidade, health checks)
  • Deploys requerem orquestração (rolling updates, períodos de graça de health check)
  • Escalabilidade requer configuração (auto-scaling groups, orquestração de contêineres)
  • Monitoramento e logging requerem configuração explícita

A Lacuna de Frameworks Está Diminuindo

Frameworks como SST (Serverless Stack) e Architect melhoraram dramaticamente a DX serverless. O SST em particular fornece um modo de desenvolvimento "Live Lambda" onde seu código rodando localmente lida com invocações Lambda em tempo real, eliminando o ciclo deploy-teste inteiramente.

No lado tradicional, plataformas como Railway, Fly.io e Render simplificaram o deploy para git push. A carga operacional de rodar um backend tradicional diminuiu significativamente.

Quando Serverless Vence

Workloads Orientados a Eventos

Se seu código roda em resposta a eventos — uploads de arquivos, mudanças no banco de dados, mensagens de fila, tarefas agendadas — serverless é o encaixe natural. Você não está mantendo um polling loop ou um processo listener. A plataforma aciona seu código exatamente quando necessário.

// Firebase Functions: triggered on Firestore document creation
import { onDocumentCreated } from 'firebase-functions/v2/firestore';

export const onOrderCreated = onDocumentCreated('orders/{orderId}', async (event) => {
  const order = event.data?.data();
  if (!order) return;

  // Send confirmation email
  await sendEmail(order.customerEmail, {
    template: 'order-confirmation',
    data: { orderId: event.params.orderId, items: order.items },
  });

  // Update inventory
  await updateInventory(order.items);

  // Notify restaurant
  await sendPushNotification(order.restaurantId, {
    title: 'New Order',
    body: `Order #${event.params.orderId} received`,
  });
});

Tráfego Esporádico ou Imprevisível

Uma ferramenta interna que recebe 50 requisições na segunda de manhã e 2 requisições no resto da semana. Um receptor de webhook que processa eventos de uma API de terceiros. Um gerador de relatórios agendados que roda por 30 segundos uma vez por dia. Esses workloads têm custo quase zero em serverless e desperdiçam dinheiro em infraestrutura sempre ativa.

Endpoints de API com Tráfego Baixo a Médio

Para a API do MVP de uma startup servindo alguns milhares de requisições por dia, serverless fornece um backend funcional com overhead operacional mínimo e custo quase zero. Você pode focar inteiramente na lógica de negócio.

Processamento Paralelo de Dados

Precisa processar 10.000 imagens, transcodificar 500 vídeos ou rodar analytics em um grande conjunto de dados? Distribua para milhares de invocações Lambda concorrentes, processe em paralelo e pague apenas pelo tempo de computação usado. Alcançar o mesmo paralelismo com servidores tradicionais requer provisionar (e pagar por) essa capacidade de pico.

Quando Backend Tradicional Vence

Conexões Persistentes

Conexões WebSocket, Server-Sent Events, streams gRPC e long-polling todos requerem uma conexão persistente entre cliente e servidor. Funções Lambda têm um limite de execução de 15 minutos e não são projetadas para conexões de longa duração.

Você pode contornar isso com APIs WebSocket do API Gateway ou AWS IoT Core, mas a complexidade e o custo frequentemente excedem um simples servidor WebSocket em Cloud Run ou uma plataforma de contêineres.

Pipelines de Requisição Complexos

Quando uma requisição passa por autenticação, rate limiting, validação de input, lógica de negócio, operações de banco de dados, atualizações de cache, emissão de eventos e formatação de resposta — o padrão de pipeline de middleware do Express/Fastify/Koa é ergonomicamente superior ao padrão de handler plano das cloud functions.

// Traditional: clean middleware pipeline
app.use(cors());
app.use(helmet());
app.use(rateLimiter);
app.use(authenticate);
app.use('/api/v1', apiRouter);
app.use(errorHandler);

Replicar isso no Lambda requer ou um framework como Middy (que adiciona peso e complexidade) ou duplicar código de setup entre funções.

Operações com Estado

Se sua aplicação mantém estado em memória — um cache, um pool de conexões, um contador de rate limiter, dados de sessão — um servidor tradicional lida com isso naturalmente. Funções Lambda podem reutilizar estado entre invocações warm, mas você não pode depender disso. Qualquer estado que precisa persistir necessita de um serviço externo (Redis, DynamoDB), que adiciona latência e custo.

Requisitos de Latência Apertados

Para APIs onde cada milissegundo importa — real-time bidding, servidores de jogos, transações financeiras — a latência imprevisível introduzida por cold starts é inaceitável, mesmo com provisioned concurrency. Um processo de servidor bem ajustado com pool de conexões e caches warm entrega tempos de resposta consistentes abaixo de 10ms que serverless não pode garantir.

A Abordagem Híbrida

Na prática, a maioria dos sistemas em produção que construo acaba sendo híbrida. A API principal roda em um backend tradicional (Cloud Run ou uma plataforma de contêineres), enquanto workloads auxiliares rodam em cloud functions.

Uma Arquitetura Híbrida Prática

Client
  │
  ├─→ API Gateway / Load Balancer
  │     └─→ Cloud Run (main API)
  │           ├─→ PostgreSQL (Cloud SQL)
  │           ├─→ Redis (Memorystore)
  │           └─→ Pub/Sub (event bus)
  │
  └─→ Cloud Functions (event handlers)
        ├─→ Image processing (on upload)
        ├─→ Email sending (on order creation)
        ├─→ Analytics aggregation (scheduled)
        └─→ Webhook processing (HTTP trigger)

A API principal lida com tráfego de requisição-resposta síncrono com conexões persistentes ao banco de dados, cache em memória e latência consistente. Cloud functions lidam com workloads assíncronos orientados a eventos onde cold starts não importam porque o usuário não está esperando por uma resposta.

Estratégia de Migração: Tradicional para Serverless

Se você tem um backend tradicional existente e quer adotar serverless seletivamente:

  1. Identifique operações orientadas a eventos atualmente rodando como background jobs, tarefas cron ou consumidores de fila. Essas são as mais fáceis de migrar.
  2. Extraia endpoints independentes que não compartilham estado com outros endpoints. Endpoints de health check, receptores de webhook e APIs utilitárias são bons candidatos.
  3. Mantenha a API principal como tradicional a menos que tenha uma razão convincente para decompô-la. O overhead operacional de gerenciar 50 funções Lambda frequentemente excede gerenciar um contêiner.
  4. Use um event bus (Pub/Sub, EventBridge, SQS) como camada de integração entre sua API tradicional e funções serverless. A API publica eventos; funções assinam e reagem.

Estratégia de Migração: Serverless para Tradicional

Ir na direção oposta às vezes é necessário quando uma API serverless ultrapassa sua arquitetura:

  1. Não reescreva tudo de uma vez. Comece consolidando as funções de maior tráfego em um único servidor de API.
  2. Mantenha funções orientadas a eventos como funções. Migre apenas handlers de requisição-resposta.
  3. Use as mesmas rotas de API. Aponte seu API Gateway/load balancer para o novo servidor para rotas migradas e continue roteando para Lambda para o resto.
  4. Migre conexões de banco de dados primeiro. O maior ganho de performance é mudar de setup de conexão por invocação para pool de conexões persistente.

Notas Específicas por Plataforma

Firebase Functions (v2)

Firebase Functions v2 roda em Cloud Run por baixo, o que dá vantagens significativas sobre a v1: concorrência configurável (múltiplas requisições por instância), minimum instances, timeouts mais longos (até 60 minutos) e alocações de memória maiores. Se você está em Firebase v1 functions, o upgrade para v2 vale o esforço de migração.

O Firebase Emulator Suite fornece a melhor experiência de desenvolvimento local no espaço serverless. Ele emula Firestore, Auth, Storage e Functions em um único ambiente local, com hot-reload nas mudanças de código.

AWS Lambda

Lambda tem o ecossistema de integração mais profundo — triggers de virtualmente todo serviço AWS, layers para código compartilhado e SnapStart para workloads Java. O recurso de function URL elimina a necessidade de API Gateway para casos de uso simples, economizando US$ 3,50 por milhão de requisições.

Lambda@Edge e CloudFront Functions são poderosos para manipulação de requisição/resposta no nível do CDN, mas a experiência de desenvolvimento e debugging é significativamente pior que Lambda padrão.

Cloud Run

Cloud Run borra a linha entre serverless e tradicional. Ele roda contêineres (qualquer linguagem, qualquer framework), escala a zero e suporta concorrência (múltiplas requisições por instância). É serverless no modelo de cobrança mas tradicional no modelo de execução. Para equipes que querem economia serverless sem reescrever seu código como handlers de função, Cloud Run é frequentemente a resposta certa.

Tomando a Decisão

Aqui está o framework de decisão que uso:

  1. O workload é orientado a eventos e assíncrono? Serverless.
  2. O tráfego é baixo e imprevisível? Serverless.
  3. Você precisa de conexões persistentes ou estado em memória? Tradicional.
  4. Latência baixa consistente é crítica? Tradicional.
  5. A equipe é pequena e avessa a ops? Serverless (ou Cloud Run).
  6. O tráfego é alto e estável? Tradicional (mais barato).
  7. É uma mistura do acima? Híbrido.

A pior decisão é tratar isso como uma escolha de tudo ou nada. Use a ferramenta certa para cada workload, conecte-as através de interfaces bem definidas, e você obtém os benefícios de ambas sem se comprometer inteiramente com nenhuma.

DU

Danil Ulmashev

Full Stack Developer

Interesse em trabalhar juntos?