SYSTEM DESIGN · PERFORMANCE · LEITURA DE ~8 MIN

Improper
Instantiation

Uma conversa real sobre o antipadrão de recriar objetos caros — como o HttpClient — a cada requisição, e por que isso pode esgotar sockets e derrubar o throughput da aplicação.

Diagrama de Improper Instantiation: muitas requisições criando HttpClient e sockets demais, em contraste com uma instância reutilizada com menor custo.
IMPROPER INSTANTIATION · QUANDO O NEW A CADA REQUEST VIRA CUSTO

PERGUNTA 01

MARCELO

Resume esse artigo aí — o que é esse tal de Improper Instantiation?

A RESPOSTA

É o antipadrão de arquitetura que consiste em criar repetidamente objetos caros, ou objetos que foram projetados para serem reutilizados. A ideia central é simples: se um objeto foi feito para ser compartilhado, ficar criando e destruindo novas instâncias dele a cada requisição pode destruir a performance da aplicação.

O exemplo clássico é o HttpClient. Este código parece inocente:

C#PARECE INOCENTE
using (var httpClient = new HttpClient())
{
    return await httpClient.GetStringAsync(url);
}

Mas em uma aplicação web isso cria um HttpClient novo a cada requisição. Sob carga alta, o servidor pode esgotar sockets e começar a gerar SocketException.

O mesmo vale pra qualquer objeto caro de inicializar, mesmo sem sockets envolvidos:

PADRÃO RUIMA CADA REQUEST
Request
   ↓
new ServiçoCaro()
   ↓
configura/conecta/inicializa
   ↓
usa
   ↓
descarta

Pagar esse custo de novo a cada requisição reduz throughput e aumenta latência. A solução é reutilizar a instância quando o objeto for compartilhável e thread-safe:

PADRÃO BOMUMA VEZ SÓ
Aplicação inicia
      ↓
cria HttpClient UMA VEZ
      ↓
 ┌────┼────┐
 ↓    ↓    ↓
Req1 Req2 Req3
 └────┼────┘
      ↓
mesma instância

Dependendo do recurso, a solução pode ser um singleton ou um pool de objetos reutilizáveis. Mas existe uma ressalva importante: isso não é "transforme tudo em singleton". O antipadrão só existe quando você recria algo que poderia ser compartilhado — e o objeto precisa ser seguro para concorrência. Recursos escassos, como conexões de banco, normalmente fazem mais sentido com pooling do que com uma instância única guardada pra sempre.

  • Sintomas: queda de throughput e aumento de erros sob carga.
  • Sintomas: esgotamento de sockets, conexões ou file handles.
  • Sintomas: mais uso de memória e mais atividade de garbage collection.
  • Sintomas: aumento de atividade de rede, disco ou banco sem motivo aparente.
CritérioRecriando a cada requestReutilizando
Limite antes de erro~30 usuários concorrentesnão atingido no teste
Carga sustentadadegrada com erros~500 req/s sem erro
Tempo médio de respostalinha de basecerca da metade
MENTAL MODEL PRA SYSTEM DESIGN

Objeto caro + usado com frequência + thread-safe → pense em reutilização ou pooling, não em new a cada request.

PERGUNTA 02

MARCELO

Oxi, e qual a vantagem de fazer essa destruição do HttpClient toda hora? HttpClient é tipo um portal de comunicação com outro server, né?

A RESPOSTA

É isso mesmo: dá pra pensar no HttpClient como um canal, um portal que o código usa pra fazer requisições HTTP a outros servidores. E destruir esse canal toda hora normalmente não traz vantagem nenhuma.

O artigo não fala de "servidor 1 falando com servidor 2" — ele fala de um erro de programação: criar objetos de comunicação caros repetidamente quando eles poderiam ser reutilizados.

SEU BACKENDCHAMANDO OUTRO SERVIDOR
marcelomurilo.com (backend)
        |
        | HttpClient
        v
api.openai.com

O problema do artigo é fazer isto sob carga:

ERRADOUM CANAL POR REQUEST
Request 1 chega → cria HttpClient → chama API externa → destrói
Request 2 chega → cria OUTRO       → chama              → destrói
Request 3 chega → cria OUTRO       → chama              → destrói

O recomendado é mais parecido com isto — o mesmo canal fazendo várias chamadas:

CERTOUM CANAL, VÁRIAS CHAMADAS
marcelomurilo.com (backend)
          |
          v
     HttpClient
     único / reutilizado
      /        \
     v          v
OpenAI API   GitHub API

Vale desfazer uma confusão comum aqui: rotas como /conteudos e /en/conteudos não são "servidores separados", cada um com seu próprio HttpClient. Na prática são só rotas diferentes do mesmo site:

ROTASMESMO SITE
marcelomurilo.com
   |
   ├── /conteudos
   └── /en/conteudos

O HttpClient só entra em cena se alguma dessas rotas precisar buscar dado em outro serviço:

FLUXOONDE O HTTPCLIENT ENTRA
Usuário
  ↓
marcelomurilo.com/conteudos
  ↓
seu backend
  ↓
HttpClient
  ↓
API externa

Em uma frase: quando o programa precisa de um objeto caro como o HttpClient pra acessar outros serviços, não crie um novo a cada requisição — reutilize quando for seguro.

PERGUNTA 03

MARCELO

O que é esgotamento de sockets? Socket = meia?

A RESPOSTA

Não, socket não é meia nesse contexto. Em redes, socket é basicamente um ponto de comunicação entre dois computadores ou processos.

Ilustração com o trocadilho socket não é meia: uma meia de tênis aparece separada por um X de dois servidores conectados por um socket de rede.
SOCKET ≠ MEIA · NO ARTIGO, O ASSUNTO É REDE
REDESOCKET
Seu servidor
   |
   | socket
   v
Outro servidor

Quando o backend faz uma chamada HTTP, o sistema operacional usa sockets por baixo dos panos pra manter a comunicação de rede. E existem limites: quantos sockets ou conexões podem ficar abertos, ou em determinados estados, ao mesmo tempo.

Se o código cria e destrói HttpClient demais, acaba abrindo conexões demais em sequência. O sistema fica sem recursos disponíveis e aparecem erros como SocketException — exatamente um dos sintomas que o artigo destaca.

PERGUNTA 04

MARCELO

Entendi. Então se eu não destruo a conexão do HttpClient, quais são os trade-offs?

A RESPOSTA

Existem, sim. Compartilhar uma única instância exige que ela seja thread-safe. O HttpClient foi projetado pra esse uso, mas nem todo objeto foi.

Também pode dar problema se você ficar alterando propriedades globais desse HttpClient compartilhado enquanto várias requisições acontecem ao mesmo tempo — mudar DefaultRequestHeaders a cada request, por exemplo, pode causar condição de corrida.

E nem todo recurso deve ser mantido pra sempre. O próprio artigo cita conexões de banco: deixar uma conexão aberta sem necessidade pode impedir que outros usuários consigam usar o banco. Nesses casos, pool costuma fazer mais sentido do que uma instância única.

Então a regra não é "nunca destrua nada". É mais ou menos assim:

Diagrama de trade-offs: objetos baratos podem ser recriados, objetos caros e thread-safe devem ser reutilizados, e recursos limitados devem usar pool.
TRADE-OFFS · RECRIAR, REUTILIZAR OU USAR POOL
  • Objeto barato e descartável → pode criar e destruir normalmente.
  • Objeto caro, compartilhável e thread-safe → reutilize a mesma instância.
  • Recurso limitado e concorrente (ex.: conexão de banco) → considere um pool.

PERGUNTA 05

MARCELO

Isso que o artigo explica era mais um hábito de programador, então?

A RESPOSTA

Sim — Improper Instantiation é essencialmente sobre um hábito comum. O raciocínio costuma ser: "criei o objeto, usei, não preciso mais, vou destruir pra liberar memória." Isso normalmente parece boa prática.

Só que, para objetos como o HttpClient, que encapsulam recursos caros e conexões externas, fazer criar → usar → destruir mil vezes pode ser muito pior do que criar uma vez → usar mil vezes.

E uma pequena correção conceitual importante: o HttpClient não é exatamente "a conexão". Ele é mais um gerenciador de comunicação HTTP que, internamente, pode abrir e reutilizar conexões e sockets:

HIERARQUIAO QUE ESTAVA FALTANDO
HttpClient
   ↓
gerencia requisições HTTP
   ↓
conexões
   ↓
sockets
   ↓
rede

É exatamente essa camada de gerenciamento — e a reutilização que ela permite — que o artigo original quer preservar.

FIM · OBRIGADO POR LER

Objeto caro + reutilizável → não recrie a cada request.

Improper Instantiation é gastar CPU, memória, conexões e tempo recriando objetos que poderiam simplesmente ser reutilizados. Guarde o modelo mental: objeto caro, usado com frequência e thread-safe pede reutilização ou pooling — não um new a cada requisição.

LER BACK PRESSURE VOLTAR AOS CONTEÚDOS