Decision tree: qual ferramenta para qual problema

TL;DR

A pergunta-chave é: qual o problema? CPU-bound em handler → Worker Thread (com pool em prod). Escalar HTTP além de single-thread → orquestrador (K8s/ECS); Cluster apenas se single-VM. Rodar comando externo → spawn / execFile (nunca exec com input do usuário). Spawn de processo Node isolado → fork. A maioria dos erros não é técnica — é escolher a ferramenta errada para o problema.


Diagrama

flowchart TD
    START(["`**Qual é o problema?**`"]):::question

    CPU{"`CPU-bound
    em handler/job?`"}:::decision
    HTTP{"`Escalar HTTP
    além de 1 thread?`"}:::decision
    EXT{"`Rodar processo
    externo?`"}:::decision
    ISO{"`Node filho
    isolado?`"}:::decision

    WT(["`**Worker Thread**
    + pool em prod`"]):::solution
    ORCH(["`**Orquestrador**
    1 pod por container`"]):::solution
    CLU(["`**Cluster / PM2**
    single-VM`"]):::solution
    SPAWN(["`**spawn / execFile**
    sem exec com input`"]):::solution
    FORK(["`**fork**
    isolamento OS`"]):::solution

    START --> CPU
    START --> HTTP
    START --> EXT
    START --> ISO

    CPU -->|sim| WT
    HTTP -->|orquestrador| ORCH
    HTTP -->|single-VM| CLU
    EXT -->|output grande ou input externo| SPAWN
    ISO -->|isolamento total| FORK
    ISO -->|CPU-bound puro| WT

    classDef question fill:#4A90D9,color:#fff,stroke:#2c6fad
    classDef decision fill:#F5A623,color:#000,stroke:#c47d0e
    classDef solution fill:#e8f4e8,color:#000,stroke:#4A90D9

O que é

A decision tree de paralelismo é um artefato de síntese que separa dois eixos que costumam ser confundidos:

  • O problema: CPU-bound? HTTP scaling? Comando externo? Isolamento de processo?
  • A ferramenta: Worker Thread? Cluster? spawn? execFile? fork?

Node tem três modelos de paralelismo nativos — Worker Threads (threads dentro do mesmo processo), Cluster (múltiplos processos compartilhando a mesma porta TCP), e child_process (processo externo completamente separado). Cada modelo resolve uma classe diferente de problema. Aplicar Worker Thread a um problema de escalonamento HTTP, ou Cluster a um problema CPU-bound em handler individual, não só não resolve como adiciona complexidade sem benefício.

Os três modelos não são gradações de poder — são soluções para problemas orthogonais:

ModeloFronteiraProblema que resolve
Worker ThreadThread (mesmo processo)CPU-bound dentro de um handler
ClusterProcesso (mesma porta TCP)HTTP scaling em single-VM
child_processProcesso (isolamento total)Comando externo ou Node filho isolado

Esta nota é puramente sintética: consolida os critérios de decisão das notas 01 a 10 em um único artefato de referência para consulta rápida e preparação de entrevistas.


Por que importa

Em entrevistas sênior e em code reviews, a pergunta raramente é “você conhece Worker Threads?“. A pergunta real é “dado esse problema específico, qual ferramenta você escolheria e por quê?“. A decisão errada é uma das fontes mais comuns de dívida técnica em código Node de produção:

  • Cluster para CPU-bound em handler: cria N réplicas do mesmo problema. Cada worker ainda bloqueia seu próprio event loop. O trabalho dentro de um request nunca é paralelizado — apenas multiplicado.
  • Worker Thread para problema de escalonamento HTTP: Workers não compartilham a porta TCP. O servidor continua aceitando conexões em uma única thread. A escala de HTTP exige múltiplos processos ou múltiplos pods, não múltiplas threads.
  • exec com input externo: a string passa por um shell. Um semicolon ou $() no input vira injeção de comando. Vulnerabilidade estrutural, não de implementação.
  • fork onde Worker Thread basta: fork cria um processo OS completo (~100ms de custo). Worker Thread é uma thread (~1-5ms). Para CPU-bound sem necessidade de isolamento total, Worker Thread é a ferramenta certa.

Conhecer a decision tree não é memorizar respostas — é ter o critério para chegar à resposta certa a partir do problema.

A sequência correta antes de qualquer ferramenta

A decision tree de ferramentas pressupõe que paralelismo já foi validado como necessário. Antes de chegar nela, a sequência de diagnóstico é:

  1. Medir: event loop lag via perf_hooks.monitorEventLoopDelay(), percentis de latência por endpoint (p50/p95/p99), CPU usage por thread.
  2. Identificar: o bottleneck é CPU-bound (lag alto, cálculo dominante) ou I/O-bound (lag baixo, espera de rede/banco)?
  3. Tentar alternativas: streaming, paginação, refatoração de algoritmo, API async em vez de sync, UV_THREADPOOL_SIZE para operações nativas, fila de background para trabalho não-imediato.
  4. Paralelizar: se as alternativas falharam e o problema é confirmado como CPU-bound ou de escalonamento — aí sim percorrer a decision tree.

Pular os passos 1-3 e ir direto ao paralelismo é a origem da maioria dos casos de dívida técnica com Worker Threads adicionados sem benefício mensurável.


Como funciona — fluxograma

Qual o problema?

├─ CPU-bound em handler ou job?
│   └─ → Worker Thread
│       ├─ Tarefa frequente / alta carga? → pool (piscina)
│       │   (piscina = lib de referência; availableParallelism() threads)
│       └─ Tarefa rara / esporádica? → Worker por task é OK
│
├─ Preciso escalar HTTP além de 1 thread?
│   ├─ Tem orquestrador (K8s, ECS, Fly.io)?
│   │   → deixa o orquestrador (1 pod por core, réplicas declarativas)
│   ├─ Single-VM deploy / VPS sem container runtime?
│   │   → Cluster (ou PM2 em modo cluster)
│   └─ Dev local pra testar comportamento multi-worker?
│       → Cluster
│
├─ Preciso rodar comando externo (ffmpeg, git, python, imagemagick)?
│   ├─ Output streaming / output grande (> 1 MB)?
│   │   → spawn (streams, sem maxBuffer)
│   ├─ Output curto e sem input do usuário na string?
│   │   → execFile com array de args (sem shell)
│   │       ou exec se o comando é completamente hardcoded
│   └─ Argumento vem de input externo?
│       → SEMPRE execFile ou spawn com array — NUNCA exec
│           (exec interpreta shell: ; | && $() viram injeção)
│
└─ Preciso spawnar um processo Node isolado?
    ├─ CPU-bound sem necessidade de isolamento total?
    │   → Worker Thread (mais leve, ~1-5ms vs ~100ms)
    ├─ Isolamento total de memória (multi-tenancy, código não-confiável)?
    │   → fork (processos OS separados, crash não afeta o pai)
    ├─ Native module legado não thread-safe?
    │   → fork (cada processo tem seu isolate V8 separado)
    └─ Supervisor tree / processo descartável que pode crashar?
        → fork + padrão de supervisor com backoff exponencial

Cluster vs orquestrador

Em 2026, se existe um orquestrador (K8s, ECS, Nomad, Fly.io), não adicione Cluster. O orquestrador já gerencia réplicas, health checks e rolling updates. Cluster dentro de pod apenas adiciona processos sem benefício proporcional. A regra: 1 processo Node por container, orquestrador cuida do resto.

Como usar o fluxograma

Percorra o fluxograma em voz alta enunciando o problema antes de nomear a ferramenta. O erro comum é começar pela ferramenta e buscar justificativa depois — “vou usar Worker Threads porque é moderno”. A árvore força o sentido correto: problema → ferramenta → razão.


Tabela problema → ferramenta → razão

ProblemaFerramentaPor quê
Hash bcrypt em handler HTTPWorker Thread + poolCPU-bound no event loop; pool reutilizável evita overhead de criação por request
Image processing de 100 MB/reqWorker Thread + transferListZero-copy de buffers grandes entre threads; sem duplicação de memória
Matrix ops em ML inferenceWorker Thread + SharedArrayBufferMemória compartilhada zero-copy; Atomics para coordenação
Servir HTTP com 4 cores, single-VMCluster (ou PM2)Round-robin de conexões TCP; 1 worker por core aproveita os 4 cores
Servir HTTP com 4 cores, K8s4 réplicas de podOrquestrador gerencia scaling, health check e rolling update
WebSocket server em K8s4 pods + sticky session via ingressConexões persistentes exigem afinidade; ingress com ip_hash ou cookie
Rodar ffmpegspawnOutput de vídeo é grande; streaming evita maxBuffer
Rodar git log --oneline -5execFile com array de argsOutput curto; sem shell elimina vetor de injeção
Rodar script com pipes de shell hardcodedexecPipes exigem shell; OK se args são completamente hardcoded sem input externo
Sandbox de código de tenantfork (ou vm/isolated-vm)Isolamento de processo; crash de tenant não derruba o pai
Worker tree (queue managers, jobs)fork + padrão de supervisorCrash + respawn é o comportamento esperado; backoff exponencial evita loop
Parsing de arquivo não-confiávelfork com --max-old-space-sizeSIGSEGV ou OOM no parser não derruba o processo principal

Na prática — aplicando a decision tree

O exercício útil é percorrer a árvore em voz alta — enunciando o problema antes de nomear a ferramenta. A estrutura abaixo demonstra esse raciocínio para 4 cenários diferentes.

Caso 1: endpoint que recebe upload de imagem e gera thumbnail

Diagnóstico: processamento de imagem é CPU-bound. Cada request que chega bloqueia o event loop enquanto o resize roda. Em carga alta, todos os outros endpoints ficam lentos — o event loop lag sobe porque a thread JS está ocupada com o cálculo de pixels, não aguardando I/O.

Decision tree:

  • CPU-bound em handler? → Worker Thread
  • Tarefa frequente (upload é uma feature core, não um job raro)? → pool

Solução: Worker Thread com pool dimensionado em availableParallelism(). O upload vai para o pool; o event loop principal fica livre para continuar recebendo requests e processando I/O. Buffers de imagem passam via transferList para evitar cópia — um buffer de 20 MB não deve ser clonado duas vezes no processo de ir de main para worker e voltar com o resultado.

O que não fazer:

  • Usar cluster para “escalar” o endpoint — cada worker teria o mesmo problema de bloqueio individual.
  • Criar um Worker por request sem pool — o overhead de criação de thread (~ms) se acumula em alta carga.

Caso 2: API que executa aws s3 ls para o usuário

Diagnóstico: precisa rodar comando externo (CLI do AWS). O bucket name vem do request body — é input externo não-controlado.

Decision tree:

  • Rodar comando externo? → child_process
  • Output curto (listagem de S3)? → execFile ou exec
  • Tem argumento de origem externa (bucket name)? → execFile com array — NUNCA exec

Solução:

import { execFile } from 'node:child_process/promises';
 
const { stdout } = await execFile('aws', ['s3', 'ls', `s3://${req.body.bucket}`], {
  encoding: 'utf8',
});

req.body.bucket chega como argumento literal ao processo aws — sem shell, sem injeção. Se o usuário enviar ; rm -rf / como bucket, o AWS CLI recebe esse string como nome de bucket e retorna um erro de bucket inválido, sem executar nada no shell.

O que não fazer:

// ❌ — exec interpreta o shell: ; rm -rf / executa de verdade
exec(`aws s3 ls s3://${req.body.bucket}`, callback);

Caso 3: build server que orquestra processos de compilação

Diagnóstico: cada build é um processo de longa duração que pode crashar por bug no bundler, OOM, ou exceção não capturada. O build server precisa sobreviver a crashes de builds individuais e decidir se reinicia builds com falha.

Decision tree:

  • Processo Node isolado com IPC? → fork
  • O processo pode crashar sem afetar o pai? → fork (processo OS separado)
  • Supervisor tree / processo descartável? → fork + padrão de supervisor com backoff

Solução: fork('./build-worker.js') por build. O pai monitora o evento 'exit', registra o código de saída, e decide se reinicia com backoff exponencial — sem reiniciar builds que terminaram com código 0. Um SIGSEGV no bundler, um OOM no webpack, uma exceção não capturada no processo de build: o servidor de build está intacto.

O que não fazer:

  • Worker Thread: um crash de thread pode corromper o estado do processo principal. Para processos que lidam com bundlers ou parsers não-confiáveis, isolamento total via fork é o modelo correto.
  • spawn sem IPC: funciona, mas perde o canal bidirecional child.send() / process.send() que permite enviar configuração de build e receber status intermediário.

Caso 4: WebSocket server escalando em K8s

Diagnóstico: WebSocket requer que todas as mensagens de um cliente vão para o mesmo processo (stateful). K8s com round-robin puro quebra isso — o handshake HTTP inicial que faz o upgrade para WebSocket pode ir para o pod 1; um reconect pode ir para o pod 3, que não tem o contexto da conexão.

Decision tree:

  • Escalar HTTP/WS? → sim
  • Tem orquestrador (K8s)? → orquestrador cuida de réplicas
  • WebSocket (conexão persistente, stateful)? → sticky session via ingress

Solução: 4 pods (1 por core disponível, configurado no Deployment), ingress configurado com sticky session. No nginx: ip_hash. No traefik: cookie-based affinity. Sessão/estado da conexão WS em Redis ou store externo — não em memória local do pod, porque pods podem ser substituídos a qualquer momento pelo K8s durante rolling updates.

O que não fazer:

  • Adicionar cluster dentro do pod além das réplicas K8s — 4 pods × 4 workers = 16 processos, sem ganho proporcional.
  • Sticky session apenas por IP sem Redis: se o pod for substituído (crash, rolling update), o estado da sessão desaparece mesmo que o cliente seja roteado para o pod correto.

Casos práticos adicionais

Caso 5 — Geração de PDF em endpoint de relatório

Um endpoint /reports/pdf gera PDFs com Puppeteer ou wkhtmltopdf. A geração leva entre 2 e 15 segundos por documento e é CPU + I/O-bound (renderização de HTML + escrita de arquivo).

Diagnóstico:

  • CPU-bound? Parcialmente — renderização de HTML consome CPU, mas a maior parte do tempo é I/O (Chromium carregando assets, escrevendo arquivo)
  • Handler bloqueia o event loop? Sim — se você usa uma lib síncrona ou chama o CLI diretamente via execFile

Decision tree:

  1. Rodar processo externo (wkhtmltopdf)? → execFile ou spawn
  2. Output é o próprio arquivo gerado (não stdout grande)? → execFile suficiente
  3. Argumento vem de input externo (nome do relatório, datas)? → execFile com array, nunca exec
import { execFile } from 'node:child_process/promises';
import { randomUUID } from 'node:crypto';
import path from 'node:path';
 
async function generatePdf(templateUrl, outputDir) {
  // templateUrl é construída internamente (não vem de input bruto)
  // mas por defensividade, usar execFile com array de qualquer forma
  const outputPath = path.join(outputDir, `${randomUUID()}.pdf`);
 
  await execFile('wkhtmltopdf', [
    '--quiet',
    '--page-size', 'A4',
    templateUrl,   // URL interna
    outputPath,
  ], { timeout: 30_000 }); // 30s de timeout
 
  return outputPath;
}

Se você usa Puppeteer (Node nativo), a geração roda no processo filho Chromium — não bloqueia o event loop Node. Para concorrência alta, um pool de instâncias Puppeteer é mais eficiente do que spawnar um processo por request.


Caso 6 — Hash de senha em endpoint de autenticação

Endpoint POST /auth/register recebe senha em texto plano. bcrypt com custo 12 leva ~300–400ms de CPU puro — bloqueando o event loop para todos os outros requests durante esse tempo.

Diagnóstico:

  • CPU-bound? Sim — bcrypt é computação pura de hash
  • Handler bloqueia? Sim — bcrypt.hashSync() trava a thread JS

Decision tree: CPU-bound em handler → Worker Thread → tarefa frequente (auth é crítica) → pool

// hash-worker.js — roda em Worker Thread
import { workerData, parentPort } from 'node:worker_threads';
import bcrypt from 'bcrypt';
 
const { password, cost } = workerData;
const hash = await bcrypt.hash(password, cost);
parentPort.postMessage({ hash });
// auth-service.js — usa piscina de workers
import Piscina from 'piscina';
import { availableParallelism } from 'node:os';
 
const hashPool = new Piscina({
  filename: new URL('./hash-worker.js', import.meta.url).href,
  maxThreads: Math.max(2, availableParallelism() - 1), // deixar 1 thread para o event loop
});
 
export async function hashPassword(password, cost = 12) {
  return hashPool.run({ password, cost });
}
 
// Cleanup no shutdown
process.on('SIGTERM', async () => {
  await hashPool.destroy();
  process.exit(0);
});

Por que não fork: fork teria ~100ms de overhead de criação de processo por hash — proibitivo em auth de alta carga. Worker Thread (~1-5ms de criação + pool reutilizável) é a escolha correta. bcrypt é thread-safe no N-API.

Por que não Cluster para esse problema: Cluster cria 4 réplicas com o mesmo problema — cada uma trava seu event loop por 300ms durante hash. O problema está dentro de um único handler, não na capacidade de aceitar conexões.


Armadilhas comuns

1. Confundir DB lento com CPU-bound

O que acontece: O endpoint tem latência alta, parece CPU-bound — mas medir o event loop lag mostra valores baixos. Worker Thread adicionado não reduz a latência nem 1ms.

Por quê: Handler com await db.query() que demora 2 segundos: o event loop não está bloqueado — ele está aguardando I/O. Worker Thread não ajudaria; a query continuaria demorando 2 segundos na thread do worker.

Como evitar: Medir primeiro. perf_hooks.monitorEventLoopDelay() revela se o lag é alto (CPU-bound) ou baixo (I/O-bound). Se o lag é baixo mas a latência é alta, a solução está no banco (índice, query plan, connection pool), não no paralelismo.

const { monitorEventLoopDelay } = require('node:perf_hooks');
const h = monitorEventLoopDelay({ resolution: 10 });
h.enable();
// Após intervalo de observação:
console.log('p99 lag (ms):', h.percentile(99) / 1e6);
// lag baixo + latência alta = problema de I/O, não CPU

2. Cluster + K8s — overhead duplicado sem perceber

O que acontece: 4 réplicas K8s × 4 workers PM2 = 16 processos Node, 16 heaps separadas, overhead de IPC dentro de cada pod. O K8s não consegue fazer health check por worker individualmente — se um dos 4 workers internos crashar, o pod ainda aparece como healthy.

Por quê: O erro clássico na migração de VPS para K8s: mover o ecosystem.config.js para dentro do Dockerfile e rodar PM2 com instances: 4 dentro do container. O orquestrador já gerencia réplicas — adicionar Cluster ou PM2 em modo cluster dentro do pod duplica o overhead sem benefício equivalente.

Como evitar: CMD ["node", "src/server.js"] no Dockerfile — um processo por container. O K8s gerencia réplicas, health checks e rolling updates.

# ❌ Dockerfile com PM2 cluster dentro do pod
CMD ["pm2-runtime", "ecosystem.config.js"]
 
# ✓ Um processo por container — K8s gerencia réplicas
CMD ["node", "src/server.js"]

3. Worker Thread como solução universal para "API lenta"

O que acontece: Worker Threads são adicionados a um endpoint lento. A latência não muda. A complexidade de threading aumentou sem benefício mensurável.

Por quê: Worker Threads resolvem CPU-bound. Não resolvem query lenta, falta de índice no banco, N+1 queries, ou I/O-bound. O bottleneck não estava na thread JS — estava na rede ou no banco.

Como evitar: O diagnóstico vem primeiro. Se o event loop lag é baixo, Worker Thread não é a resposta. Tentar alternativas na ordem: índice no banco, paginação, streaming, API async em vez de sync, UV_THREADPOOL_SIZE para operações nativas, fila de background.

// Diagnóstico rápido antes de qualquer Worker Thread
const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();
// p99 < 5ms? Problema é I/O — não adicionar Worker Thread
// p99 > 50ms? Problema pode ser CPU-bound — investigar o que trava

4. exec com input do usuário — sempre vulnerabilidade

O que acontece: Input do usuário contendo ;, |, &&, $() é interpretado pelo shell e executa comandos arbitrários no servidor (Remote Code Execution).

Por quê: Não existe sanitização confiável para input passado para exec. A lista de metacaracteres de shell muda entre shells, entre versões, e entre contextos. Qualquer regex de filtragem tem bypass documentado.

Como evitar: Nunca usar exec com variáveis externas. execFile e spawn com array de argumentos passam o input como string literal ao processo — sem shell, sem injeção possível.

// ❌ — vulnerável independente de "sanitização"
const filename = req.query.file.replace(/[^a-z0-9.]/gi, '');
exec(`cat ${filename}`, callback); // qualquer bypass da regex = RCE
 
// ✓ — estruturalmente seguro
execFile('cat', [req.query.file], callback);
// Shell nunca entra no caminho. Metacaracteres são texto inerte.

5. fork onde Worker Thread basta — overhead desnecessário

O que acontece: Cada tarefa CPU-bound cria um processo OS completo com ~100ms de overhead. Sob carga alta, o tempo de criação domina o tempo de execução. Memória cresce com cada fork porque cada processo tem heap própria.

Por quê: fork cria um processo OS completo: novo isolate V8, nova heap, nova stack, novo event loop. Worker Thread cria uma thread no mesmo processo: ~1-5ms. Para CPU-bound puro sem necessidade de isolamento total, Worker Thread é sempre mais eficiente.

Como evitar: fork é para quatro casos específicos: isolamento de segurança (código de tenant), native modules não thread-safe, supervisor tree, processo descartável. Fora desses casos, Worker Thread com pool.

// ❌ — fork para CPU-bound simples: ~100ms por criação, processo OS inteiro
const child = fork('./hash-worker.js');
child.send({ password });
 
// ✓ — Worker Thread: ~1-5ms, thread no mesmo processo
const worker = new Worker('./hash-worker.js', { workerData: { password } });

Em entrevista

Cheatsheet de referência rápida

CPU-bound em handler?
  → Worker Thread
  → + pool se alta carga (piscina, availableParallelism())
  → + transferList se buffers grandes
  → + SharedArrayBuffer se acesso concorrente a dados compartilhados

HTTP scaling?
  → orquestrador se K8s/ECS/Fly.io (1 processo por container)
  → Cluster ou PM2 se single-VM
  → Dev local: Cluster para simular multi-worker

Comando externo?
  → spawn se output grande / streaming
  → execFile se output curto + input externo nos args
  → exec APENAS se comando completamente hardcoded

Node child isolado?
  → Worker Thread se CPU-bound sem necessidade de isolamento
  → fork se: isolamento total / native addon legado / supervisor tree / processo descartável

Frase pronta (em inglês)

“When I face a Node parallelism question, I always start from the problem, not the tool. CPU-bound work inside a handler — something like image resizing or bcrypt hashing — goes to Worker Threads, and in production I’d use a pool sized to availableParallelism() so I’m not creating threads per request. Scaling HTTP across cores depends on context: if there’s an orchestrator like Kubernetes, I let it manage replicas — one process per container, no cluster inside the pod. On a single VM without a container runtime, native cluster or PM2 make sense. For external commands, the rule is simple: if arguments can come from user input, always use execFile or spawn with an argument array — never exec, because exec runs through a shell and any metacharacter in the input becomes a shell injection vector. Finally, for spawning an isolated Node process, I’d prefer Worker Threads if it’s purely CPU-bound — much cheaper creation cost. fork wins when I need full OS-level isolation: running untrusted tenant code, working with non-thread-safe native addons, or building a supervisor tree where the parent needs to outlive and restart crashing children.”

Vocabulário consolidado

PT-BREN
árvore de decisãodecision tree
trabalho limitado por CPUCPU-bound work
pool de workersworker pool
cópia zerozero-copy
orquestradororchestrator
réplicareplica
sessão stickysticky session
injeção de shellshell injection
argumento literalliteral argument
isolamento de processoprocess isolation
árvore de supervisãosupervisor tree
processo descartáveldisposable process
reinicialização com backoffrestart with exponential backoff
custo de criaçãospawn overhead / creation cost
paralelismo disponívelavailable parallelism

Perguntas frequentes em entrevista

“Como você escolheria entre Worker Thread e fork?” Worker Thread é a escolha padrão para CPU-bound: menor custo de criação (~1-5ms vs ~100ms), mesma API de eventos, memória compartilhável via SharedArrayBuffer. fork ganha quando isolamento total é o requisito — código de tenant não-confiável, native addons não thread-safe, supervisor tree onde o filho pode crashar sem afetar o pai, ou processo descartável com memória limitada via --max-old-space-size.

“Por que não usar Cluster para paralelizar CPU-bound?” Cluster cria N réplicas do processo completo — cada réplica com seu próprio event loop. Se o problema é CPU-bound dentro de um único handler, cada worker bloqueia seu próprio event loop com o mesmo trabalho. Você multiplicou o problema, não o paralelizou. Worker Thread paraleliza o trabalho dentro de um único request, liberando o event loop do handler. São soluções para problemas orthogonais.

“Quando exec é aceitável?” Quando o comando completo é hardcoded no código — sem nenhuma variável externa na string. exec('git log --oneline -5') é seguro porque não há interpolação. exec(\git log —author=${req.query.author}`)é vulnerável mesmo queauthorpareça inócuo — o shell interpreta todo metacaractere. A regra: se existe qualquer variável na string, useexecFileouspawn` com array.

“O que acontece se eu usar Cluster dentro de um pod K8s?” Nada quebra, mas você tem overhead sem benefício equivalente. 4 réplicas K8s × 4 workers cluster = 16 processos Node com 16 heaps separadas. O K8s não consegue fazer health check por worker individualmente — o readiness probe é por pod. Se um dos 4 workers interiores crashar, o pod ainda aparece como healthy. O padrão correto é 1 processo por container; o orquestrador gerencia replicas, health check e rolling update.


O que vem a seguir

A decision tree é o artefato de síntese do galho — mas ter o raciocínio disponível é diferente de ter praticado os erros reais. A nota [[12 - Armadilhas, regras práticas, cheatsheet]] fecha o galho com o top 10 de armadilhas extraídas das notas 01–11, a tabela ferramenta×atributo em formato de cheatsheet, e o vocabulário PT→EN consolidado. É o artefato de revisão final antes de uma entrevista. Depois do galho Paralelismo, o próximo domínio relacionado é Streams — onde os dados grandes que você aprende a processar em Worker Threads e spawn precisam fluir pelo sistema sem acumular na memória.


Fontes


Rubric

CritérioStatus
TL;DR cobre os 4 ramos da decision treeok
Fluxograma ASCII legível e completook
Tabela problema → ferramenta → razãook
4 casos práticos com diagnóstico + raciocíniook
5 armadilhas com exemplos de códigook
Frase pronta em inglês cobre os 4 ramosok
Vocabulário EN mínimo 6 termosok (15 termos)
FAQ de entrevista com 4 perguntasok
Wikilinks para notas do galhook
Sem fabricação de dados do usuáriook
Nota puramente sintética — sem WebFetchok

Veja também