# SnapStart em container: o bake-off contra concorrência provisionada

A AWS liberou SnapStart para funções empacotadas como imagem de container, prometendo partida sub-segundo em artefatos de até 10 GB. Coloquei a novidade lado a lado com concorrência provisionada, com o .zip e com um serviço sempre ligado no Fargate, usando os preços publicados e os limites da documentação. A conclusão não é sobre latência: é sobre onde o seu pipeline passa a pagar aluguel.

- URL: https://fernando.moretes.com/blog/snapstart-em-container-o-bake-off-contra-concorrencia-provisionada-aws-lambda-n

- Markdown: https://fernando.moretes.com/blog/snapstart-em-container-o-bake-off-contra-concorrencia-provisionada-aws-lambda-n/article.md?lang=pt

- Published: 2026-09-02T16:39:57.193Z

- Category: IA & Agentes

- Tags: aws-lambda, snapstart, serverless, cold-start, finops, container-images, observability

- Reading time: 8 min

- Source: [AWS Lambda now supports SnapStart for container image functions](https://aws.amazon.com/about-aws/whats-new/2026/07/aws-lambda-snapstart-container/)

---

Em 2 de setembro de 2026 a AWS estendeu o Lambda SnapStart para funções empacotadas como imagem de container — o formato que muita organização adotou por padrão corporativo de deploy, ou simplesmente porque precisava de mais que os 250 MB do .zip. A promessa é honesta: em vez de baixar camadas de imagem e inicializar o runtime a cada ambiente de execução novo, o Lambda tira um snapshot do ambiente já inicializado no momento em que você publica a versão e retoma dali. Só que trocar inicialização por snapshot não é uma flag de performance neutra. Muda quando você paga, muda o que quebra e muda onde o deploy falha. Este é o bake-off que eu faria antes de ligar isso em produção.

## O que de fato foi destravado

O bloqueio nunca foi o mecanismo de snapshot — o Firecracker já congelava memória e disco desde o SnapStart original em Java. O bloqueio era o contrato de ciclo de vida: uma imagem de container pode carregar qualquer runtime, e o Lambda precisava de um jeito padronizado de saber quando a inicialização terminou e quando o ambiente voltou do congelamento.

Esse contrato agora é público, e vive na Runtime API. Ao fim da sua inicialização, se `AWS_LAMBDA_INITIALIZATION_TYPE` valer `snap-start`, você roda seus hooks de *before-snapshot* e chama `GET /runtime/restore/next`. A chamada bloqueia — literalmente, o processo fica parado ali — até que o Lambda restaure aquele ambiente a partir do snapshot e devolva HTTP 200. Aí você roda os hooks de *after-restore* e entra no loop normal de invocação. Erros vão para `/runtime/init/error` (falha o `PublishVersion`) ou `/runtime/restore/error` (falha a invocação em voo e derruba o ambiente).

Se você usa as imagens base da AWS para Java 11+, Python 3.12+ ou .NET 8+, o Lambda coordena tudo isso e a experiência é idêntica ao .zip. Se você traz sua própria imagem base, um Runtime Interface Client próprio, ou as imagens base de `provided.al2023`, Node.js e Ruby, existem dois caminhos: implementar o `/restore/next` ou declarar `LABEL com.amazonaws.lambda.feature.snapstart="Allow"` no Dockerfile. Sem uma das duas coisas, publicar a versão simplesmente falha.

## O ciclo de vida do SnapStart em imagem de container

A inicialização sai do caminho da requisição e vai para o caminho do deploy. Os medidores de custo seguem o snapshot, não a invocação.

### 🏗️ Build e publicação

- Dockerfile LABEL ...snapstart=Allow (ci)
- Amazon ECR imagem ≤ 10 GB descomprimida (storage)
- PublishVersion ApplyOn=PublishedVersions (ci)

### ❄️ Init único (tempo de deploy)

- Init runtime + pesos do modelo (compute)
- beforeCheckpoint GET /runtime/restore/next (compute)
- Snapshot Firecracker cifrado, cacheado, replicado (storage)

### ⚡ Invocação (tempo de request)

- Restore retoma do snapshot (compute)
- afterRestore re-semeia CSPRNG, reconecta (security)
- Handler resposta ao cliente (compute)

### 📊 Sinais de observabilidade

- INIT_REPORT Init Duration (data)
- REPORT Restore + Billed Restore (data)
- X-Ray subsegmento Restore (data)

### 💸 Medidores de cobrança

- Cache do snapshot US$0,0000015046/GB-s (mín. 3h) (external)
- Restauração US$0,0001398 por GB (external)

### Fluxos

- dockerfile -> ecr: docker push (mesma região)
- ecr -> publish: otimização da imagem → Active
- publish -> init: Lambda inicializa uma única vez
- init -> hook: teto de max(timeout, 130 s)
- hook -> snap: bloqueia até congelar
- snap -> restore: retomada por ambiente novo
- restore -> after: HTTP 200 em /restore/next
- after -> handler: entra no loop de invoke
- init -> initreport: custo de init, no deploy
- restore -> report: partida a frio = Restore + Duration
- restore -> xray: substitui Initialization
- snap -> cache: por versão publicada ativa
- restore -> restorecost: por ambiente restaurado

## A física: o que o snapshot remove e o que ele não remove

O SnapStart tira do caminho da requisição três coisas caras: o download e a otimização das camadas da imagem, a carga do runtime e a sua inicialização — imports, wiring de framework, compilação de esquema, carregamento de pesos de modelo em memória. Tudo isso passa a acontecer uma vez por versão publicada, no deploy.

O que ele não remove é o que é genuinamente por ambiente. Conexões de rede abertas na init não têm estado garantido depois da retomada; o SDK da AWS costuma se reconectar sozinho, o resto é responsabilidade sua no hook de *after-restore*. Entropia é o caso perigoso: se você gerou um UUID, um segredo ou uma seed na init, esse valor entra no snapshot e sai idêntico em todos os ambientes restaurados. O Lambda já ajuda num ponto sensível — com SnapStart ativo o runtime passa a usar as credenciais de container (`AWS_CONTAINER_CREDENTIALS_FULL_URI`) em vez das variáveis de access key, justamente para que credenciais não expirem congeladas dentro do snapshot.

E há uma inversão de design que quase ninguém nota na primeira leitura: com SnapStart, o `/tmp` fica limitado a 512 MB, contra os 10.240 MB configuráveis de uma função normal. O padrão clássico de inferência — baixar pesos do S3 para o `/tmp` na init — deixa de caber. O caminho que sobra é assar os pesos na própria imagem (até 10 GB descomprimidos) e carregá-los em memória, onde o snapshot os captura já materializados. É exatamente o cenário que a AWS cita, mas ele exige reescrever o carregamento, não só ligar a flag.

## Quatro formas de matar a partida a frio de um artefato grande
| Critério | Container + SnapStart | Container + Conc. provisionada | .zip + SnapStart | ECS Fargate sempre ligado |
| --- | --- | --- | --- | --- |
| Partida a frio | Sub-segundo (Restore + Duration) | Dezenas de ms, sem partida a frio | Sub-segundo, sem pull de imagem | Zero em regime; minutos ao escalar |
| Custo fixo mensal (2 GB, us-east-1) | ~US$ 7,80 por versão publicada | ~US$ 21,60 por unidade provisionada | ~US$ 7,80 por versão publicada | vCPU + GB por segundo, 24×7 |
| Custo por partida | US$ 0,00028 por restauração (2 GB) | Nenhum; você já pagou o tempo todo | US$ 0,00028 por restauração (2 GB) | Nenhum, mas ociosidade é paga |
| Teto do artefato | 10 GB descomprimidos, via ECR | 10 GB descomprimidos, via ECR | 250 MB descompactados | Sem teto prático |
| Armazenamento efêmero (/tmp) | Máximo de 512 MB | Até 10.240 MB | Máximo de 512 MB | Volume da task, configurável |
| Runtimes cobertos | Java 11+, Python 3.12+, .NET 8+ e base própria com hooks | Qualquer runtime | Só Java 11+, Python 3.12+, .NET 8+ | Qualquer runtime |
| Alvo de invocação | Só versão publicada ou alias | Versão ou alias provisionado | Só versão publicada ou alias | Endpoint do balanceador |
| Modo de falha típico | PublishVersion falha; estado não-único vazado entre ambientes | Estouro de concorrência cai em partida a frio | Dependência não cabe em 250 MB | Capacidade ociosa vira a maior linha da fatura |

## A conta, feita com os preços publicados

Os números de us-east-1, arquitetura x86: cache do snapshot a US$ 0,0000015046 por GB-segundo, restauração a US$ 0,0001397998 por GB, concorrência provisionada a US$ 0,0000041667 por GB-s e duração sob demanda a US$ 0,0000166667 por GB-s. Para runtimes gerenciados Java a documentação isenta o preço de SnapStart; a conta abaixo vale para Python e .NET.

Uma função de 2 GB com uma versão publicada mantida ativa um mês inteiro custa cerca de **US$ 7,80** só de cache. Uma única unidade de concorrência provisionada nos mesmos 2 GB custa cerca de **US$ 21,60** no mês. Cada restauração de 2 GB sai por US$ 0,00028 — ou seja, você precisaria de aproximadamente **77 mil restaurações mensais** para que o custo variável do SnapStart alcançasse *uma* unidade provisionada. Para a esmagadora maioria das APIs, o SnapStart ganha por larga margem.

O problema não é o preço unitário; é a multiplicação. A cobrança de cache é **por versão publicada** e continua enquanto a versão existir, com mínimo de 3 horas faturadas. Uma esteira que publica versão a cada merge e nunca poda deixa dezenas de snapshots pagos rodando para trás. Vinte versões de 2 GB esquecidas no ar custam mais de US$ 150 por mês para servir absolutamente nenhum tráfego. Some a isso o fato de que o Lambda regenera snapshots periodicamente para aplicar patches de runtime, e cada re-execução da init é cobrada. Uma política de retenção de versões deixa de ser higiene e passa a ser controle financeiro.

## Como eu decidiria, por perfil de carga

### Container + SnapStart

**Pros**
- Melhor relação latência/custo para tráfego intermitente com artefato grande
- Mantém o padrão corporativo de deploy em imagem e o pipeline de ECR intacto
- Pesos de modelo carregados em memória entram no snapshot já materializados

**Cons**
- Teto de 512 MB em /tmp derruba o padrão de baixar artefato do S3 na init
- Exige auditoria de unicidade: entropia, IDs e conexões abertas na init
- Incompatível com concorrência provisionada — não dá para hedgear

**Verdict:** Padrão para API interativa e inferência com pesos assados na imagem.

### Container + concorrência provisionada

**Pros**
- Latência de partida em dezenas de milissegundos, sem retomada nenhuma
- Funciona com qualquer runtime e com /tmp de até 10 GB
- Sem exigências de unicidade sobre o código de inicialização

**Cons**
- Cerca de 2,8× o custo do cache de snapshot por GB-hora equivalente
- Dimensionar mal é caro; o estouro cai em partida a frio completa

**Verdict:** Só quando o SLO é estrito o bastante para que a retomada não caiba nele.

### .zip + SnapStart

**Pros**
- Sem etapa de otimização de imagem nem estado Pending/Inactive do container
- Ciclo de vida coordenado pelo runtime gerenciado, zero código de hook

**Cons**
- 250 MB descompactados é pouco para dependências de ML
- Migrar de container para .zip exige uma função nova — o tipo de pacote é imutável

**Verdict:** Continua o caminho mais simples quando a dependência cabe. Não é para onde migrar.

### ECS Fargate sempre ligado

**Pros**
- Nenhuma restrição de runtime, de /tmp ou de unicidade de estado
- Conexões persistentes, cache local quente e processos de longa duração

**Cons**
- Você paga ociosidade 24×7 e herda escalonamento, patching e balanceador
- Escalar horizontalmente leva minutos, não milissegundos

**Verdict:** Justifica-se por utilização alta e sustentada, não por medo de partida a frio.

## O que passa a quebrar na esteira, não na requisição

Este é o ponto que mais me interessa como arquiteto: o SnapStart move a inicialização para o deploy, e com ela move os modos de falha.

A init e os hooks de *before-snapshot* compartilham um timeout combinado de `max(timeout_da_função, 130 segundos)`. Estourou, o `PublishVersion` falha. Uma função de inferência que carrega 4 GB de pesos e antes só ficava lenta na primeira chamada agora quebra o pipeline de release — o que, sinceramente, é o lugar certo para quebrar, desde que a esteira trate isso como erro de deploy e não como flake.

Segundo: SnapStart só existe em versão publicada ou alias que aponte para versão. `$LATEST` nunca é SnapStart. Isso significa que o loop de desenvolvimento que invoca `$LATEST` exercita um caminho de código diferente do de produção; os testes de contrato precisam bater no alias. Terceiro: o tipo de pacote é imutável. Você não converte uma função de container em .zip — cria outra função, com outro ARN, outro alias, outros event source mappings. Planeje isso como migração, não como toggle.

E há os estados que só aparecem com tráfego raro. Uma função de container sem invocação por semanas tem a imagem otimizada recuperada, volta para `Pending` e **rejeita a primeira invocação**. Para runtimes Java, o snapshot é apagado após 14 dias sem invocação e você recebe `SnapStartNotReadyException`. Ambos são erros retentáveis pelo chamador — desde que o chamador tenha retry com backoff e a operação seja idempotente. Sem isso, a sua função de baixo tráfego tem um erro embutido no calendário.

## Como medir isso sem se enganar

O primeiro efeito colateral do SnapStart é que os seus dashboards de partida a frio param de funcionar em silêncio. O campo `Init Duration` **some do `REPORT`**, porque a inicialização não acontece mais na invocação; ele passa a viver num registro separado, o `INIT_REPORT`, junto com a duração dos hooks de *before-snapshot*. No `REPORT` aparecem dois campos novos: `Restore Duration` e `Billed Restore Duration`. Eles não são a mesma coisa — o primeiro inclui trabalho feito fora da microVM, que o usuário espera mas você não paga; o segundo é só a carga do runtime e os hooks de *after-restore*.

A fórmula que importa é simples e precisa entrar no seu SLO: **partida a frio = `Restore Duration` + `Duration`**. Se o seu alarme só olha `@duration`, ele vai mostrar uma melhora que não existe. No X-Ray a mudança é análoga: não há mais subsegmento `Initialization`, e sim `Restore`. Pela Telemetry API chegam `platform.restoreStart`, `platform.restoreRuntimeDone` (com status success, failure ou timeout) e `platform.restoreReport`.

Dois detalhes práticos. As variáveis `AWS_LAMBDA_LOG_GROUP_NAME` e `AWS_LAMBDA_LOG_STREAM_NAME` não existem em função SnapStart — bibliotecas de log que dependem delas quebram de forma discreta. E a medida honesta de impacto não é a duração da função: é a `IntegrationLatency` do API Gateway ou a `UrlRequestLatency` da function URL, em p99 e p99,9. Só ali você vê o que o cliente sentiu.

## Os cinco erros que eu esperaria ver nos primeiros três meses

- Ligar `ApplyOn=PublishedVersions` sem auditar a init: seeds, UUIDs e segredos gerados antes do snapshot passam a ser idênticos em todos os ambientes restaurados.
- Publicar uma versão por merge e nunca podar — o cache do snapshot é cobrado por versão ativa, com mínimo de 3 horas, para sempre.
- Manter o padrão de baixar pesos de modelo do S3 para o `/tmp` na init, ignorando o teto de 512 MB que o SnapStart impõe.
- Testar contra `$LATEST` e concluir que 'não mudou nada' — `$LATEST` nunca usa SnapStart.
- Declarar vitória com base em `@duration`, sem somar `Restore Duration` nem olhar a latência de integração na borda.

> **O verdadeiro trade-off não é latência contra custo:** É determinismo contra unicidade. A concorrência provisionada mantém N ambientes independentes, cada um com sua própria entropia, suas próprias conexões e seu próprio ciclo de vida. O SnapStart mantém **um** estado inicial canônico e o clona. Tudo que era acidentalmente único por ambiente vira deliberadamente compartilhado. Em sistemas financeiros isso deixa de ser detalhe de performance e vira questão de correção: um gerador pseudoaleatório clonado, um token de idempotência pré-computado ou um pool de conexões congelado produzem bugs que não aparecem em teste de carga, aparecem em reconciliação.

## Leitura pelos pilares

- **security**: O snapshot é cifrado, mas qualquer segredo materializado na init é clonado; busque credenciais no handler e re-semeie CSPRNG no after-restore.
- **reliability**: `SnapStartNotReadyException` e o estado `Pending` do container exigem retry com backoff e operações idempotentes no chamador.
- **performance**: Meça `Restore Duration` + `Duration` e valide contra `IntegrationLatency` na borda; a melhora só é real em p99,9.

> **Nota de curadoria:** Eu ligaria isso, mas na terceira semana, não na primeira. A ordem que uso é sempre a mesma: primeiro instrumento o `Restore Duration` num ambiente de homologação com o alias real, depois audito a init caçando entropia, credenciais e conexões — é sempre nessa auditoria que aparece o achado desconfortável. Só então mexo em produção. Já vi um sistema de pagamento em que a init pré-computava um sufixo de chave de idempotência 'para economizar tempo'; sob concorrência provisionada isso nunca colidiu, porque cada ambiente tinha o seu. Com snapshot clonado, teria colidido, e o incidente não apareceria em latência — apareceria em transação duplicada, dois dias depois, no fechamento. Performance que você não consegue reconciliar não é performance, é dívida.

## Recomendação

Para APIs interativas e inferência empacotadas em imagem de container, com Java 11+, Python 3.12+ ou .NET 8+ sobre as imagens base da AWS, o SnapStart é agora a escolha padrão: entrega partida sub-segundo por cerca de um terço do custo mensal de uma unidade de concorrência provisionada equivalente, e o ponto de virada do custo de restauração está tão longe (~77 mil restaurações por mês, a 2 GB) que raramente importa. Reserve a concorrência provisionada para os endpoints cujo SLO não comporta a retomada, ou que dependem de `/tmp` acima de 512 MB. Não migre para .zip só para ganhar SnapStart — o tipo de pacote é imutável e o benefício não paga a troca de ARN. E antes de qualquer coisa, faça duas tarefas nada glamourosas: uma auditoria de unicidade na init e uma política automática de poda de versões publicadas. É nessas duas que o SnapStart é ganho ou perdido.

**Rating:** 8.5/10

## Referências

- [AWS What's New — Lambda SnapStart for container image functions (Sep 2, 2026)](https://aws.amazon.com/about-aws/whats-new/2026/07/aws-lambda-snapstart-container/)
- [AWS Lambda Developer Guide — Improving startup performance with Lambda SnapStart](https://docs.aws.amazon.com/lambda/latest/dg/snapstart.html)
- [AWS Lambda Developer Guide — Implementing SnapStart hooks for container images](https://docs.aws.amazon.com/lambda/latest/dg/snapstart-runtime-hooks-custom.html)
- [AWS Lambda Developer Guide — Activating and managing Lambda SnapStart](https://docs.aws.amazon.com/lambda/latest/dg/snapstart-activate.html)
- [AWS Lambda Developer Guide — Monitoring for Lambda SnapStart](https://docs.aws.amazon.com/lambda/latest/dg/snapstart-monitoring.html)
- [AWS Lambda Developer Guide — Create a Lambda function using a container image](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html)
- [AWS Lambda Pricing — SnapStart, provisioned concurrency and on-demand rates](https://aws.amazon.com/lambda/pricing/)
