Do básico ao avançado: técnicas para escalar aplicações web
Domine os conceitos de system design: microserviços, load balancing, caching, sharding e arquiteturas distribuídas para aplicações que suportam milhões de usuários.
Entenda como evoluir de uma arquitetura básica para uma estrutura resiliente e escalável por meio da separação de responsabilidades e eliminação de pontos únicos de falha.
Aplicação e banco de dados devem estar em servidores distintos. Isso evita disputa por recursos e melhora a escalabilidade individual de cada parte.
Uma arquitetura robusta deve ter capacidade de failover automático com múltiplas instâncias para tolerância a falhas.
Componentes cruciais para rodar aplicações robustas e altamente escaláveis
Crie múltiplas instâncias da sua aplicação para distribuir requisições e eliminar gargalos
Distribui o tráfego entre servidores automaticamente e melhora a performance
Evite que falhas em um ponto prejudiquem toda a aplicação
Use as estratégias certas no momento certo para dar um passo além em sua arquitetura.
Adiciona mais recursos (CPU, memória) ao servidor. Funciona por um tempo, mas tem limite físico e não elimina pontos únicos de falha.
Duplica instâncias da aplicação em servidores diferentes para distribuir requisições e garantir alta disponibilidade.
Implementações reais de padrões de escalabilidade usando Node.js, Docker e Kubernetes.
# nginx.conf
upstream backend {
server 192.168.1.10:3000 weight=3;
server 192.168.1.11:3000 weight=2;
server 192.168.1.12:3000 weight=1;
server 192.168.1.13:3000 backup;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Health check
proxy_connect_timeout 3s;
proxy_send_timeout 3s;
proxy_read_timeout 3s;
}
Configuração de load balancer com peso diferenciado e servidor backup para failover automático. Inclui health checks e timeouts para detecção rápida de falhas.
const redis = require('redis');
const client = redis.createCluster({
rootNodes: [
{ host: 'redis-1.cluster.local', port: 6379 },
{ host: 'redis-2.cluster.local', port: 6379 },
{ host: 'redis-3.cluster.local', port: 6379 }
],
defaults: {
socket: {
reconnectStrategy: (retries) => Math.min(retries * 50, 500)
}
}
});
// Cache com TTL automático
const cacheUser = async (userId, userData) => {
const key = `user:${userId}`;
await client.setEx(key, 3600, JSON.stringify(userData)); // 1 hora
};
// Cache invalidation pattern
const invalidateUserCache = async (userId) => {
const patterns = [`user:${userId}`, `user:${userId}:*`];
for (const pattern of patterns) {
const keys = await client.keys(pattern);
if (keys.length > 0) await client.del(keys);
}
};{
Sistema de cache distribuído com cluster Redis, incluindo estratégias de TTL, invalidação de cache e recuperação automática de conexões.
// consul-service.js
const consul = require('consul')();
const registerService = async (name, port) => {
const serviceId = `${name}-${process.env.HOSTNAME}`;
await consul.agent.service.register({
id: serviceId,
name: name,
port: port,
address: process.env.HOSTNAME,
check: {
http: `http://${process.env.HOSTNAME}:${port}/health`,
interval: '10s',
timeout: '3s',
deregistercriticalserviceafter: '30s'
}
});
console.log(`Service ${name} registered with ID: ${serviceId}`);
};
const discoverService = async (serviceName) => {
const services = await consul.health.service(serviceName);
return services[0].map(s => ({
host: s.Service.Address,
port: s.Service.Port
}));
};
class CircuitBreaker {
constructor(options = {}) {
this.failureThreshold = options.failureThreshold || 5;
this.resetTimeout = options.resetTimeout || 10000;
this.failureCount = 0;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = Date.now();
}
async call(fn, ...args) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn(...args);
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failureCount++;
if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.resetTimeout;
}
}
}
// sharding-strategy.js
const getShardKey = (userId) => {
// Consistent hashing para distribuição uniforme
const hash = require('crypto').createHash('md5').update(userId.toString()).digest('hex');
return parseInt(hash.substring(0, 8), 16) % SHARD_COUNT;
};
const shardConfig = {
0: { host: 'db-shard-0.cluster.local', port: 5432 },
1: { host: 'db-shard-1.cluster.local', port: 5432 },
2: { host: 'db-shard-2.cluster.local', port: 5432 },
3: { host: 'db-shard-3.cluster.local', port: 5432 }
};
const getConnection = (userId) => {
const shardId = getShardKey(userId);
return connectionPool[shardId];
};
// Query cross-shard para agregações
const aggregateAcrossShards = async (query) => {
const promises = Object.keys(shardConfig).map(shardId => {
const conn = connectionPool[shardId];
return conn.query(query);
});
const results = await Promise.all(promises);
return results.reduce((acc, result) => acc.concat(result.rows), []);
};
const dbConfig = {
master: { host: 'db-master.cluster.local', port: 5432 },
replicas: [
{ host: 'db-replica-1.cluster.local', port: 5432 },
{ host: 'db-replica-2.cluster.local', port: 5432 },
{ host: 'db-replica-3.cluster.local', port: 5432 }
]
};
class DatabaseRouter {
constructor() {
this.replicaIndex = 0;
this.healthCheck();
}
async write(query, params) {
return await this.masterConnection.query(query, params);
}
async read(query, params) {
const replica = this.getHealthyReplica();
try {
return await replica.query(query, params);
} catch (error) {
console.warn('Replica failed, falling back to master');
return await this.masterConnection.query(query, params);
}
}
getHealthyReplica() {
// Round-robin entre réplicas saudáveis
const healthy = this.replicas.filter(r => r.healthy);
if (healthy.length === 0) return this.masterConnection;
const replica = healthy[this.replicaIndex % healthy.length];
this.replicaIndex++;
return replica;
}
async healthCheck() {
setInterval(async () => {
for (const replica of this.replicas) {
try {
await replica.query('SELECT 1');
replica.healthy = true;
} catch (error) {
replica.healthy = false;
}
}
}, 5000);
}