8. Performance e Escalabilidade
A criação de aplicações de alta performance e altamente escaláveis é crucial para o sucesso de nossos produtos. Esta seção fornece diretrizes detalhadas para desenvolvedores sobre como otimizar o desempenho e garantir a escalabilidade em nosso ambiente de produção.
8.1. Otimização de consultas de banco de dados
A otimização de consultas de banco de dados é fundamental para a performance geral da aplicação.
Diretrizes para Laravel
-
Use Eloquent com cautela:
- Prefira consultas otimizadas do Query Builder para operações complexas.
// Evite: $users = User::all()->where('active', true)->take(10); // Prefira: $users = User::where('active', true)->take(10)->get(); -
Eager Loading para evitar o problema N+1:
- Sempre use
with()para carregar relacionamentos antecipadamente.
// Evite: $books = Book::all(); foreach ($books as $book) { echo $book->author->name; } // Prefira: $books = Book::with('author')->get(); foreach ($books as $book) { echo $book->author->name; } - Sempre use
-
Utilize índices adequadamente:
- Adicione índices para colunas frequentemente usadas em cláusulas WHERE, ORDER BY e JOIN.
Schema::table('users', function (Blueprint $table) { $table->index('email'); $table->index(['status', 'created_at']); }); -
Use chunking para grandes conjuntos de dados:
User::chunk(1000, function ($users) { foreach ($users as $user) { // Processe cada usuário } });
Diretrizes para Node.js
-
Use ORM com sabedoria:
- Para consultas complexas, considere usar consultas SQL brutas otimizadas.
// Usando Sequelize const users = await User.findAll({ where: { status: 'active' }, limit: 10, include: [{ model: Profile, required: true }] }); // Para consultas mais complexas, considere SQL bruto const [results, metadata] = await sequelize.query( "SELECT users.*, profiles.* FROM users INNER JOIN profiles ON users.id = profiles.user_id WHERE users.status = 'active' LIMIT 10" ); -
Implemente paginação:
const pageSize = 20; const page = req.query.page || 1; const offset = (page - 1) * pageSize; const users = await User.findAndCountAll({ limit: pageSize, offset: offset, where: { /* suas condições */ } }); -
Use índices e explique consultas:
- Regularmente use EXPLAIN para analisar e otimizar consultas.
const [results, metadata] = await sequelize.query( "EXPLAIN SELECT * FROM users WHERE email = 'example@email.com'" ); console.log(results);
8.2. Caching
O caching é essencial para reduzir a carga no banco de dados e melhorar os tempos de resposta.
Diretrizes para Laravel
-
Use o facade Cache do Laravel:
$value = Cache::remember('users', 3600, function () { return DB::table('users')->get(); }); -
Implemente cache em nível de consulta:
$users = Cache::remember('users.active', 3600, function () { return User::where('active', true)->get(); }); -
Use tags de cache para invalidação seletiva:
Cache::tags(['users', 'profiles'])->put('key', $value, 3600);
Diretrizes para Node.js
-
Use o cliente node-redis:
const redis = require('redis'); const client = redis.createClient({ host: process.env.REDIS_HOST, port: process.env.REDIS_PORT }); async function getCachedData(key, fetchFunction) { const cachedData = await client.get(key); if (cachedData) { return JSON.parse(cachedData); } const freshData = await fetchFunction(); await client.set(key, JSON.stringify(freshData), 'EX', 3600); return freshData; } -
Implemente cache em nível de API:
app.get('/api/users', async (req, res) => { const users = await getCachedData('api:users', async () => { return await User.findAll(); }); res.json(users); });
8.3. Balanceamento de carga
Embora o balanceamento de carga seja principalmente uma preocupação de infraestrutura, os desenvolvedores devem projetar aplicações para suportá-lo efetivamente.
Diretrizes gerais
-
Projete para statelessness:
- Não armazene estado de sessão localmente. Use Redis ou outro armazenamento distribuído para sessões.
-
Use UUIDs para identificadores:
- Evite depender de IDs auto-incrementais que podem causar conflitos em múltiplas instâncias.
// Laravel use Illuminate\Database\Eloquent\Concerns\HasUuids; class User extends Model { use HasUuids; } // Node.js const { v4: uuidv4 } = require('uuid'); const user = await User.create({ id: uuidv4(), name: 'John Doe' }); -
Implemente health checks:
- Crie endpoints de health check para que o balanceador de carga possa monitorar a saúde da aplicação.
// Laravel Route::get('/health', function () { return response()->json(['status' => 'healthy']); }); // Node.js/Express app.get('/health', (req, res) => { res.json({ status: 'healthy' }); });
8.4. Escalabilidade horizontal e vertical
A escalabilidade é crucial para lidar com o crescimento do tráfego e dos dados.
Diretrizes para escalabilidade:
-
Projetar para concorrência:
- Use filas para tarefas assíncronas e demoradas.
// Laravel Job::dispatch($data); // Node.js com Bull const Queue = require('bull'); const myQueue = new Queue('my-queue', 'redis://127.0.0.1:6379'); myQueue.add({ foo: 'bar' }); -
Otimizar assets:
- Use CDN para servir assets estáticos.
- Implemente lazy loading para imagens e scripts não críticos.
-
Implementar rate limiting:
// Laravel Route::middleware('throttle:60,1')->group(function () { Route::get('/api/endpoint', function () { // }); }); // Node.js/Express com express-rate-limit const rateLimit = require("express-rate-limit"); const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutos max: 100 // limite cada IP a 100 requisições por janela }); app.use("/api/", apiLimiter); -
Usar filas de eventos:
- Implemente SQS ou SNS para processamento assíncrono de eventos.
// Node.js com AWS SDK const AWS = require('aws-sdk'); const sqs = new AWS.SQS({apiVersion: '2012-11-05'}); const params = { MessageBody: JSON.stringify({ foo: 'bar' }), QueueUrl: "SQS_QUEUE_URL" }; sqs.sendMessage(params, (err, data) => { if (err) console.log(err, err.stack); else console.log(data); });
8.5. Monitoramento e análise de performance
O monitoramento contínuo é essencial para identificar e resolver problemas de performance.
Integração do NewRelic
-
Para Laravel:
- Instale o pacote NewRelic via Composer:
composer require newrelic/newrelic-laravel - Adicione o middleware NewRelic no arquivo
app/Http/Kernel.php:protected $middleware = [ // ... \NewRelic\Laravel\Middleware\NewRelicMiddleware::class, ]; - Configure as transações personalizadas:
\NewRelic\NewRelic::setTransactionName('CustomTransaction');
- Instale o pacote NewRelic via Composer:
-
Para Node.js:
- Instale o pacote NewRelic:
npm install newrelic - Adicione no topo do seu arquivo principal:
require('newrelic'); - Configure transações personalizadas:
const newrelic = require('newrelic'); newrelic.setTransactionName('CustomTransaction');
- Instale o pacote NewRelic:
Diretrizes para monitoramento eficaz
-
Implemente logging detalhado:
// Laravel Log::info('Operação concluída', ['user_id' => $user->id, 'action' => 'purchase']); // Node.js const winston = require('winston'); const logger = winston.createLogger({/* configuração */}); logger.info('Operação concluída', { user_id: user.id, action: 'purchase' }); -
Use custom metrics no NewRelic:
// Laravel \NewRelic\NewRelic::recordMetric('Custom/MyMetric', 1.0); // Node.js newrelic.recordMetric('Custom/MyMetric', 1.0); -
Implemente tracing distribuído:
- Use o APM do NewRelic para rastrear transações entre serviços.
-
Monitore queries lentas:
- Configure alertas no NewRelic para queries que excedem um limite de tempo.
-
Implemente dashboards personalizados:
- Crie dashboards no NewRelic para visualizar métricas críticas de negócio.
Diretrizes adicionais
-
Otimização de frontend:
- Minimize e concatene arquivos CSS e JavaScript.
- Use lazy loading para imagens e componentes não críticos.
- Implemente service workers para caching de assets no cliente.
-
Uso eficiente de WebSockets:
- Use WebSockets para comunicação em tempo real, mas com cuidado para não sobrecarregar o servidor.
// Node.js com Socket.io const io = require('socket.io')(server); io.on('connection', (socket) => { console.log('Novo cliente conectado'); socket.on('event', (data) => { // Lógica de evento }); }); -
Otimização de imagens:
- Use formatos modernos como WebP.
- Implemente redimensionamento e compressão automáticos.
-
Implementação de GraphQL para APIs flexíveis:
- Considere usar GraphQL para permitir que os clientes solicitem exatamente os dados necessários.
-
Uso de streams para processamento de grandes volumes de dados:
const fs = require('fs'); const csv = require('csv-parser'); fs.createReadStream('big_file.csv') .pipe(csv()) .on('data', (row) => { // Processe cada linha }) .on('end', () => { console.log('CSV file successfully processed'); });