Diretrizes de Desenvolvimento
8. Performance e Escalabilidade

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

  1. 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();
  2. 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;
    }
  3. 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']);
    });
  4. Use chunking para grandes conjuntos de dados:

    User::chunk(1000, function ($users) {
        foreach ($users as $user) {
            // Processe cada usuário
        }
    });

Diretrizes para Node.js

  1. 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"
    );
  2. 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 */ }
    });
  3. 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

  1. Use o facade Cache do Laravel:

    $value = Cache::remember('users', 3600, function () {
        return DB::table('users')->get();
    });
  2. Implemente cache em nível de consulta:

    $users = Cache::remember('users.active', 3600, function () {
        return User::where('active', true)->get();
    });
  3. Use tags de cache para invalidação seletiva:

    Cache::tags(['users', 'profiles'])->put('key', $value, 3600);

Diretrizes para Node.js

  1. 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;
    }
  2. 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

  1. Projete para statelessness:

    • Não armazene estado de sessão localmente. Use Redis ou outro armazenamento distribuído para sessões.
  2. 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'
    });
  3. 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:

  1. 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' });
  2. Otimizar assets:

    • Use CDN para servir assets estáticos.
    • Implemente lazy loading para imagens e scripts não críticos.
  3. 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);
  4. 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

  1. 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');
  2. 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');

Diretrizes para monitoramento eficaz

  1. 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' });
  2. Use custom metrics no NewRelic:

    // Laravel
    \NewRelic\NewRelic::recordMetric('Custom/MyMetric', 1.0);
     
    // Node.js
    newrelic.recordMetric('Custom/MyMetric', 1.0);
  3. Implemente tracing distribuído:

    • Use o APM do NewRelic para rastrear transações entre serviços.
  4. Monitore queries lentas:

    • Configure alertas no NewRelic para queries que excedem um limite de tempo.
  5. Implemente dashboards personalizados:

    • Crie dashboards no NewRelic para visualizar métricas críticas de negócio.

Diretrizes adicionais

  1. 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.
  2. 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
      });
    });
  3. Otimização de imagens:

    • Use formatos modernos como WebP.
    • Implemente redimensionamento e compressão automáticos.
  4. Implementação de GraphQL para APIs flexíveis:

    • Considere usar GraphQL para permitir que os clientes solicitem exatamente os dados necessários.
  5. 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');
      });