8 min

Teste de carga em Go antes da primeira contratação backend

Use teste de carga em Go para simular tráfego realista e medir latência p95, pressão no banco, memória e falhas antes de decidir a contratação.

Teste de carga em Go antes da primeira contratação backend

Dez mil usuários ativos mensais não são um requisito de capacidade. Esse é um número de faturamento ou análise. Um serviço Go pode atender muito mais pessoas quando as solicitações são leves e distribuídas ao longo do mês, ou cair com algumas centenas se cada sessão disparar consultas lentas, uploads e chamadas a serviços externos.

A pergunta útil é se o backend gerado cumpre um objetivo de serviço definido sob o pico de tráfego esperado, com margem suficiente para crescimento e uma falha comum. Você pode responder antes de contratar um engenheiro de backend, mas apenas se o teste de carga se parecer com seu produto e registrar ao mesmo tempo o que fazem a API, o runtime do Go e o PostgreSQL. Um gráfico verde de latência média quase não prova nada.

Este é o teste que eu exigiria antes de dizer a um fundador que um backend Go gerado está pronto para 10.000 usuários ativos mensais. Ele produz um resultado repetível de aprovação ou falha, revela o primeiro gargalo e separa um problema de capacidade de um problema de correção.

Usuários mensais precisam virar solicitações de pico

Converta a previsão de usuários em solicitações por segundo antes de escolher qualquer nível de carga. Usuários ativos mensais escondem as duas variáveis que movimentam um backend: quantas sessões chegam no período mais movimentado e quanto trabalho cada sessão cria.

Comece com dados observados se já houver um beta privado. Conte as sessões nos 15 minutos mais movimentados, as solicitações por sessão e a distribuição de rotas. Se ainda não há tráfego, escreva as premissas para que todos possam questioná-las. Por exemplo, suponha que 10.000 usuários ativos criem oito sessões por mês, 15 solicitações de API por sessão e que 20 por cento do tráfego diário caia na hora mais movimentada. Isso produz cerca de 8 solicitações por segundo em um dia movimentado normal. Lançamentos, notificações, prazos de folha de pagamento ou um fuso horário compartilhado podem multiplicar o pico real.

Não transforme essa conta em falsa precisão. Use-a para definir três níveis de teste:

  • Pico esperado: a carga mais movimentada que você prevê agora.
  • Pico de crescimento: o dobro do pico esperado, a menos que a empresa tenha uma previsão melhor.
  • Nível de estresse: aumente o tráfego até que um objetivo falhe ou um recurso fique saturado.

Os testes do pico esperado e de crescimento dizem se o lançamento planejado tem margem. O teste de estresse mostra o que quebra primeiro e como a falha aparece para os usuários. Essa última resposta importa porque um serviço que rejeita rápido o trabalho excedente é mais fácil de operar do que um que consome todas as conexões do banco e paralisa rotas não relacionadas.

Use um modelo de carga aberto no teste de capacidade. Um modelo aberto inicia solicitações em uma taxa de chegada fixa mesmo quando as anteriores ficam lentas. Um modelo fechado com número fixo de usuários virtuais costuma esconder o colapso: respostas mais lentas fazem esses usuários enviarem menos solicitações novas, então a carga oferecida cai exatamente quando o serviço sofre. A documentação do k6 da Grafana distingue os modelos com executores de taxa de chegada e informa dropped_iterations quando o gerador não consegue iniciar o trabalho programado. Trate iterações descartadas como falha do gerador, não como sucesso do servidor.

Mantenha cada nível estável por pelo menos 30 minutos depois do aquecimento. Testes de cinco minutos não veem renovação de conexões, ciclos do coletor de lixo, expulsão do cache, tarefas em segundo plano e aumento gradual de memória. Depois que o teste curto passar, execute um teste de resistência separado de duas horas no pico esperado.

Escreva a conversão numa planilha pequena e mantenha todas as unidades. Usuários mensais multiplicados por sessões por usuário e solicitações por sessão resultam em solicitações mensais. Só divida depois de distribuir o tráfego entre dias de operação e a hora mais movimentada. Depois acrescente tentativas repetidas, tarefas em segundo plano, webhooks e polling que a análise de usuários talvez não conte. Um frontend que consulta a cada dez segundos pode criar mais trabalho de API do que os cliques que abriram a página.

Modele as rajadas separadamente do pico estável. Login após uma notificação, conclusão de importação ou clientes repetindo pedidos depois de uma interrupção curta podem concentrar trabalho em um minuto. Adicione uma etapa que alcance depressa a taxa esperada, permaneça tempo suficiente para encher filas e volte ao normal. O serviço deve se recuperar sem backlog crescente nem reinicialização manual. Registre o tempo de recuperação. Um sistema pode passar no teste estável e ainda ser inseguro se uma rajada curta deixar o pool ou os workers travados.

Não multiplique a taxa final por um fator de segurança arbitrário e chame o resultado de realista. Ligue a margem a uma incerteza comercial: erro de previsão, campanha planejada, uma réplica indisponível ou tempo necessário para acrescentar capacidade. Teste cada premissa da qual pretende depender. Guarde a planilha junto do resultado, porque uma execução aprovada perde o sentido quando ninguém lembra quais previsões e premissas de rajada produziram o objetivo.

A distribuição do tráfego deve parecer uma sessão real

Um teste realista preserva a frequência das rotas, o tamanho dos payloads, a autenticação, a distribuição dos dados, as pausas do usuário e a contenção de escrita. Chamar /health cem vezes por segundo mede o handler de saúde, não a aplicação.

Monte a distribuição a partir dos logs de acesso quando possível. Agrupe rotas por ação de negócio e não pela URL literal, pois /projects/123 e /projects/456 têm a mesma forma. Uma distribuição plausível para um SaaS inicial poderia reservar 45 por cento para listas e detalhes, 20 por cento para pesquisa, 15 por cento para criação ou atualização, 10 por cento para login e renovação de token e 10 por cento para exportações ou outro trabalho pesado. Seus números devem vir do fluxo do produto, não deste exemplo.

Use muitas contas e registros de teste. Reutilizar uma conta pode criar um cache quente irreal, serializar atualizações na mesma linha ou ativar limites de taxa que o tráfego real distribuiria. Prepare tenants pequenos, médios e grandes. Inclua registros ausentes, entradas inválidas e falhas de autorização, porque os caminhos de erro podem consultar o banco ou alocar respostas de modo diferente dos caminhos de sucesso.

Coloque uploads grandes e exportações longas em seu próprio cenário se eles tiverem outro objetivo. Mesmo assim, execute-os junto com o tráfego normal. Caso contrário, o teste não verá exatamente o incidente que os usuários percebem: uma classe de exportação ocupa o pool enquanto uma página simples de configurações espera atrás dela.

Não simule PostgreSQL, armazenamento de objetos, filas nem serviços externos no teste final de capacidade. Uma simulação ajuda a isolar o custo do handler, mas remove as dependências que provavelmente definem a capacidade. Aponte o teste para um stack de staging com os mesmos tamanhos de instância, ajustes do banco, índices, limites de conexão e caminho de rede da produção. Dados limpos com o formato da produção são melhores do que mil linhas iniciais idênticas.

Evite testar por um cache de distribuição se a API normalmente não fica em cache. Por outro lado, mantenha o cache real no caminho quando a produção o usa. O objetivo não é fazer o backend parecer ocupado. É reproduzir o trabalho realmente criado por uma solicitação do usuário.

Um teste k6 executável deve registrar o contrato

Mantenha limites e etapas de tráfego no controle de versão para que uma execução não vire uma captura de tela interpretada depois. O k6 trata limites como critérios de aprovação ou falha e encerra com código diferente de zero quando falham, o que torna o resultado adequado para uma verificação de release.

O esqueleto a seguir executa uma sessão mista por taxa de chegada, verifica o significado da resposta e coloca limites de latência separados em leituras comuns e exportações pesadas. Substitua rotas, payloads e objetivos pelos valores acordados para seu produto. O código é simples de propósito para que uma verificação com falha corresponda a uma ação do usuário.

import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';

const businessErrors = new Rate('business_errors');

export const options = {
  scenarios: {
    expected_peak: {
      executor: 'ramping-arrival-rate',
      startRate: 5,
      timeUnit: '1s',
      preAllocatedVUs: 40,
      maxVUs: 200,
      stages: [
        { target: 10, duration: '5m' },
        { target: 10, duration: '30m' },
        { target: 20, duration: '10m' },
        { target: 20, duration: '30m' },
      ],
    },
  },
  thresholds: {
    'http_req_duration{name:project_list}': ['p(95)<300'],
    'http_req_duration{name:project_create}': ['p(95)<500'],
    'http_req_duration{name:export}': ['p(95)<2000'],
    http_req_failed: ['rate<0.01'],
    business_errors: ['rate<0.005'],
    dropped_iterations: ['count==0'],
  },
};

export function setup() {
  const response = http.post(`${__ENV.BASE_URL}/api/login`, JSON.stringify({
    email: __ENV.TEST_EMAIL,
    password: __ENV.TEST_PASSWORD,
  }), { headers: { 'Content-Type': 'application/json' } });
  check(response, { 'login succeeds': r => r.status === 200 });
  return { token: response.json('token') };
}

export default function (data) {
  const headers = { Authorization: `Bearer ${data.token}`, 'Content-Type': 'application/json' };
  const list = http.get(`${__ENV.BASE_URL}/api/projects?limit=25`, {
    headers,
    tags: { name: 'project_list' },
  });
  businessErrors.add(!check(list, {
    'list status is 200': r => r.status === 200,
    'list has items': r => Array.isArray(r.json('items')),
  }));

  if (Math.random() < 0.25) {
    const create = http.post(`${__ENV.BASE_URL}/api/projects`, JSON.stringify({
      name: `load-${__VU}-${__ITER}`,
    }), { headers, tags: { name: 'project_create' } });
    businessErrors.add(!check(create, { 'create status is 201': r => r.status === 201 }));
  }

  if (Math.random() < 0.03) {
    const runExport = http.post(`${__ENV.BASE_URL}/api/exports`, '{}', {
      headers,
      tags: { name: 'export' },
    });
    businessErrors.add(!check(runExport, { 'export accepted': r => r.status === 202 }));
  }

  sleep(Math.random() * 2 + 1);
}

Os objetivos de exemplo são pontos de partida, não promessas universais. Defina o p95 por classe de rota conforme o atraso tolerado pelo usuário e os requisitos do produto. Não avalie uma exportação assíncrona e uma solicitação de preenchimento automático com o mesmo limite global de 300 ms.

Execute o script em uma máquina que não hospede a aplicação. Confirme que o gerador tem CPU livre e nenhuma iteração descartada. Guarde o commit exato, a configuração do ambiente, o identificador do snapshot de dados, o comando e a saída bruta. Sem isso, uma comparação futura depende principalmente de memória e otimismo.

Calibre o gerador antes de confiar numa execução longa. Aponte-o para um handler pequeno sem trabalho de banco, suba a taxa solicitada acima do teste planejado e confira se ele a sustenta sem esgotar sua própria CPU, sockets ou rede. Quando o k6 adiciona usuários virtuais ou informa iterações descartadas, a máquina de carga pode ser o limite. Distribua a geração entre máquinas somente se uma não conseguir oferecer o trabalho necessário e sincronize os relógios para alinhar os gráficos do servidor e do cliente.

Dê a cada execução uma linha de base tranquila. Pare migrações, importações e tarefas de staging sem relação, a menos que também funcionem no pico real. Depois programe um segundo teste com o trabalho real em segundo plano. O par mostra tanto a capacidade limpa da API quanto a capacidade operacional que os usuários receberão. Se apenas a execução tranquila passar, o plano de lançamento depende de uma ficção de staging.

Crie um segundo script para uma única jornada do usuário e execute-o com um usuário virtual antes de acrescentar carga. Inspecione todas as respostas, registros criados e limpezas. Isso encontra um token incorreto, uma verificação sempre verdadeira ou dados de teste que colidem após a primeira iteração. Um resultado de capacidade não vale nada se o script exercita uma página de erro ou lê repetidamente o mesmo objeto em cache.

p95 precisa de contexto por rota

Use a latência p95 porque médias escondem uma minoria lenta, mas nunca leia p95 sozinho. Com p95 de 800 ms, uma em cada vinte solicitações demora pelo menos esse tempo, o que pode fazer uma página com várias chamadas parecer sempre lenta. O percentil também fica instável em rotas com poucas amostras, então informe o número de solicitações ao lado.

Registre p50, p95, p99, máximo, throughput e taxa de erro para cada ação de negócio nomeada. p50 mostra o comportamento normal, p95 funciona como controle prático e p99 expõe a cauda sem deixar um único máximo dominar a conversa. Separe os resultados por status. Uma resposta 500 rápida não deve melhorar a história da latência.

Meça a duração do handler no servidor além da duração observada pelo cliente. A diferença inclui conexão, proxies, tempo de rede e transferência da resposta. Se o p95 do cliente sobe enquanto o handler fica estável, procure fora do handler. Se os dois sobem e o tempo de espera do banco aumenta, a solicitação provavelmente está na fila por uma conexão ou consulta.

Separe aquecimento e estado estável. Um binário Go implantado não está compilando, mas caches frios, novas conexões, inicialização tardia e escala automática podem distorcer os primeiros minutos. Os usuários também vivem esse comportamento frio, então mantenha-o como resultado separado.

Médias ainda ajudam na contabilidade de recursos. Tempo total do banco dividido por chamadas pode revelar uma consulta moderadamente lenta e muito frequente. Não pode substituir um objetivo de percentil. A área costuma confundir latência e capacidade: latência descreve o tempo do trabalho concluído, enquanto capacidade descreve quanto trabalho oferecido o serviço sustenta sem filas ou erros crescentes. Boa latência com baixa taxa alcançada não prova capacidade.

Defina falha antes da execução. Eu reprovaria um teste de lançamento quando uma rota crítica perder o alvo p95, falhas HTTP inesperadas passarem da taxa acordada, verificações de negócio falharem, iterações programadas forem descartadas ou um recurso continuar saturado. Cumprir quatro de cinco critérios é uma execução reprovada com dados úteis de diagnóstico.

Esperas por conexão revelam filas ocultas no banco

Implante o build medido
Koder.ai pode implantar e hospedar o commit exato testado sob tráfego realista.

Instrumente database/sql antes do teste, pois a latência da aplicação não diz se o PostgreSQL está lento ou se a aplicação espera para acessá-lo. DB.Stats() do Go informa OpenConnections, InUse, Idle, WaitCount e WaitDuration, junto de contadores de fechamento. Exporte tudo ao sistema de métricas a cada poucos segundos.

func recordDBStats(ctx context.Context, db *sql.DB, g GaugeSet) {
    ticker := time.NewTicker(5 * time.Second)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            s := db.Stats()
            g.Set("db_open_connections", float64(s.OpenConnections))
            g.Set("db_in_use_connections", float64(s.InUse))
            g.Set("db_idle_connections", float64(s.Idle))
            g.Set("db_wait_count_total", float64(s.WaitCount))
            g.Set("db_wait_seconds_total", s.WaitDuration.Seconds())
        }
    }
}

Calcule a mudança nos contadores acumulados durante a janela estável. WaitCount subindo significa que solicitações esperaram por uma conexão livre. A mudança de WaitDuration dividida pela de WaitCount dá a espera média do pool no intervalo. Trace InUse contra MaxOpenConnections; uma linha plana no limite com espera crescente mostra saturação.

Não aumente SetMaxOpenConns até o gráfico parecer melhor. Essa correção popular transfere a fila para o PostgreSQL e pode aumentar contenção, memória e latência. Primeiro descubra por que as conexões ficam ocupadas: consultas lentas, transações abertas durante chamadas de rede, leitura de uma linha por vez ou chamadas esquecidas de Rows.Close(). Depois dimensione o pool dentro do orçamento de conexões para todas as réplicas e workers.

A documentação do Go diz que um valor não positivo de SetMaxOpenConns deixa o pool ilimitado. Esse padrão é perigoso em produção quando várias réplicas podem abrir conexões juntas. Defina um limite explícito, escolha de propósito os comportamentos de inatividade e vida útil e reserve capacidade para migrações, administração e tarefas em segundo plano.

Acompanhe a duração das transações separadamente. Um handler pode responder em 200 ms enquanto uma limpeza adiada ou transação perdida segura a conexão por muito mais tempo. As métricas do pool revelam a pressão, mas traces ou tempos da transação mostram o responsável.

A prova de consultas lentas deve vir do PostgreSQL

Ative pg_stat_statements no ambiente de teste e tire snapshots antes e depois de cada execução. A documentação do PostgreSQL o descreve como rastreador das estatísticas de planejamento e execução de instruções normalizadas. A view inclui chamadas, linhas, tempos total e médio, atividade de blocos e blocos temporários. Essa evidência é muito melhor do que adivinhar pela consulta que apareceu num único trace.

Use a diferença entre snapshots porque a view é acumulativa. Só redefina numa base isolada, pois a redefinição destrói evidências de outros trabalhos. Esta consulta encontra as instruções que consumiram mais tempo durante uma janela limpa:

SELECT
  queryid,
  calls,
  round(total_exec_time::numeric, 1) AS total_ms,
  round(mean_exec_time::numeric, 2) AS mean_ms,
  rows,
  shared_blks_read,
  temp_blks_written
FROM pg_stat_statements
WHERE calls > 20
ORDER BY total_exec_time DESC
LIMIT 20;

O tempo total encontra consultas frequentes que dominam o trabalho do banco. O tempo médio encontra instruções individualmente lentas. Nenhum fornece p95 porque pg_stat_statements agrega chamadas; use tracing ou histogramas quando a cauda de uma instrução importar. Esta é outra distinção frequentemente perdida: uma consulta lenta pode ter média alta, cauda alta ou apenas custo total enorme por ser frequente. Cada causa requer correção diferente.

Para as principais instruções, execute EXPLAIN (ANALYZE, BUFFERS) com parâmetros seguros e representativos fora do teste cronometrado. ANALYZE executa a instrução, então envolva alterações de dados numa transação revertida ou use uma cópia descartável. Procure grandes diferenças entre linhas estimadas e reais, loops repetidos, varreduras sequenciais em tabelas grandes e seletivas, ordenações que vão para o disco e muitos blocos compartilhados lidos.

Código gerado costuma criar um padrão N+1 que parece bom com dados iniciais: busca 25 projetos e executa uma consulta de proprietário e outra de contagem por projeto. Com dez solicitações por segundo, esse endpoint pode criar mais de 500 instruções no banco por segundo antes que qualquer outra rota funcione. A correção pode ser um join, um WHERE id = ANY($1) em lote ou uma contagem pré-calculada. Elevar o limite do pool mantém o desperdício.

Registre também esperas de bloqueio. Uma consulta rápida isolada pode parar sob atualizações concorrentes no mesmo tenant, conta ou sequência. Se a latência sobe somente no cenário de escrita, examine esperas ativas e limites de transação em vez de acrescentar um índice por reflexo.

A memória precisa estabilizar sob carga constante

Controle o backend gerado
A exportação mantém instrumentação e correções de carga sob seu controle durante o crescimento.

Avalie a memória pelo formato ao longo do tempo, não por um único pico. Um processo Go que sobe no aquecimento e depois oscila perto de um nível estável é diferente de outro cuja linha de base depois da coleta cresce durante duas horas.

Registre memória residente, alocação e objetos do heap Go, número de goroutines, frequência e pausas do coletor e taxa de alocação. A memória do container importa porque o sistema encerra o processo pelo limite, não apenas pelo heap Go. Compare memória depois de coletas com carga parecida. Isso remove boa parte do serrilhado normal e mostra retenção.

Exponha as métricas padrão do runtime Go ou um endpoint protegido de profiling em staging. Tire um perfil do heap perto do início e outro no final, depois compare os locais de alocação retida com go tool pprof. Tire também um perfil de goroutines. Uma contagem crescente pode revelar solicitações presas em canais, corpos de resposta não fechados ou trabalho de fundo iniciado sem cancelamento.

Não defina GOMEMLIMIT exatamente no limite do container. O processo também precisa de memória para pilhas de goroutines, mapeamentos executáveis, buffers do driver e outras alocações fora do heap. Deixe margem e prove-a com os maiores payloads. Um teste com JSON pequeno diz pouco sobre um endpoint que lê um upload de 20 MB para a memória.

Force os casos incômodos: corpos máximos aceitos, resultados grandes, clientes cancelados, timeouts e exportações repetidas. Verifique se a memória volta depois do trabalho. Observe também CPU, pois coleta intensa pode manter a memória sob o limite enquanto destrói a latência.

Uma condição prática combina teto e tendência. Exija que a memória residente fique com segurança abaixo do limite no pico de crescimento e que a linha de base após coleta e o número de goroutines parem de subir durante o teste longo. Não existe porcentagem segura universal. Escolha a margem conforme o reinício da plataforma, a variação do tráfego e a capacidade de outra réplica absorver um reinício.

Falhas incluem respostas erradas e comportamento em sobrecarga

Mantenha o staging realista
A implantação e hospedagem Koder.ai permitem testar a aplicação fora da máquina local.

Conte separadamente falhas de transporte, status HTTP, timeouts, panics e respostas erradas. http_req_failed captura solicitações HTTP com falha segundo o callback do k6, mas um 200 com lista vazia, cobrança duplicada ou registro ausente continua sendo falha. Por isso o exemplo emite business_errors a partir das verificações de conteúdo.

Marque rejeições esperadas, como entrada inválida ou limite intencional, para que não poluam a taxa inesperada. Depois verifique o contrato: status correto, corpo limitado e rejeição rápida. Um sistema sobrecarregado não deve gastar 30 segundos antes de devolver 503.

Procure nos logs recuperação de panic, erros de prazo do contexto, atraso para obter conexão, falhas de serialização do PostgreSQL e consultas canceladas. Agrupe por causa estável, não pela mensagem inteira, para identificadores não criarem milhares de categorias. Guarde traces representativos da primeira ocorrência e da cauda de alta latência.

Execute um teste de degradação depois do teste limpo. Reduza conexões disponíveis, adicione latência controlada a uma dependência externa ou reinicie uma réplica enquanto o tráfego continua. Faça isso apenas no ambiente isolado. O objetivo é confirmar que timeouts, cancelamento e verificações de saúde contêm a falha em vez de permitir que filas consumam todos os recursos.

Configure o servidor HTTP explicitamente. A documentação de net/http do Go diz que valores zero ou negativos de ReadTimeout, WriteTimeout e IdleTimeout podem significar ausência de timeout, dependendo do campo. Serviços gerados costumam chamar http.ListenAndServe com padrões sem tomar a decisão. Um http.Server próprio, prazos por solicitação e corpos limitados impedem clientes lentos e dependências travadas de segurar recursos indefinidamente.

Revise a correção depois da execução. Conte os registros criados, verifique idempotência quando clientes repetiram solicitações, confirme que tarefas em segundo plano terminaram uma vez e que nenhum estado parcial sobreviveu às falhas. Testes de carga encontraram mais bugs de trabalho duplicado nos meus projetos do que revisões de código engenhosas.

O primeiro limite determina a decisão de contratar

Você pode lançar sem engenheiro backend quando o sistema passa repetidamente nos testes esperado e de crescimento, o teste longo atinge uma linha de memória estável, as filas do banco permanecem controladas e a equipe explica a primeira falha de estresse. Uma execução verde por sorte não é evidência. Execute o mesmo commit pelo menos três vezes e investigue variações grandes.

Guarde um registro compacto para cada execução:

  • Commit e ambiente, incluindo tamanhos de réplica e configuração do banco.
  • Escala dos dados, distribuição do tráfego, taxas de chegada e duração.
  • p95 e p99 por rota, throughput atingido, falhas de negócio e iterações descartadas.
  • Picos de CPU e memória, tendência após coleta e tendência das goroutines.
  • Esperas do pool, SQL principal por tempo total, esperas de bloqueio e ponto de ruptura observado.

Contrate ajuda backend antes do lançamento se ninguém consegue explicar esperas crescentes, memória retida, contenção de bloqueios ou escritas inconsistentes. Contrate se a única pessoa capaz de executar o teste não consegue alterar o código gerado com segurança. É uma lacuna de responsabilidade, não um limite de solicitações por segundo.

Um teste reprovado não justifica automaticamente uma vaga integral. Um índice ausente, uma consulta N+1 ou exportação sem limite podem ser correções delimitadas. Falhas repetidas em transações, observabilidade, cancelamento e implantação mostram trabalho de engenharia contínuo. A diferença é se você encontrou um defeito ou descobriu que ninguém responde pelo comportamento do sistema.

Koder.ai pode gerar e exportar um backend Go, implantá-lo e conservar snapshots para rollback, mas a geração não elimina o planejamento de capacidade. Mantenha o script de carga e as mudanças de observabilidade junto do código para que cada alteração importante cumpra o mesmo contrato.

Não prometa que 10.000 usuários mensais são seguros. Prometa uma taxa medida, uma distribuição de rotas, um objetivo de latência, um orçamento de erros e uma faixa de recursos. Quando o produto mudar, altere essas entradas e execute novamente.

Perguntas frequentes

Um backend Go pode suportar 10.000 usuários ativos mensais?

Muitas vezes sim, mas usuários mensais não descrevem a carga backend. Converta a previsão em solicitações de pico por segundo e uma distribuição de rotas, depois teste limites claros de latência, erros, banco e memória.

Quantas solicitações por segundo equivalem a 10.000 usuários mensais?

Não há conversão fixa. Você precisa de sessões por usuário, solicitações por sessão, a parcela de tráfego no período mais movimentado e eventos que façam usuários chegar juntos.

Qual latência p95 é aceitável para uma API Go?

Defina objetivos pela ação do usuário, não pela linguagem ou framework. Leituras interativas podem precisar de algumas centenas de milissegundos e tarefas de fundo de outro alvo, mas escolha antes dos resultados.

Quanto tempo deve durar um teste de carga backend?

Mantenha cada nível estável por pelo menos 30 minutos depois do aquecimento e execute um teste mais longo no pico esperado. Testes curtos podem perder renovação de conexões, memória retida, tarefas de fundo e filas crescentes.

Devo usar usuários virtuais ou taxa de chegada?

Use taxa de chegada para afirmar capacidade porque ela continua oferecendo trabalho quando o serviço fica lento. Um modelo com usuários fixos pode reduzir solicitações durante a lentidão e esconder o crescimento das filas.

Como detectar o esgotamento do pool de conexões Go?

Exporte DB.Stats() e acompanhe InUse, OpenConnections, WaitCount e WaitDuration. Contadores de espera crescendo com conexões no máximo mostram que as solicitações aguardam o pool.

Devo aumentar o pool SQL Go quando há espera?

Não antes de saber por que as conexões permanecem ocupadas e qual capacidade o PostgreSQL possui. Um pool maior pode transferir a fila para o banco e piorar a contenção.

Como achar consultas PostgreSQL lentas durante o teste?

Tire snapshots antes e depois de pg_stat_statements e ordene as diferenças por tempos total e médio. Use traces ou histogramas para a cauda porque a view agregada não fornece p95 por consulta.

Como saber se um serviço Go tem vazamento de memória?

Execute um teste estável e compare memória após coletas sob carga parecida. Uma linha de base que continua subindo, principalmente com objetos ou goroutines crescentes, exige comparar perfis.

Quando uma startup deve contratar um engenheiro backend?

Contrate quando falhas revelarem necessidade contínua de responsabilidade por banco, observabilidade, concorrência, correção ou operação. Um índice isolado talvez não exija vaga integral, mas comportamento inexplicável sob carga exige alguém responsável.

Related posts