Voltar para o blog

Temporal no Cloud Run: arquitetura e critérios para workers sob demanda

Uma plataforma de integração pode passar horas aguardando arquivos de parceiros e, depois, receber milhares de operações para validar e persistir. Manter capacidade máxima continuamente parece desperdício; desligar todos os consumidores, porém, exige responder a uma pergunta: quem os iniciará quando houver trabalho?

No Temporal, workers tradicionais buscam tarefas por long polling. Eles não recebem automaticamente uma chamada HTTP para cada tarefa. Portanto, executar o container no Cloud Run não basta para obter escalabilidade sob demanda: é necessário escolher o modelo de execução e o mecanismo de ativação. (docs.temporal.io)

A recomendação central deste artigo é separar durabilidade, processamento e controle de capacidade. Avalie scale-to-zero somente depois de demonstrar como a capacidade volta, inclusive após timers, retries e falhas.

1. O que permanece quando um worker desaparece

Um Workflow representa a orquestração. Seu histórico de eventos permite reconstruir o estado por replay, executando código determinístico. Chamadas externas pertencem normalmente às Activities, não ao código determinístico do Workflow. A vida de uma execução pode ultrapassar a de qualquer container. (docs.temporal.io)

Os componentes relevantes são:

  • Temporal Service: mantém o estado durável e coordena a execução; pode ser Temporal Cloud ou uma instalação administrada pela equipe.
  • Worker: processo da aplicação, construído com um Software Development Kit, ou SDK, que executa código registrado.
  • Task Queue: mecanismo de distribuição de tarefas, consumidas pelos workers.
  • Namespace: escopo lógico de organização das execuções.

Hospedar workers não significa hospedar o Temporal Service. Este artigo pressupõe que o serviço durável já existe fora dos containers de processamento. (github.com)

Activities podem ter novas tentativas. Por isso, efeitos externos devem ser idempotentes: repetir uma operação com a mesma chave de negócio não deve duplicar uma cobrança ou importação. Heartbeats podem transportar checkpoints, mas o código precisa ler esses dados e implementar a retomada; não há restauração automática da memória da Activity. (docs.temporal.io)

2. Services, Jobs e Worker Pools não são equivalentes

Cloud Run Services

Services são uma opção para aplicações HTTP. Para manter polling fora de requisições, utilize cobrança baseada em instância, que mantém CPU alocada durante a vida da instância. Apenas configurar instâncias mínimas não substitui essa escolha. (docs.cloud.google.com)

O backlog do Temporal não é um gatilho nativo do autoscaler HTTP. Um Service em zero precisa de uma requisição para voltar a executar; utilização de CPU não existe enquanto não há instância. Para um worker continuamente disponível nesse modelo, configure um mínimo positivo e trate separadamente a expansão de capacidade. (docs.cloud.google.com)

Também será necessário cumprir o contrato HTTP do Service, escutando na porta configurada. Um endpoint artificial não transforma a fila Temporal em tráfego HTTP nem resolve o ciclo de ativação. (docs.cloud.google.com)

Cloud Run Jobs

Jobs são adequados para execução finita. Cada task possui timeout; a documentação permite até 168 horas para tasks sem GPU, mas isso não equivale a um processo permanente. (docs.cloud.google.com)

Minha recomendação é usar Jobs apenas quando houver um lote ou orçamento de execução explícito. Um worker com polling infinito não sabe que terminou o lote. Defina quando parar de buscar tarefas, como concluir ou interromper trabalho e quem executará o próximo Job. Não confunda uma task do Cloud Run com uma Task do Temporal.

Cloud Run Worker Pools

Worker Pools são a opção mais direta para processos de consumo contínuo. O número de instâncias pode ser configurado manualmente, inclusive zero, ou ajustado por um controlador externo. Criar um pool não instala, por si só, um autoscaler para filas Temporal. Instâncias solicitadas continuam sendo cobradas mesmo sem trabalho. (docs.cloud.google.com)

Integração Temporal Serverless Workers

Na documentação consultada para este artigo, o suporte Temporal a Cloud Run está em Public Preview. Um Worker Controller Instance, ou WCI, ajusta Worker Pools pela API administrativa do Cloud Run; não invoca um container por tarefa. A integração exige Worker Versioning. Use um pool por versão e não compartilhe suas filas com workers permanentes externos ao modelo de controle. (docs.temporal.io)

Para adotá-la, valide disponibilidade, versões compatíveis, permissões e suporte contratual. O procedimento oficial inclui configurar identidades, criar o pool, associar uma Worker Deployment Version e promover a versão apropriada. Não basta acrescentar uma variável de ambiente ao worker. (docs.temporal.io)

3. Arquitetura proposta

Para uma plataforma corporativa de importação, proponho separar uma camada de orquestração responsiva de uma camada elástica de Activities:

Cliente → API → Temporal Service
                    ↓
            Fila de Workflows
                    ↑ polling
          Workers de orquestração
                    ↓ agenda Activities
             Fila de importação
                    ↑ polling
          Worker Pool de Activities
                    ↓
        Object Storage + PostgreSQL

Controlador → API administrativa do Cloud Run → capacidade do pool

Essa separação utiliza a capacidade de direcionar Activities para uma Task Queue diferente da utilizada pelo Workflow. Workers conectam-se ao Temporal; não precisam publicar um endpoint de entrada para receber tarefas. (docs.temporal.io)

Decisão proposta: mantenha capacidade de orquestração positiva quando consultas e continuidade rápida forem importantes. Permita zero na fila de importação somente se a latência adicional for aceitável. Configure cada fila conforme sua criticidade, em vez de aplicar um único perfil a toda a plataforma.

Existem dois caminhos de controle:

  1. Integração Temporal: o WCI participa do controle da capacidade e pode reativar workers quando há tarefas. Esse é um mecanismo do Temporal, não do autoscaler HTTP do Cloud Run. (docs.temporal.io)
  2. Controlador próprio: um componente externo lê sinais de demanda e altera a quantidade de instâncias. O Cloud Run documenta esse padrão de escalabilidade externa; adaptar os sinais e a reconciliação ao Temporal é responsabilidade da equipe. (docs.cloud.google.com)

No segundo caminho, proponho os seguintes requisitos de projeto:

  • executar o controlador fora da capacidade que ele pode reduzir a zero;
  • reconciliar periodicamente, não depender apenas de um evento de chegada;
  • verificar demanda depois de timers, retries e conclusões de Activities;
  • aplicar limites, cooldown e tratamento de indisponibilidade das métricas;
  • manter apenas um responsável pelo número desejado de instâncias;
  • testar a corrida entre a observação de fila vazia e a chegada de trabalho.

Esses requisitos são critérios de aceitação arquitetural, não recursos automaticamente fornecidos pelo exemplo abaixo.

4. Configuração inicial de um Worker Pool

O exemplo seguinte demonstra capacidade fixa, sem WCI e sem scale-to-zero automático. Pressupõe uma imagem já publicada cujo entrypoint inicia o worker e lê as variáveis indicadas. A imagem deve configurar TLS, autenticação e registro das Activities no SDK; nomes de variáveis, isoladamente, não implementam isso.

Prepare projeto, região, Service Account, repositório e secret. Substitua os valores ilustrativos:

export PROJECT_ID='seu-projeto'
export REGION='us-central1'
export POOL='temporal-import-v1'
export IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/workers/import-worker:1.0.0"
export RUNNER_SA="temporal-worker@${PROJECT_ID}.iam.gserviceaccount.com"
export TEMPORAL_ADDRESS='seu-endpoint-temporal:7233'
export TEMPORAL_NAMESPACE='seu-namespace'

gcloud run worker-pools deploy "$POOL" \
  --project="$PROJECT_ID" \
  --region="$REGION" \
  --image="$IMAGE" \
  --instances=2 \
  --cpu=1 \
  --memory=1Gi \
  --service-account="$RUNNER_SA" \
  --set-env-vars="TEMPORAL_ADDRESS=${TEMPORAL_ADDRESS},TEMPORAL_NAMESPACE=${TEMPORAL_NAMESPACE},TEMPORAL_TASK_QUEUE=imports" \
  --set-secrets='TEMPORAL_API_KEY=temporal-api-key:1'

O comando usa opções existentes do Google Cloud CLI. Duas instâncias, uma CPU e 1 GiB são parâmetros didáticos, não dimensionamento universal. Em produção, prefira imagem identificada por digest e ajuste recursos mediante testes. (docs.cloud.google.com)

O secret precisa existir, e a identidade de execução deve poder acessá-lo. A versão numérica torna o rollout de credenciais explícito; faça rotação com implantação controlada, sem incluir o valor da chave no repositório. (docs.cloud.google.com)

Não reduza esse pool a zero antes de implementar e testar o mecanismo de retorno. Para migrar ao WCI, siga o procedimento de integração e retire controladores concorrentes; não trate esse comando isolado como instalação de Serverless Workers. (docs.temporal.io)

5. Critérios para executar sob demanda

Use a tabela como checklist de decisão, não como garantia de desempenho:

CritérioAceitar zero quandoManter capacidade quando
LatênciaHá orçamento para ativação e pollingO início precisa ser previsível
Perfil de demandaExistem intervalos ociosos relevantesA utilização é contínua
Efeitos externosOperações são idempotentesDuplicações ainda não são controladas
RecuperaçãoRetry e checkpoint foram testadosInterrupções causam retrabalho excessivo
ControleHá reativação independente e monitoradaNinguém é responsável por reativar
GovernançaPreview ou controlador próprio são aceitosO suporte exigido não está disponível

Para estimativa inicial, proponho calcular:

instâncias ≈ teto((taxa de chegada × duração média) /
                  (concorrência por instância × utilização-alvo))

É uma aproximação de planejamento, não a fórmula contratual do produto. Exemplo hipotético: 10 Activities/s, duração média de 2 s, 5 execuções simultâneas por instância e utilização-alvo de 70% sugerem 6 instâncias. Valide com distribuição de durações, retries e limites do banco.

Slots de execução, pollers e instâncias são dimensões diferentes. Aumentar pollers não cria capacidade de CPU nem resolve saturação de dependências. Meça a capacidade útil por worker e configure concorrência no SDK. (docs.temporal.io)

6. Falhas, timeouts e encerramento

Separe os limites das Activities:

  • Start-to-Close: duração máxima de uma tentativa.
  • Schedule-to-Close: orçamento total, incluindo espera e novas tentativas.
  • Heartbeat Timeout: detecção de ausência de progresso informado.
  • Schedule-to-Start: tempo aguardando um worker; não é um substituto para corrigir capacidade insuficiente.

Inclua o tempo de ativação no orçamento de espera. Timeouts agressivos podem transformar cold starts normais em falhas. (docs.temporal.io)

O contrato documentado de Worker Pools prevê SIGTERM e uma janela de 10 segundos antes de SIGKILL. Uma configuração de graceful shutdown maior no SDK não amplia essa janela. (docs.cloud.google.com)

Ao receber encerramento, inicie o shutdown do worker para interromper polling e tratar tarefas em andamento conforme o SDK. Valide esse comportamento com sinais reais, não apenas encerrando a aplicação em ambiente local. (docs.temporal.io)

No desenho proposto, Activities longas devem persistir resultados parciais e tolerar repetição. Heartbeat é um mecanismo de progresso, não uma transação distribuída com PostgreSQL. Minha recomendação é combinar chave idempotente, restrições de unicidade e checkpoints com a semântica do destino. (docs.temporal.io)

7. Exemplo do dia a dia

Problema: uma equipe importa catálogos de parceiros. Há longos intervalos sem arquivos e picos concentrados. O objetivo é reduzir capacidade ociosa sem perder rastreabilidade.

Solução proposta: a API inicia um Workflow por importação. O Workflow agenda Activities em uma fila exclusiva. Workers elásticos processam partes do arquivo e persistem resultados com uma chave composta por importação e item.

Implementação proposta: mantenha arquivos fora do histórico, transportando referências e identificadores; limite conexões ao PostgreSQL; registre checkpoints; reserve uma janela de tolerância para ativação. Em homologação, interrompa containers após uma escrita e antes da confirmação da Activity.

Resultado esperado, não medido: retomada sem duplicação de efeitos, rastreabilidade da execução e menor tempo de capacidade ociosa. Só apresente economia ou melhoria de latência após medições comparáveis.

8. Kubernetes como alternativa de operação

Se a equipe precisa controlar drenagem, distribuição por zonas e políticas de execução com maior precisão, um Deployment Kubernetes é uma alternativa. O manifesto abaixo é uma base ilustrativa completa para os objetos mostrados; não instala Temporal nem cria credenciais. (kubernetes.io)

Pré-requisitos da imagem: worker configurado por ambiente, TLS habilitado e servidor de saúde em 8080. Os caminhos de saúde abaixo são um contrato da aplicação, não endpoints nativos do Temporal. O Secret temporal-credentials, com a chave api-key, deve ser provisionado previamente.

apiVersion: v1
kind: Namespace
metadata:
  name: temporal-workers
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: import-worker
  namespace: temporal-workers
automountServiceAccountToken: false
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: import-worker
  namespace: temporal-workers
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  selector:
    matchLabels:
      app: import-worker
  template:
    metadata:
      labels:
        app: import-worker
    spec:
      serviceAccountName: import-worker
      terminationGracePeriodSeconds: 60
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        runAsGroup: 10001
        seccompProfile:
          type: RuntimeDefault
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: import-worker
      containers:
        - name: worker
          image: us-central1-docker.pkg.dev/PROJECT_ID/workers/import-worker:1.0.0
          ports:
            - name: health
              containerPort: 8080
          env:
            - name: TEMPORAL_ADDRESS
              value: "REPLACE_WITH_TEMPORAL_ENDPOINT:7233"
            - name: TEMPORAL_NAMESPACE
              value: "REPLACE_WITH_NAMESPACE"
            - name: TEMPORAL_TASK_QUEUE
              value: "imports"
            - name: TEMPORAL_API_KEY
              valueFrom:
                secretKeyRef:
                  name: temporal-credentials
                  key: api-key
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: "1"
              memory: 1Gi
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: [ALL]
          startupProbe:
            httpGet:
              path: /health/startup
              port: health
            periodSeconds: 5
            failureThreshold: 24
          readinessProbe:
            httpGet:
              path: /health/ready
              port: health
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /health/live
              port: health
            periodSeconds: 10
          volumeMounts:
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: tmp
          emptyDir:
            sizeLimit: 256Mi
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: import-worker
  namespace: temporal-workers
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: import-worker

Adapte imagem, endpoints, secret e recursos. requests influenciam agendamento; limites restringem consumo. CPU insuficiente pode aumentar latência, e memória subdimensionada pode provocar encerramentos. (kubernetes.io)

A distribuição por zonas depende dos nós elegíveis. DoNotSchedule pode deixar Pods pendentes se não houver capacidade compatível. O PodDisruptionBudget reduz interrupções voluntárias simultâneas, mas não impede falhas de zona. (kubernetes.io)

A probe de liveness deve avaliar saúde local, evitando reinícios em cascata quando o Temporal estiver temporariamente indisponível. Readiness não interrompe polling automaticamente: esse controle precisa existir na aplicação. Não é necessário criar um Service Kubernetes para consumir a fila. (kubernetes.io)

9. Segurança, observabilidade e manutenção

Segurança

Separe a identidade que executa Activities daquela que modifica capacidade. Na integração, a identidade de controle precisa de permissões como run.workerPools.get e run.workerPools.update; a identidade de execução precisa acessar seus secrets. Não entregue administração do projeto ao worker. (docs.temporal.io)

Configure Transport Layer Security, ou TLS, e autenticação suportada pelo endpoint Temporal. Reutilize conexões do client em vez de conectar a cada tarefa. (docs.temporal.io)

Como baseline de segurança, recomendo acesso mínimo a bancos e armazenamento, rotação ensaiada, isolamento de ambientes e ausência de credenciais nos logs. No Kubernetes, revise controle de acesso baseado em funções, ou RBAC, e políticas de rede segundo as dependências reais.

Observabilidade

Monitore latência schedule-to-start, backlog, capacidade de slots, falhas e duração das Activities. CPU baixa não demonstra saúde quando tarefas estão esperando. Correlacione métricas do worker com capacidade disponível e comportamento da fila. (docs.temporal.io)

Como painel operacional proposto, inclua:

  • tempo entre demanda e primeira tarefa processada;
  • instâncias desejadas versus observadas;
  • erros do controlador e idade de sua última reconciliação;
  • retries e duplicações evitadas por idempotência;
  • conexões de banco e saturação de dependências;
  • custo por importação concluída.

Defina um Service Level Objective, ou SLO, de início e conclusão do trabalho. Teste alertas também com zero workers: métricas produzidas apenas por processos vivos não bastam para verificar o mecanismo de ativação.

Upgrades e rollback

Faça replay de históricos representativos antes de alterar Workflows. Worker Versioning permite direcionar execuções por versão; rollback precisa respeitar compatibilidade, não apenas restaurar uma imagem. (docs.temporal.io)

Na integração Cloud Run, preserve pools de versões antigas enquanto houver execuções vinculadas. Não substitua silenciosamente a imagem de um pool associado a uma versão ativa. (docs.temporal.io)

No pipeline de integração e entrega contínuas, ou CI/CD, proponho testes de replay, interrupção, rotação, cold start e recuperação. Para Temporal autogerenciado, exija plano de backup e restauração da persistência; reiniciar workers não substitui recuperar o serviço durável.

10. Troubleshooting

SintomaPossível causaInvestigaçãoCorreção
Pool não cresceIAM ou associação incorretaInspecionar pool e versãoCorrigir permissões e configuração
Instâncias sem progressoEndpoint, secret ou namespace incorretoLogs de inicializaçãoCorrigir conexão e autenticação
WCI sem fila associadaWorker nunca fez polling na versãoInspecionar deploymentAlinhar build e registro
Activities interrompidas no scale-inInstância removida durante trabalhoCorrelacionar falhas e escalaCheckpoints, retries e estabilização

Essas verificações seguem o fluxo de diagnóstico da integração. (docs.temporal.io)

gcloud run worker-pools describe "$POOL" \
  --project="$PROJECT_ID" --region="$REGION" --format=yaml

kubectl get pods -n temporal-workers
kubectl describe deployment import-worker -n temporal-workers
kubectl logs deployment/import-worker -n temporal-workers
kubectl get events -n temporal-workers --sort-by=.metadata.creationTimestamp

Para um Pod que reiniciou, use kubectl logs NOME_DO_POD -n temporal-workers --previous. Não confunda rollout bem-sucedido com capacidade efetiva de processar tarefas.

11. Custos, desenvolvimento e alternativas

Inclua no modelo financeiro tempo de instâncias, Temporal, rede, armazenamento, logs e controle de escala. Cobrança baseada em instância considera sua vida útil; scale-to-zero não elimina custos dos demais componentes. Não use preços hipotéticos como orçamento. (cloud.google.com)

Minha recomendação de FinOps é comparar três experimentos: capacidade fixa pequena, capacidade mínima com expansão e zero com reativação. Meça custo por execução concluída, incluindo retries e trabalho repetido.

Em desenvolvimento, uma instância e observabilidade simplificada podem ser suficientes para aprender. Para produção, proponho gates adicionais: limites de capacidade, health checks, alertas, credenciais segregadas, restauração testada e rollback documentado. Produção não exige zero; exige comportamento previsível.

AlternativaAvaliação arquitetural proposta
Worker Pool fixoMenos lógica própria; aceita custo ocioso
Worker Pool com WCIControle integrado; avaliar Preview e versionamento
Controlador próprioFlexibilidade; maior responsabilidade operacional
KubernetesMaior controle; exige maturidade de plataforma
Job finitoBom para lotes com término explícito
Serviço HTTP simplesConsidere se a aplicação não precisa de orquestração durável

Evite scale-to-zero quando o custo de ativação comprometer o SLO, a carga for contínua ou a recuperação ainda não estiver validada. Evite também polling em CPU condicionada a requisições, secrets hardcoded, limites arbitrários e autoscalers disputando o mesmo recurso.

Resumo prático

  • Separe Temporal Service, workers e controlador de capacidade.
  • Escolha Worker Pools para consumo contínuo; não presuma que um Service HTTP observa filas Temporal.
  • Exija uma estratégia verificável de retorno do zero.
  • Trate interrupções, idempotência e checkpoints antes de otimizar custos.
  • Faça a decisão por fila, SLO e maturidade operacional.

Conclusão

Temporal no Cloud Run deve ser tratado como uma decisão de arquitetura de execução, não apenas de hospedagem de containers. A proposta de Platform Engineering é oferecer perfis padronizados — permanente, elástico e finito — com critérios explícitos de adoção. DevOps automatiza a entrega; SRE valida recuperação e objetivos de serviço; FinOps confirma se a elasticidade realmente melhora o custo por resultado. O melhor desenho é aquele cuja ativação, interrupção e recuperação a equipe consegue demonstrar.

Quer aplicar isso no seu ambiente?

Agende uma conversa de 30 minutos sem compromisso. Eu olho o seu cenário e digo por onde começar.

Agendar uma conversa

Receba os novos artigos por e-mail

Um e-mail quando sair artigo novo sobre infraestrutura, backend e IA. Sem spam, cancele quando quiser.