3 pontos por GN⁺ 2023-12-08 | 1 comentários | Compartilhar no WhatsApp
  • FLAME busca uma expansão elástica granular ao envolver partes do código de aplicações existentes em funções e executá-las em cópias temporárias da aplicação, sem mover o código para um runtime separado
  • A abordagem tradicional de FaaS pode criar uma estrutura com HTTP, S3, SQS, API Gateway, encoder/decoder e orquestração de workflow até em tarefas como geração de miniaturas de vídeo
  • A biblioteca flame do Elixir delega a execução de funções a runners remotos com FLAME.call, e o FLAME.FlyBackend da Fly.io inicializa uma nova Fly Machine com a mesma imagem Docker e a conecta ao nó pai em cerca de 3 segundos
  • Em desenvolvimento e testes, usa-se o LocalBackend para executar no mesmo runtime; em produção, scale-to-zero e a manutenção breve de estado hot são ajustados com min, max, max_concurrency e idle_shutdown_after
  • Em vez de eliminar filas de tarefas, o FLAME separa garantia de durabilidade e execução elástica: a fila cuida de dispatch, commit e retry, enquanto trabalhos intensivos em CPU são processados dentro de chamadas FLAME

O problema que o padrão FLAME quer resolver

  • O autoescalonamento elástico reduz a carga de administrar servidores e promete custos proporcionais ao uso, mas usar FaaS pode aumentar junto a complexidade com filas separadas, armazenamento, glue code e mais dificuldade em desenvolvimento, testes e CI
  • FLAME significa Fleeting Lambda Application for Modular Execution e trata a aplicação inteira como uma lambda, executando apenas alguns trabalhos modulares em uma infraestrutura efêmera
  • O objetivo se resume em três pontos
    • reduzir a administração de servidores com fluxos de deploy existentes como fly deploy, git push heroku e kubectl
    • aplicar escala granular sob demanda apenas a partes específicas do código da aplicação
    • não reescrever a aplicação nem mover parte do código para um runtime proprietário

Movendo funções existentes com FLAME.call

  • O exemplo é a função generate_thumbnails, que converte um vídeo enviado em miniaturas com ffmpeg em uma aplicação Elixir
  • A função original cria um diretório temporário, executa o ffmpeg, salva as miniaturas geradas em armazenamento durável e registra as URLs no banco com Repo.insert_all
  • Como a transcodificação de vídeo intensiva em CPU pode parar o serviço inteiro em produção, em vez de mover todo o código para FaaS ou microserviços, o corpo da função é envolvido por FLAME.call(MyApp.FFMpegRunner, fn -> ... end)
  • FLAME.call recebe o nome do pool de runners e uma função, encontra ou inicializa uma nova cópia completa da aplicação e executa apenas aquela função
    • variáveis capturadas pelo closure, como a struct %Video{} e interval, são passadas automaticamente
    • depois de iniciar, o runner FLAME se conecta ao nó pai, recebe a função a ser executada e devolve o resultado ao chamador
    • dependendo da configuração, o runner espera mais trabalhos e depois desliga ao ficar idle, ou encerra imediatamente
  • Como a aplicação inteira é executada, incluindo a conexão com o banco, é possível usar Repo.insert_all exatamente como no código original

Complexidade de FaaS e diferença do FLAME

  • FaaS fornece componentes para resolver o problema, mas o FLAME está mais próximo de uma abordagem que reduz a própria camada de comunicação separada
  • No exemplo de miniaturas de vídeo, mesmo começando com uma simples AWS Lambda Function URL, a complexidade logo aparece
    • para transmitir as miniaturas de volta ao app por HTTP, é preciso escrever encoder e decoder customizados nos dois lados
    • se a transcodificação ou o upload do vídeo passar de 15 minutos, o hard timeout do Lambda obriga a dividir o vídeo em chunks e usar mais Lambdas
    • passam a ser necessários serviços adicionais como orquestração de workflow e SQS/S3
  • Implementações baseadas em FaaS normalmente criam os seguintes pontos de custo
    • disparar Lambda por HTTP endpoint, S3 e API Gateway
    • escrever uma Lambda sob medida para transcodificação de vídeo
    • salvar o resultado das miniaturas no SQS
    • escrever um consumer de SQS no lado da aplicação
    • salvar no banco e montar uma forma de reenviar o evento a assinantes ativos conectados a outras instâncias
  • O FLAME permite reutilizar o código interno da aplicação, o banco existente, PubSub e recursos da plataforma, reduzindo serviços separados e armazenamento para recuperar resultados

Backend da Fly.io e execução local

  • A biblioteca flame para Elixir é uma implementação do padrão FLAME e oferece por padrão LocalBackend e FlyBackend
  • O FLAME.FlyBackend inicializa uma nova cópia da aplicação em uma Machine na infraestrutura da Fly.io e consegue conectá-la ao nó pai em cerca de 3 segundos para receber trabalho
  • Como a Fly.io executa aplicações empacotadas como imagens Docker, ela solicita à API da Fly o boot de uma nova Machine com a mesma imagem do app atual
  • Na infraestrutura da Fly.io, os runners FLAME podem ser iniciados na mesma região do nó pai, reduzindo a latência entre pai e runner
  • O FLAME.FlyBackend tem, com documentação incluída, menos de 200 LOC, e sua única dependência de biblioteca é o cliente HTTP req
  • Em desenvolvimento e testes, o LocalBackend executa esse código dentro do runtime já existente no notebook ou no servidor de CI

Transferência de arquivos e simplificação de desenvolvimento e testes

  • O FLAME permite reutilizar lógica de negócio, configuração de banco, PubSub e recursos da plataforma sem escrever código fora da aplicação
  • Em Elixir, graças aos recursos distribuídos da Erlang VM, é possível enviar um stream de arquivo do nó pai para a aplicação FLAME remota
    • no nó pai, abre-se um stream do arquivo do caminho do vídeo
    • no filho FLAME, abre-se um stream de arquivo temporário e copia-se o stream do pai
    • depois, o ffmpeg usa o arquivo temporário do runner remoto como entrada para gerar as miniaturas
  • Nessa abordagem, não é preciso configurar separadamente S3 ou uma interface HTTP para entregar arquivos ao servidor FLAME
  • Isso reduz deploys de serviços separados, gestão de endpoints, recuperação de resultados via S3/SQS e a configuração de dependências para desenvolvimento, testes e CI

FLAME fora do Elixir

  • O Elixir combina bem com o modelo FLAME por oferecer supervisão de processos e mensageria distribuída, mas linguagens com primitivas de concorrência razoáveis também podem usar esse padrão
  • Um exemplo de prova de conceito em JavaScript executa funções da aplicação em outra máquina Fly Machine: fly-run-this-function-on-another-machine
  • O fluxo geral de uma chamada FLAME em JavaScript funciona movendo a parte de execução do módulo para um novo arquivo e executando-a em um pool de runners
  • Se os argumentos forem serializáveis em JSON, o fluxo geral fica semelhante ao exemplo em Elixir, com o código da aplicação rodando em instâncias efêmeras
  • Uma biblioteca FLAME completa precisaria lidar com
    • lógica de scale-up e scale-down do pool elástico
    • gerenciamento do pool considerando hot startup e cold startup
    • monitoramento de runners remotos para evitar recursos órfãos
    • uma forma de manter os deploys atualizados

Relação com filas de tarefas em background

  • O FLAME também funciona dentro de processadores de tarefas em background, mas há alguma sobreposição de papel com filas de trabalho
  • Filas de tarefas normalmente são usadas quando é preciso garantia de durabilidade, e podem ser ajustadas para processar mais tarefas conforme a carga varia
  • Trabalho durável e execução elástica são preocupações separadas
    • se a fila for usada apenas para offload de execução, será necessário glue code para colocar dados na tarefa e devolver o resultado ao chamador ou ao dispositivo do usuário
    • se for necessário garantir a geração de miniaturas após o upload do vídeo, a fila pode cuidar de dispatch, commit e retry
    • a transcodificação real pode ser executada por uma chamada FLAME dentro do job, separando durabilidade de execução escalável
  • Trabalhos que não exigem durabilidade, como pré-visualização de vídeo antes de salvar ou execução de modelo de ML quando o usuário já saiu do app, podem não combinar com uma arquitetura que grava tarefas em armazenamento durável

Pool de runners para expansão elástica

  • A implementação FLAME em Elixir define um pool elástico de runners para oferecer scale-to-zero e limite de concorrência ao mesmo tempo
  • A configuração de exemplo adiciona FLAME.Pool ao start/2 da aplicação
    • min: 0 permite scale-to-zero
    • max: 10 inicializa no máximo 10 runners
    • max_concurrency: 5 permite 5 trabalhos de ffmpeg por runner
    • idle_shutdown_after: 30_000 faz idle down após 30 segundos sem chamadas
  • O servidor web Phoenix é iniciado de forma condicional dependendo da existência do pai FLAME
    • em runners FLAME que não processam tráfego web, não há necessidade de iniciar o servidor web
    • MyApp.Repo e outros componentes como banco devem permanecer, pois são usados dentro do runner FLAME
  • Com min: 1, é possível manter pelo menos um runner de ffmpeg em estado hot desde o início da aplicação

Posicionando processos com estado

  • Partes com estado em aplicações Elixir são estruturadas em torno de primitivas de processos leves com mailbox de mensagens
  • FLAME.call e FLAME.cast são adequados para código relativamente stateless, enquanto FLAME.place_child permite iniciar uma especificação de processo existente em um runner FLAME em vez de localmente
  • FLAME.place_child pode ser usado nos mesmos pontos em que se usaria interfaces como Task.Supervisor.start_child e DynamicSupervisor.start_child
  • No exemplo de geração de miniaturas durante upload com LiveView, chunks do upload são enviados ao processo ThumbnailGenerator, que se comunica com o ffmpeg
    • ao encontrar um delimitador PNG no stdout do ffmpeg, ele envia uma mensagem de imagem ao processo LiveView
    • o LiveView recebe a mensagem em handle_info e adiciona a nova imagem à UI
  • Ao trocar uma chamada existente DynamicSupervisor.start_child(@sup, spec) por FLAME.place_child(Thumbs.FFMpegRunner, spec), o processo ThumbnailGenerator passa a rodar em um runner FLAME
  • Como processos podem trocar mensagens independentemente de onde estejam, se o processo terminar por fim do upload ou fechamento da aba do navegador, o servidor FLAME detecta o encerramento e faz idle down quando não houver mais trabalho

Monitoramento remoto e tratamento de falhas

  • Infraestrutura efêmera precisa de failsafes para evitar recursos órfãos
  • Quando o pai inicia um runner, o runner deve desligar sozinho ao ficar idle e também fazer failsafe shutdown se não conseguir mais se comunicar com o nó pai
  • Quando um novo deploy substitui o pai, o runner também deve encerrar para garantir que o cluster continue executando o mesmo código
  • Chamadores ativos que aguardam o resultado de um runner precisam considerar a possibilidade de ele ser desligado por qualquer motivo
  • As primitivas fornecidas pela Erlang VM simplificam essa implementação
    • monitoramento e supervisão de processos locais e remotos
    • node monitoring para detectar nós entrando e saindo
    • fluxo de shutdown que controla a ordem de início e término da aplicação para dar tempo de runners ativos concluírem o trabalho durante um novo deploy
  • Os detalhes internos da implementação ficarão para outro texto, e por enquanto é possível examinar o código-fonte do flame

Estado atual e próximos passos

  • A biblioteca FLAME para Elixir ainda está em estágio inicial, mas já pode ser experimentada agora
  • Estão previstos no futuro técnicas mais avançadas de crescimento de pool e uma análise aprofundada da implementação em Elixir
  • Também é possível discutir implementações do padrão FLAME em outras linguagens

1 comentários

 
GN⁺ 2023-12-08
Opiniões no Hacker News
  • Depois de passar, nos últimos 4 anos, pela dor e pela complexidade de uma app composta por mais de 100 funções Lambda, acho que este texto acerta em cheio nas desvantagens da arquitetura serverless FaaS
    Quando se começa, essas desvantagens não ficam muito visíveis. Pelo contrário: se o uso é baixo, as vantagens são claras — é praticamente grátis e quase não exige manutenção
    Só mais tarde, quando os workflows de Lambda ficam todos entrelaçados e cada vez mais rígidos por causa das interdependências, você se arrepende de não ter ido de monólito e gastado algumas centenas de dólares a mais para gerenciar por conta própria. Hoje em dia, em lugares como a fly.io, talvez custe até menos
    Fico curioso para saber como fica se você não usa Elixir

    • Tenho visto repetidamente o padrão de que desenvolvedores motivados conseguem fazer quase qualquer processo ou arquitetura funcionar por uns 18 meses, e agora isso parece quase uma regra
      Quando a situação começa a piorar, também costuma ser a hora de procurar um novo emprego. Especialmente se, cerca de um ano depois de entrar, a pessoa já se arrepende do processo que ela mesma introduziu. Vi isso várias vezes com chefes ruins, “testes unitários” bagunçados, Scrum etc.
      Só não sei se as pessoas identificam claramente a causa do desconforto que sentem no trabalho, ou se simplesmente encaram como um vago “está na hora de sair”. Quando eu tentava dar nome ao que era incômodo, recebia muita resistência; depois de ler Good to Great, passei a gastar muito menos energia emocional com isso. Ninguém quer dizer: “ah, isso é consequência das minhas ações”
      As pessoas que efetivamente criaram o sistema estilo Rube Goldberg que eu mantenho foram as primeiras a ir embora. O capitão daquele navio perguntou a um colega se deveríamos abrir o código do nosso engine, e o colega respondeu que ninguém iria querer usar um sistema que reinventava, de forma pior, uma roda que já existia. Essa pessoa saiu por vontade própria em 3–4 meses
    • Estamos construindo algo que deve permitir resolver esse problema em qualquer linguagem. No momento há um SDK TypeScript, e Java também está em desenvolvimento
      Se você puder escrever código comum orientado a serviços em que Lambdas chamam umas às outras, não precisa dividir a aplicação em centenas de pedaços de curta duração. Mas isso é inviável se você tiver de pagar também pelo tempo de espera. Se a Lambda pudesse pausar a execução enquanto aguarda E/S, o problema estaria resolvido. Por isso acho que execução durável (durable execution) pode ser a resposta
      Nas últimas semanas eu estava escrevendo um artigo mostrando isso: https://restate.dev/blog/suspendable-functions-make-lambda-t...
    • Uma seção do texto também tratou de FLAME fora de Elixir. Em resumo, é um padrão geralmente aplicável a linguagens que tenham um modelo de concorrência razoável
      Seria difícil obter toda a usabilidade que Elixir dá de graça, como funções com serialização de variáveis capturadas, mas em linguagens como JavaScript acho que dá para chegar a uns 90% movendo a parte de execução do módulo para um novo arquivo, em vez de envolvê-la em uma closure
      Quem implementar uma biblioteca FLAME também terá de escrever as partes de pooling, monitoramento e comunicação remota. Elixir ganha muita coisa de graça em mensageria distribuída e monitoramento. Os recursos relacionados a posicionamento de processos também são, na prática, específicos de Elixir
    • Com apenas 12 Lambdas já era difícil de suportar. A app original foi feita por alguém que não pensou muito em manutenção ou deploy, e o código estava copiado e colado por toda parte
      No fim migramos para um monólito Lambda gordo, em que uma única Lambda tratava vários endpoints
    • Se você não se importa muito com cold start, ou consegue gerenciá-lo, dá para criar monólitos também em Lambda
      Dito de outro modo, dá para ir de monólito e ainda minimizar os custos na AWS. Não é uma escolha de um ou outro
      Hoje estou usando asp.net, e até uma app bem grande, implantada como ready-to-run com um modelo EF otimizado, inicia relativamente rápido
  • Sou o autor. Fico feliz por finalmente publicar isto e vou responder se houver perguntas. Espero que algumas pessoas se sintam suficientemente motivadas a implementar o padrão FLAME em JavaScript, Go e outras linguagens

    • Parece bom. Espero que a Microsoft preste atenção. O Azure Functions é complexo demais em configuração de segurança e deploy, e parte de premissas estranhas sobre que tipo de código você quer executar
    • A implementação mais natural provavelmente seria algo como vert.x na JVM
      Ele já tem mecanismos para lidar com escalonamento reativo, Future e execução assíncrona por meio de corrotinas sobre um event bus, além de dar suporte à serialização e distribuição de dados por todo o cluster. Como também há clientes de event bus para várias linguagens populares, seria possível criar aplicações misturando várias linguagens
    • Seria melhor reduzir um pouco o exagero
      Quando o problema é apresentado como “um destino pior que a morte” e a solução parece tão fácil e indolor que qualquer outra abordagem faz você parecer idiota, textos assim tendem a ser descartados como puro texto de vendas. É o estilo de vendedor de panaceia
      O início do texto identifica bem o problema em si; se houvesse uma comparação objetiva muito menos emocional, acho que as pessoas curiosas por uma abordagem melhor teriam menos resistência
      O conteúdo substantivo do texto foi interessante, mas a forma de apresentação me afastou. Espero que você encare isso como feedback construtivo
    • O texto e o vídeo são bons, e o conceito também é muito interessante. Estou ansioso por uma implementação em JavaScript, mas não parece fácil
      E fiquei com um pouquinho de culpa por ter reservado ffmpeg.fly.dev antes
    • Fico me perguntando em que isso é fundamentalmente diferente da forma como o Sidekiq instancia uma base de código Rails para executar jobs em segundo plano
  • Achei interessante a parte: “imagine simplesmente envolver qualquer trecho do código da app existente em uma função, fazê-lo escalar automaticamente, e esse bloco de código ser executado em uma cópia temporária da app”
    Parece o que o fork faz, mas criado para serverless. Excelente trabalho

  • Alguns anos atrás, usei um serviço que fazia basicamente isso. PiCloud infelizmente foi absorvido pelo Dropbox, mas antes disso tinha exatamente esse modelo de espalhar tarefas de forma transparente para workers. O código era empacotado e executado nos workers
    O exemplo está aqui. Dá para ver que é exatamente o mesmo modelo: https://github.com/picloud/basic-examples/blob/master/exampl...
    Nunca usei Elixir, mas usei Erlang décadas atrás, e o BEAM não parece ter mudado fundamentalmente muita coisa. Como isso é uma parte central do design, imagino que seja muito mais adequado para esse tipo de trabalho. Ainda assim, não existe almoço grátis completo: acho que há a possibilidade de o processo principal morrer enquanto está esperando

  • Concordo com o argumento geral. Em https://www.windmill.dev adotamos uma abordagem diferente: vemos a unidade de abstração no nível do código-fonte, não no nível do contêiner
    Fazemos o parsing da função main e dos imports para extrair argumentos e dependências e então executamos o código como está no runtime desejado (TypeScript, Python, Go, Bash). O segredo principal é gerenciar o cache de forma eficiente para que os workers permaneçam sempre quentes, independentemente dos imports
    Essa abordagem não é tão integrada à codebase quanto o FLAME, mas o público-alvo é diferente. Nossos usuários criam do zero workflows complexos, tarefas cron ou scripts pontuais com UI gerada automaticamente
    No FLAME, parece que todo o contexto é capturado em snapshot e restaurado novamente na VM de destino. Outra abordagem é introduzir uma sintaxe para especificar qual contexto é necessário e qual não é, carregando apenas o mínimo. Estamos explorando isso no momento para integrar melhor codebases existentes ao Windmill e não depender de chamadas HTTP

    • O que realmente acontece no FLAME não é exatamente isso. O FLAME apenas chama funções em nós remotos usando o recurso de clusterização nativo do BEAM
      Nesse processo, o envio apenas do contexto necessário é tratado implicitamente. O texto também explica assim: “FLAME.call recebe o nome de um pool de runners e uma função. Em seguida, encontra ou inicializa uma nova cópia da aplicação inteira e executa a função lá. As variáveis capturadas pela função como closure, por exemplo a struct %Video{} e interval, são enviadas automaticamente junto”
    • Entrou um w a mais. Para quem estiver procurando, a URL é esta: https://www.windmill.dev/
      Gosto do objetivo do projeto. Realmente espero que o Windmill se torne uma alternativa open source melhor ao Retool/Airtable
  • Gosto do fato de que “no FLAME, os runners de desenvolvimento e teste simplesmente rodam no backend local”. Serverless com uma experiência de desenvolvimento local decente é ótimo

    • Exato. Um dos motivos pelos quais não gosto de serverless é que, comparado a rodar um monólito, a experiência de desenvolvimento local é muito pior
  • Se “em seguida ele encontra ou inicializa uma nova cópia da aplicação inteira e essa função é executada lá”, então a cada Flame.call ele inicia de novo todo o processo da app e copia o contexto de execução para dentro?
    Do ponto de vista de escalabilidade, é uma solução muito simples, mas parece que também teria desvantagens
    Se o tempo de inicialização da app aumentar em 10 ms, isso acrescentaria 10 ms a todos os pontos de Flame.call da aplicação, e imagino que o mesmo valha para memória
    Parece que seria preciso considerar essas preocupações ao usar esse sistema

    • O FLAME.Pool mais adiante no texto trata dessa parte. Os runners são colocados em pool e mantidos quentes por um tempo configurável, depois descem quando ficam ociosos
      Sob carga, o pool já estará aquecido, então praticamente não se paga o custo de cold start. Além disso, estamos adicionando técnicas mais sofisticadas de crescimento de pool à biblioteca Elixir, para evitar também a situação de encontrar um runner cheio e ter que fazer um novo cold start
      Em runners quentes, o único overhead é a latência entre pai e filho. Como devem estar no mesmo datacenter, deve ser 1 ms ou menos
  • Excelente. Parece uma versão específica para Elixir bem leve do que criamos em https://www.inngest.com/
    Ambos são parecidos no sentido de envolver código existente com alguma coisa para que ele possa ser usado em funções serverless e, essencialmente, chamado via RPC remoto
    Esse tipo de código frequentemente roda como uma sequência de etapas imperativas. Cada etapa pode ser executada em série ou em paralelo como uma Lambda adicional. Mas há estado implícito capturado em variáveis entre as etapas. Por causa disso, a função vira um workflow. No modelo da Inngest, capturamos esse estado e o injetamos de volta na função para torná-la durável
    Em termos de durabilidade, esse tipo de processo deve se basear em uma fila. O bom desse modelo é que filas são baratas. Se você torna uma fila tão barata quanto uma linha de código, tudo fica fácil. Qualquer desenvolvedor consegue escrever código confiável sem se preocupar com infraestrutura
    Monitoramento e observabilidade também são importantes. Dead letter queues são realmente horríveis, e é preciso conseguir gerenciar e reexecutar funções ou etapas que falham
    Também há diferenças entre FLAME e Inngest. A Inngest é baseada em filas, orientada a eventos e pode ser disponibilizada via HTTP em qualquer linguagem. Como a Inngest armazena o estado externamente, você pode escrever um workflow em Elixir, depois reescrevê-lo em TypeScript e fazer redeploy, e funções em execução podem ser migradas em tempo real entre linguagens de backend, de forma semelhante ao CRIU
    Com uma abordagem orientada a eventos, também é possível fazer controle de fluxo. Debounce, batch, throttling e fan-out podem ser tratados em qualquer runtime ou linguagem. Por exemplo, uma app Elixir no Fly pode enviar um evento para executar uma função em TypeScript + Lambda
    Estou curioso para ver para onde o FLAME vai. Vejo pontos em que os objetivos são parecidos

    • A Inngest parece um serviço interessante. O texto também tratou de processadores de tarefas, durabilidade e retentativas
      Quando preciso de durabilidade, retentativas e workflows em Elixir, normalmente uso Oban, e aqui continuarei fazendo isso. Um job do Oban chamará o FLAME para lidar com a execução elástica
  • Este é um dos motivos pelos quais eu realmente detesto a capitalização de títulos em estilo americano que o HN impõe. Serverless parece significar repensar a empresa Serverless.com, com S maiúsculo, e não repensar o princípio serverless, com s minúsculo.
    Além disso, eu gostaria que alguém repensasse Serverless.

    • Acho que a capitalização não é algo imposto pelo HN, mas sim definido por quem publica o texto.
      Não sei se você viu https://sst.dev/, mas parece que eles fizeram exatamente isso. Por exemplo, com Live Lambda Development, não é preciso subir o código para a nuvem e esperar o deploy, reduzindo bastante o ciclo de feedback e tornando o desenvolvimento local bem mais fácil.
  • A ideia é bem legal e a API também é excelente.
    Na parte que diz que “tarefas CPU-bound, como transcodificação de vídeo, podem rapidamente paralisar um serviço inteiro em produção”, não bastaria simplesmente fazer autoescalonamento baseado em CPU?

    • Tentei abordar essa ideia no começo do texto. O problema dessa abordagem é que ela escala no nível operacional errado.
      Para lidar com uma tarefa específica que está pegando fogo, você acaba escalando a aplicação inteira, incluindo o servidor web. O que queremos, e o motivo pelo qual buscamos FaaS, é escala elástica granular. A ideia aqui é permitir esse tipo de escalonamento granular para o código existente da aplicação, em vez de sair apertando botões de escalar servidor web ou worker e torcer para dar certo.
    • Sim e não. O restante da carga de trabalho pode não precisar de tanta CPU. Talvez só uma ou duas cargas precisem desse desempenho, e você queira garantir que essa tarefa não seja preterida por outras coisas.
      Ou talvez precise de GPU.
      O serviço principal pode funcionar bem com 1 ou 2 servidores, mas uma tarefa que acontece talvez uma vez por dia pode precisar escalar para dezenas, centenas ou milhares de máquinas quando necessário.