Coisas que ninguem sabe sobre o funcionamento real de sistemas que você usa todo dia
Vou ser direto. A maioria das pessoas que trabalha com tecnologia repete conceitos que nunca testou em condições reais. Eu já vi engenharia inteira ficar parada porque alguém seguiu um tutorial da internet sem entender o que estava acontecendo por baixo do capô. Isso é comum demais. O que eu vou escrever aqui são detalhes que praticamente ninguém menciona em tutoriais. Não porque sejam secretos, mas porque raramente valem a pena documentar. São coisas que aparecem quando algo quebra às 3 da manhã e você precisa resolver sem ajuda.
Coisas que ninguem sabe sobre como buffers realmente funcionam
Quando você chama uma função de leitura de arquivo, o sistema operacional não vai diretamente ao disco. Ele passa pelo buffer do kernel primeiro. Isso é documentado. O que poucas pessoas sabem é que o tamanho desse buffer não é fixo — ele se adapta dinamicamente baseado na carga do sistema, no tipo de mídia e até na hora do dia em determinados sistemas Linux com kernel 5.15 e superiores. Eu tive esse problema na prática há cerca de dois anos. Estava processando arquivos de log de aproximadamente 40 GB em um servidor de aplicação rodando Ubuntu com kernel 5.19. O script simplesmente travava em 73% de processamento. Nada nos logs indicava erro. Memory usage estava normal. CPU idle. O sistema parecia estar respirando bem e mesmo assim não avançava.
A solução veio de observar o comando vfs_cache_pressure em /proc/sys/vm/. O valor padrão é 100, mas naquele servidor estava em 312. O kernel estava descartando entradas do cache de forma agressiva, o que fazia com que as chamadas de E/S fossem reprocessadas repetidamente em vez de aproveitarem o buffer. Mudei para 50, adicionei uma linha no sysctl.conf e o processamento passou de 6 horas para 47 minutos. Sem alterar uma única linha do código. Isto é um exemplo típico de coisa que ninguem sabe mas que faz diferença crítica. O buffer existe, mas o que controla seu comportamento muitas vezes está escondido em parâmetros que ninguém revisa depois da instalação.
Sobre como DNS realmente resolve nomes na prática
Todo mundo sabe que DNS traduz nomes em IPs. O que pouca gente entende é o mecanismo de stub resolver versus recursive resolver e como a falha em um deles pode criar atrasos de vários segundos que parecem problemas de rede quando na verdade são problemas de configuração local. Em sistemas Windows, o service called DNS Client (Dnscache) mantém um cache que pode ficar corrompido sem gerar qualquer mensagem de erro visível. Eu passei três dias investigando lentidão em requisições HTTP internas numa rede corporativa. Todos apontavam para o servidor de aplicação. Na realidade, cada requisição esperava 5 segundos timeout no stub resolver local antes de cair no recursive. Resolução: flush do cache DNS com ipconfig /flushdns e ajuste da interface para usar o DNS interno da empresa diretamente, sem passar pelo resolução encadeada padrão.
O que acontece de verdade quando um conntrack table estoura
Este é provavelmente o ponto mais negligenciado em ambientes Linux que rodam como firewall ou proxy. A tabela conntrack armazena o estado de cada conexão. Quando ela enche, novas conexões são simplesmente descartadas. O serviço não cai. A aplicação não mostra erro. Os pacotes simplesmenteparam de entrar. Em junho de 2024, lidamos com um serviço de API que intermitentemente rejeitava conexões sem motivo aparente. Os logs de aplicação estavam limpos. O servidor respondia ping. Mas solicitações POST simplesmente não eram recebidas. Identificamos o problema executando conntrack -L | wc -l e vendo que a tabela estava em 98% da capacidade máxima de 65536 entries. O valor máximo estava configurado manualmente para aquele limite há anos, sem justificativa.
Ajustamos net.netfilter.nf_conntrack_max para 524288, aumentamos o timeout de TCP CLOSE_WAIT e o problema parou. A latência média caiu de 340 ms para 12 ms nas mesmas requisições. Sem update de software. Sem mudança de hardware.
Thresholds de timeout que ninguém configura e que quebram deploys
Load balancers têm tempos de espera para novas conexões, para keep-alive, para health checks. A configuração padrão quase sempre é inadequada para aplicações modernas que fazem warm-up de JVM, carregamento de modelos de ML ou inicialização de contêineres com camadas pesadas. Um caso prático: estávamos fazendo deploy canário de um microsserviço Python com dependências pesadas. O health check do load balancer estava configurado com timeout de 5 segundos. O serviço levava aproximadamente 12 segundos para responder corretamente pela primeira vez após o início. O LB marcava o pod como unhealthy, removía da rotação e reiniciava. O serviço entrava num loop infinito de restart. Levou quatro horas para alguém notar que o problema não era o código, mas o threshold de tempo configurado no balanceador.
Aumentar o initialDelaySeconds e o periodSeconds no health check resolveu. Simples, mas o tipo de problema que aparece em produção e consome dias.
👉 Clique no botão abaixo para saber mais sobre o assunto!
Coisas que ninguem sabe sobre garbage collection e memory leaks reais
Memory leak em Java não significa sempre que o código está errado. Significa que o GC não está conseguindo liberar objetos porque ainda existe pelo menos uma referência ativa, direta ou indireta, apontando para eles. ClassLoader leaks, por exemplo, são responsáveis por grande parte dos OOM em servidores que rodaram deployments repetidos sem restart. Cada deploy cria um novo ClassLoader. Se ele não é liberado, todas as classes carregadas ficam presas na memória. Em nossa pipeline de CI, tínhamos um teste de integração que iniciava um Spring Boot embutido por execução. O teste rodava 30 vezes. Após a décima sétima execução, o heap atingia o limite e o teste falhava intermitentemente. O erro parecia aleatório. Usamos o JProfiler e identificamos que o contexto do Spring não estava sendo fechado adequadamente entre execuções, mantendo referências aos ClassLoaders. Adicionar uma chamada explícita para close() no ApplicationContext dentro do @After resolveu. O uso de memória ficou estável em 180 MB em vez de subir para 1,4 GB.
Diferença entre latência e throughput que ninguém explica direito
Latência é o tempo que uma única requisição leva do início ao fim. Throughput é quantas requisições são processadas por segundo. Elas não são a mesma coisa e otimizá-las exige estratégias diferentes. Aumentar threads em um servidor bloqueante pode melhorar throughput até certo ponto e piorar latência simultaneamente, porque as threads competem por recursos. Temos um serviço de filas que processava pedidos. Inicialmente, aumentamos o pool de threads de 20 para 100 esperando ganho de throughput. O throughput subiu 40%. A latência média puxada para cima em 3 segundos porque threads ociosas geravam overhead de scheduling e contenção no lock da fila. Reduzimos para 35 threads e ajustamos o modelo para processamento assíncrono com callbacks. Latência voltou a 200 ms e throughput permaneceu em 95% do pico anterior.
O problema de N+1 que ninguém associa a queries aninhadas
O problema N+1 em ORMs é amplamente conhecido. O que poucos compreendem é que ele também acontece em consultas que usam JOINs implícitos via lazy loading. Você faz uma query que retorna 50 registros. Cada registro dispara uma query adicional quando uma propriedade relacionada é acessada. 50 querys extras. Muitas vezes invisíveis nos logs porque cada uma retorna resultados pequenos e rápidos individualmente. Em um relatório que gerava exportações CSV com aproximadamente 2000 registros, o tempo de geração passava de 4 segundos para 47 segundos quando uma relação era acessada. A solução foi adicionar um fetch join explícito na query do repositório. Tempo voltou a 5 segundos. A diferença é que o desenvolvedor que escreveu a query original não tinha consciência de que estava usando lazy loading naquela parte do código.
Como logs de produção mentem para você
Logs são a primeira ferramenta de debug e também a mais confiável até o momento em que algo está errado. Um log pode registrar que uma requisição foi recebida, mas não dizer que o payload chegou corrompido porque o content-type estava inconsistent. Já vi tickets abertos e fechados baseados em logs que não capturavam o verdadeiro estado dos dados porque o framework de logging estava configurado para nível INFO e ignorava objetos grandes por padrão. A solução prática é configurar logging de debug seletivo em ambientes de staging com dados sintéticos idênticos aos de produção. Isso custa recursos mas identifica problemas que logs em produção nunca mostram. Um erro de serialização JSON que afetava 3% das requisições levou seis dias para ser reproduzido porque os logs padrão nunca registravam o corpo da resposta.
Sincronização de relógio e por que sistemas distribuídos falham nela
Em sistemas distribuídos, a ordem dos eventos depende do relógio. Se dois nós têm diferença de 200 ms, eventos que ocorreram na ordem correta em um nó podem aparecer invertidos em outro. NTP corrige isso na maioria dos casos, mas microflutuações de 50 a 100 ms são comuns e suficientes para quebrar lógica de consistência eventual. Em um sistema de transações financeiras que usava Kafka como broker, observamos ordens de pagamento chegando invertidas em 0,7% dos casos. O problema não estava no código de ordenação. Estava na diferença de timestamp entre os produtores. Um deles rodava em VM com clock sync desabilitado por configuração antiga. Ativamos o NTP, corrigimos a configuração e o problema eliminou. A verificação inicial falhou porque o monitoramento de latência não incluía drift de relógio entre nós.
Cache invalidation é mais difícil do que parece
Existem apenas duas coisas difíceis em ciência da computação: invalidação de cache e naming de coisas. Isso não é piada. É a definição exata do problema. Invalidar cache na hora certa exige conhecer todas as dependências de um dado. Na prática, sistemas reais têm dependências em camadas que ninguém mapeia documentadamente. Em um sistema de e-commerce, o preço de um produto era cacheado por 15 minutos em Redis. Quando o preço era atualizado no banco, o cache era limpo. Parecia correto. O problema era que um serviço de recomendação lia o preço do cache diretamente sem passar pelo validador. Quando o preço era atualizado via API administrativa, o cache era limpo, mas o serviço de recomendação reconstruía o cache com o preço antigo porque o validador ainda não tinha sido chamado. Preços errados apareciam na tela por até 15 minutos após qualquer atualização.
A correção foi implementar invalidação push: toda atualização de preço disparava um evento que limpava todas as chaves relacionadas em todos os serviços consumidores, não apenas no cache principal. Resolveram-se os 15 minutos de inconsistência. A complexidade adicionada no fluxo de atualização foi aceitável considerando o impacto em produção.
Como medir coisa que não tem métrica óbvia
Existem métricas que todo mundo acompanha: uptime, latência, taxa de erro. Existem outras que ninguém acompanha e que são igualmente importantes. Tempo médio até recuperação, por exemplo. Quantos minutos passam entre o início de um incidente e o retorno ao funcionamento normal. Essa métrica revela se seu processo de incidentes funciona independentemente de você ter muitos ou poucos incidentes. Outra métrica negligenciada é a proporção de mudanças que causaram rollback. Se 15% dos seus deploys precisam ser desfeitos, você tem um problema de qualidade que não aparece em nenhuma dashboard de uptime ou erro.
Não existe fórmula única. O que funciona em sistemas batch não funciona em sistemas interativos. O que funciona em APIs não funciona em filas. A lição prática é simples: escolha três métricas que realmente reflitam o comportamento do seu sistema e monitore-as diariamente. O resto é ruído. Isso é basicamente o que separa alguém que opera sistemas de alguém que apenas assiste dashboards. A diferença não é conhecimento teórico. É experiência com falhas específicas que não estão em nenhum documento.