Sobre nós Guias Projetos Contactos
Админка
please wait

Declaração do problema

A sua aplicação está a apresentar tempos de resposta lentos, utilização elevada de recursos ou uma fraca experiência de utilizador. Precisa de identificar estrangulamentos e implementar otimizações de forma sistemática.

Processo de otimização de desempenho

┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Measure │───▶│ Identify │───▶│ Optimize │───▶│ Verify │
│ Baseline │ │ Bottlenecks │ │ Target │ │ Impact │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │
└────────────────────────────────────────────────────────┘
Iterate

1. Desempenho da base de dados

Identificar queries lentas

PostgreSQL

-- Ativar o logging de queries lentas
ALTER SYSTEM SET log_min_duration_statement = '500'; -- Registar queries > 500 ms
SELECT pg_reload_conf();
-- Encontrar queries lentas
SELECT
query,
calls,
total_time / 1000 as total_seconds,
mean_time / 1000 as mean_seconds,
rows
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 20;
-- Encontrar índices em falta
SELECT
schemaname,
relname,
seq_scan,
seq_tup_read,
idx_scan,
idx_tup_fetch,
n_tup_ins,
n_tup_upd,
n_tup_del
FROM pg_stat_user_tables
WHERE seq_scan > 0
ORDER BY seq_tup_read DESC
LIMIT 20;

MySQL

-- Ativar o slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- Encontrar queries lentas usando o Performance Schema
SELECT
DIGEST_TEXT,
COUNT_STAR,
SUM_TIMER_WAIT/1000000000000 as total_seconds,
AVG_TIMER_WAIT/1000000000000 as avg_seconds,
SUM_ROWS_EXAMINED,
SUM_ROWS_SENT
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;

Otimização de queries

Adicionar índices em falta

-- Analisar o plano de execução da query
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE user_id = 123
AND status = 'pending'
AND created_at > '2024-01-01';
-- Criar índice composto
CREATE INDEX CONCURRENTLY idx_orders_user_status_date
ON orders(user_id, status, created_at);
-- Índice parcial para queries comuns
CREATE INDEX CONCURRENTLY idx_orders_pending
ON orders(user_id, created_at)
WHERE status = 'pending';

Otimizar JOINs

-- Mau: Fazer JOIN de tabelas grandes sem índices adequados
SELECT u.*, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id;
-- Melhor: Usar subquery com índice
SELECT u.*,
(SELECT COUNT(*) FROM orders WHERE user_id = u.id) as order_count
FROM users u;
-- Ou usar materialized view para agregações complexas
CREATE MATERIALIZED VIEW user_stats AS
SELECT
user_id,
COUNT(*) as order_count,
SUM(total) as total_spent
FROM orders
GROUP BY user_id;
-- Atualizar periodicamente
REFRESH MATERIALIZED VIEW CONCURRENTLY user_stats;

Pooling de ligações

PgBouncer para PostgreSQL

# pgbouncer.ini
[databases]
myapp = host=localhost port=5432 dbname=myapp
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 3

2. Estratégias de cache

Cache multi-camada

┌─────────────────────────────────────────────────────────────────┐
│ Browser Cache │
│ Static assets, API responses with Cache-Control │
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────┐
│ CDN Cache │
│ Static files, edge caching for API │
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────┐
│ Application Cache │
│ Redis, Memcached, In-memory │
└─────────────────────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────────────────────┐
│ Database Cache │
│ Query cache, buffer pool │
└─────────────────────────────────────────────────────────────────┘

Implementação de cache com Redis

// Exemplo de cache em Laravel
class ProductService
{
public function getProduct($id)
{
return Cache::tags(['products'])->remember(
"product:{$id}",
now()->addMinutes(60),
fn() => Product::with('category', 'images')->findOrFail($id)
);
}
public function getPopularProducts()
{
return Cache::remember(
'products:popular',
now()->addMinutes(15),
fn() => Product::popular()->limit(20)->get()
);
}
public function updateProduct($id, $data)
{
$product = Product::findOrFail($id);
$product->update($data);
// Invalidar caches relacionados
Cache::tags(['products'])->forget("product:{$id}");
Cache::forget('products:popular');
return $product;
}
}
// Exemplo de cache em Node.js
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
class CacheService {
async get(key) {
const data = await redis.get(key);
return data ? JSON.parse(data) : null;
}
async set(key, value, ttl = 3600) {
await redis.setex(key, ttl, JSON.stringify(value));
}
async getOrSet(key, fetchFn, ttl = 3600) {
let data = await this.get(key);
if (data) return data;
data = await fetchFn();
await this.set(key, data, ttl);
return data;
}
async invalidatePattern(pattern) {
const keys = await redis.keys(pattern);
if (keys.length > 0) {
await redis.del(...keys);
}
}
}
// Utilização
const product = await cache.getOrSet(
`product:${id}`,
() => db.products.findUnique({ where: { id } }),
3600
);

Padrão cache-aside

async function getUser(userId) {
const cacheKey = `user:${userId}`;
// Tentar primeiro a cache
let user = await cache.get(cacheKey);
if (!user) {
// Cache miss: ir buscar à base de dados
user = await db.users.findUnique({ where: { id: userId } });
if (user) {
// Guardar na cache
await cache.set(cacheKey, user, 3600);
}
}
return user;
}
// Invalidação de cache na atualização
async function updateUser(userId, data) {
const user = await db.users.update({
where: { id: userId },
data,
});
// Invalidar cache
await cache.delete(`user:${userId}`);
return user;
}

3. Desempenho do frontend

Otimização do bundle

// vite.config.js
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
// Chunk de vendor para dependências que raramente mudam
vendor: ['react', 'react-dom', 'react-router-dom'],
// Chunk da biblioteca de UI
ui: ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
},
},
},
// Ativar minificação e tree-shaking
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
},
// Pré-bundling de dependências
optimizeDeps: {
include: ['react', 'react-dom'],
},
});

Code splitting

// Lazy loading em React
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Reports = lazy(() => import('./pages/Reports'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/reports" element={<Reports />} />
</Routes>
</Suspense>
);
}

Otimização de imagens

// Componente Image do Next.js
import Image from 'next/image';
export function ProductImage({ src, alt }) {
return (
<Image
src={src}
alt={alt}
width={400}
height={300}
loading="lazy"
placeholder="blur"
blurDataURL="/placeholder.png"
sizes="(max-width: 768px) 100vw, 50vw"
/>
);
}
// Otimização manual com srcset
<picture>
<source srcset="image.webp" type="image/webp" />
<source srcset="image.jpg" type="image/jpeg" />
<img
src="image.jpg"
alt="Product"
loading="lazy"
decoding="async"
srcset="image-400.jpg 400w, image-800.jpg 800w"
sizes="(max-width: 600px) 400px, 800px"
/>
</picture>

4. Desempenho da API

Compressão de respostas

// Express com compression
const compression = require('compression');
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) return false;
return compression.filter(req, res);
},
threshold: 1024, // Comprimir apenas respostas > 1 KB
}));

HTTP/2 e keep-alive

# nginx.conf
http {
# Enable HTTP/2
listen 443 ssl http2;
# Keep-alive settings
keepalive_timeout 65;
keepalive_requests 1000;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_types text/plain text/css application/json application/javascript;
# Static file caching
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}

Paginação e seleção de campos

// Seleção de campos em GraphQL
const userResolvers = {
Query: {
users: async (_, { first, after, fields }, { db }) => {
// Selecionar apenas os campos pedidos
const select = buildSelectFromFields(fields);
return db.users.findMany({
take: first,
skip: after ? 1 : 0,
cursor: after ? { id: after } : undefined,
select,
});
},
},
};
// API REST com sparse fieldsets
app.get('/api/users', async (req, res) => {
const fields = req.query.fields?.split(',') || ['id', 'name', 'email'];
const page = parseInt(req.query.page) || 1;
const limit = Math.min(parseInt(req.query.limit) || 20, 100);
const users = await db.users.findMany({
select: Object.fromEntries(fields.map(f => [f, true])),
skip: (page - 1) * limit,
take: limit,
});
res.json({ data: users, meta: { page, limit } });
});

5. Processamento em background

Colocar operações pesadas em filas

// Job em Laravel
class ProcessOrderJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Order $order) {}
public function handle()
{
// Processamento pesado
$this->generateInvoice();
$this->sendNotifications();
$this->updateInventory();
$this->syncExternalSystems();
}
public function failed(\Throwable $exception)
{
Log::error('Order processing failed', [
'order_id' => $this->order->id,
'error' => $exception->getMessage(),
]);
}
}
// Despachar job
ProcessOrderJob::dispatch($order)->onQueue('orders');

Node.js com BullMQ

import { Queue, Worker } from 'bullmq';
const orderQueue = new Queue('orders', {
connection: { host: 'redis', port: 6379 },
});
// Adicionar job
await orderQueue.add('process-order', {
orderId: order.id,
}, {
attempts: 3,
backoff: { type: 'exponential', delay: 1000 },
});
// Worker
const worker = new Worker('orders', async (job) => {
const { orderId } = job.data;
await generateInvoice(orderId);
await sendNotifications(orderId);
await updateInventory(orderId);
return { success: true };
}, {
connection: { host: 'redis', port: 6379 },
concurrency: 5,
});
worker.on('completed', (job) => {
console.log(`Job ${job.id} completed`);
});
worker.on('failed', (job, err) => {
console.error(`Job ${job.id} failed:`, err);
});

Monitorização de desempenho

Métricas-chave a acompanhar

// Métricas personalizadas com prom-client
const histogram = new client.Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP request duration',
labelNames: ['method', 'route', 'status'],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 5],
});
const dbQueryDuration = new client.Histogram({
name: 'db_query_duration_seconds',
help: 'Database query duration',
labelNames: ['operation', 'table'],
buckets: [0.001, 0.01, 0.1, 0.5, 1],
});
const cacheHitRatio = new client.Gauge({
name: 'cache_hit_ratio',
help: 'Cache hit ratio',
});

A mentalidade de desempenho de um senior

Os «primeiros 60 segundos» num servidor em chamas

Quando faz SSH para um servidor sob carga, execute estes comandos por esta ordem:

# 1. Médias de carga (a aumentar ou a diminuir?)
uptime
# 2. Erros do kernel — OOM kills, erros de disco?
dmesg | tail
# 3. Vista global de processos, memória, swap, CPU
vmstat 1
# 4. Que processo está a causar a carga?
pidstat 1
# 5. Latência e saturação do disco
iostat -xz 1
# 6. Utilização de memória e cache
free -m
# 7. Throughput de rede
sar -n DEV 1

Forense de desempenho da base de dados

Quando o pager dispara porque «a base de dados está lenta», precisa de visibilidade imediata:

-- PostgreSQL: O que está a correr AGORA MESMO?
SELECT
pid,
usename,
state,
age(clock_timestamp(), query_start) AS duration,
query
FROM pg_stat_activity
WHERE state <> 'idle'
AND query NOT LIKE '%pg_stat_activity%'
AND age(clock_timestamp(), query_start) > interval '5 seconds'
ORDER BY duration DESC;

Dica de senior: Uma query bloqueada no estado idle in transaction impede o vacuuming, levando ao inchaço das tabelas. Termine estas sessões se excederem um limiar.

Terminar queries zombie

-- Soft Kill (tente isto primeiro — pára a query em segurança)
SELECT pg_cancel_backend(<pid>);
-- Hard Kill (fecha o socket — pode causar 500s se a app não tratar a reconexão)
SELECT pg_terminate_backend(<pid>);

Limites de descritores de ficheiros no Linux

«Too many open files» (EMFILE) é um erro clássico em produção:

# Verificar limites atuais
ulimit -Sn # Limite soft
ulimit -Hn # Limite hard
# Corrigir permanentemente em /etc/security/limits.conf:
* soft nofile 200000
* hard nofile 500000

Desenhar para a falha: circuit breakers

Em sistemas distribuídos, a falha é latente. A rede vai falhar por instantes.

O problema da tempestade de retries:

  • O Serviço A chama o Serviço B. O Serviço B está lento.
  • Ingénuo: o Serviço A faz retry 3 vezes imediatamente.
  • Resultado: o Serviço B estava a aguentar 100 req/s. Agora tem 300 req/s. Falha ainda mais.

Padrão senior: exponential backoff + jitter

  • Esperar 1s, depois 2s, depois 4s.
  • Adicionar ruído aleatório (jitter) para que todos os clientes não façam retry em simultâneo.

Máquina de estados do circuit breaker: 1. Closed (Normal): Os pedidos passam. 2. Open (Broken): Taxa de erro > 50%. Falhar imediatamente. Não chamar o serviço a jusante. 3. Half-Open (Testing): Após o timeout, deixar passar 1 pedido. Sucesso = Close. Falha = Open novamente.

Bibliotecas: Resilience4j (Java), Polly (.NET), opossum (Node.js).

Bulkheads: isolar falhas

Se «Image Processing» usa 100% das threads, «Login» não deve cair.

Solução: Thread pools por dependência.

  • Connection Pool A (Payment): 10 threads
  • Connection Pool B (Images): 10 threads
  • Se o Pool B encher, o Pool A não é afetado

Checklist de desempenho

Base de dados

  • [ ] Logging de queries lentas ativado
  • [ ] Índices em falta identificados e adicionados
  • [ ] Pooling de ligações configurado (PgBouncer, etc.)
  • [ ] Resultados de queries colocados em cache de forma adequada
  • [ ] Queries N+1 eliminadas
  • [ ] Sessões idle in transaction monitorizadas e terminadas

Aplicação

  • [ ] Cache de respostas implementada
  • [ ] Operações pesadas movidas para filas
  • [ ] Memory leaks identificados e corrigidos
  • [ ] Async/await usado corretamente
  • [ ] Circuit breakers em dependências externas
  • [ ] Exponential backoff com jitter nos retries

Frontend

  • [ ] Tamanho do bundle otimizado
  • [ ] Code splitting implementado
  • [ ] Imagens otimizadas e carregadas de forma lazy
  • [ ] CSS crítico inline
  • [ ] Cache do service worker

Infraestrutura

  • [ ] CDN configurada para assets estáticos
  • [ ] HTTP/2 ativado
  • [ ] Compressão Gzip/Brotli ativada
  • [ ] Keep-alive de ligações configurado
  • [ ] Limites de descritores de ficheiros aumentados
  • [ ] Limites de recursos do sistema ajustados

Artigos relacionados na wiki

 
 
 
Языки
Темы
Copyright © 1999 — 2026
ZK Interactive