🚧 Troubleshooting Tuesday: Database Performance Debugging - Los 5 Problemas Que Matan la Performance

Los martes nos enfocamos en resolver problemas reales. Hoy analizamos los cuellos de botella de base de datos más comunes que pueden arruinar la performance de una aplicación y las técnicas sistemáticas para identificarlos y resolverlos.

:snail: Problema #1: Queries N+1 - El Asesino Silencioso

:cross_mark: Síntomas:

  • Aplicación lenta en listados

  • Número excesivo de queries en logs

  • Performance que empeora exponencialmente con más datos

:magnifying_glass_tilted_left: Identificación:

-- Lo que vemos en logs (señal de alarma)
SELECT * FROM posts ORDER BY created_at DESC LIMIT 10;
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
SELECT * FROM users WHERE id = 3;
-- ... se repite para cada post

Herramientas de detección:

// En desarrollo - contador de queries
let queryCount = 0;
const originalQuery = db.query;
db.query = function(...args) {
    queryCount++;
    console.log(`Query #${queryCount}:`, args[0]);
    return originalQuery.apply(this, args);
};

:white_check_mark: Soluciones según el ORM:

Eloquent (Laravel):

// ❌ Problema N+1
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->user->name; // Query por cada post
}

// ✅ Solución con Eager Loading
$posts = Post::with('user')->get();
foreach ($posts as $post) {
    echo $post->user->name; // Una sola query adicional
}

Sequelize (Node.js):

// ❌ Problema
const posts = await Post.findAll();
for (const post of posts) {
    const user = await post.getUser(); // N queries
}

// ✅ Solución
const posts = await Post.findAll({
    include: [{ model: User }]
});

Django ORM:

# ❌ Problema
posts = Post.objects.all()
for post in posts:
    print(post.user.name)  # Query por cada post

# ✅ Solución
posts = Post.objects.select_related('user').all()
for post in posts:
    print(post.user.name)  # Una sola query con JOIN

:turtle: Problema #2: Índices Faltantes o Mal Optimizados

:cross_mark: Señales de alerta:

  • Queries que tardan segundos en ejecutarse

  • Full table scans en EXPLAIN plans

  • CPU de base de datos constantemente alto

:magnifying_glass_tilted_left: Debugging sistemático:

PostgreSQL:

-- Identificar queries lentas
SELECT query, mean_time, calls, total_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10;

-- Analizar plan de ejecución
EXPLAIN (ANALYZE, BUFFERS) 
SELECT * FROM users 
WHERE email = 'user@example.com' 
  AND status = 'active';

MySQL:

-- Habilitar slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;

-- Analizar query específica
EXPLAIN FORMAT=JSON
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.created_at > '2025-01-01';

:white_check_mark: Estrategias de indexación:

Índices compuestos estratégicos:

-- Para WHERE + ORDER BY
CREATE INDEX idx_posts_status_created 
ON posts (status, created_at DESC);

-- Para JOIN + filtros
CREATE INDEX idx_orders_user_status 
ON orders (user_id, status, created_at);

-- Para búsquedas de texto (PostgreSQL)
CREATE INDEX idx_posts_title_gin 
ON posts USING gin(to_tsvector('english', title));

Monitoreo de uso de índices:

-- PostgreSQL: Índices no utilizados
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0
AND schemaname = 'public';

-- MySQL: Índices redundantes
SELECT table_name, non_unique, index_name, 
       column_name, cardinality
FROM information_schema.statistics
WHERE table_schema = 'your_database'
ORDER BY table_name, index_name, seq_in_index;

:floppy_disk: Problema #3: Memory y Connection Pool Issues

:cross_mark: Síntomas típicos:

  • “Connection pool exhausted” errors

  • Out of memory errors en base de datos

  • Timeouts intermitentes en aplicación

:magnifying_glass_tilted_left: Diagnosticando connection leaks:

Node.js (Sequelize):

// Monitor de conexiones
const originalQuery = sequelize.connectionManager.getConnection;
sequelize.connectionManager.getConnection = function(options) {
    console.log('Active connections:', this.pool.size);
    console.log('Available connections:', this.pool.available);
    return originalQuery.call(this, options);
};

// Configuración optimizada
const sequelize = new Sequelize(database, username, password, {
    pool: {
        max: 20,        // Máximo de conexiones
        min: 0,         // Mínimo de conexiones  
        acquire: 30000, // Tiempo máximo para obtener conexión
        idle: 10000     // Tiempo antes de cerrar conexión idle
    },
    logging: (sql, timing) => {
        if (timing > 1000) { // Log queries > 1s
            console.warn(`Slow query (${timing}ms):`, sql);
        }
    }
});

Python (Django):

# settings.py - Configuración de pool
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'mydb',
        'OPTIONS': {
            'MAX_CONNS': 20,
            'MIN_CONNS': 1,
        },
        'CONN_MAX_AGE': 600,  # Reutilizar conexiones por 10 min
    }
}

# Debugging de conexiones
import logging
logging.getLogger('django.db.backends').setLevel(logging.DEBUG)

:white_check_mark: Mejores prácticas:

Connection pooling optimizado:

// Configuración balanceada para diferentes entornos
const poolConfig = {
    development: { max: 5, min: 1 },
    staging: { max: 10, min: 2 },
    production: { max: 20, min: 5 }
};

// Monitoring de health del pool
setInterval(() => {
    const pool = sequelize.connectionManager.pool;
    if (pool.size === pool.max) {
        console.warn('Connection pool at maximum capacity');
        // Alertar al sistema de monitoreo
    }
}, 30000);

:bar_chart: Problema #4: Queries Ineficientes y Data Fetching

:cross_mark: Problemas comunes:

  • SELECT * en lugar de campos específicos

  • Lack of pagination en listados grandes

  • Agregaciones costosas sin optimización

:magnifying_glass_tilted_left: Identificación de data over-fetching:

Análisis de transferencia de datos:

-- PostgreSQL: Tamaño de resultados por query
SELECT query, 
       calls,
       total_time,
       mean_time,
       rows,
       100.0 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
FROM pg_stat_statements
WHERE rows > 1000  -- Queries que retornan muchas filas
ORDER BY rows DESC;

:white_check_mark: Optimizaciones específicas:

Paginación eficiente:

-- ❌ OFFSET lento en datasets grandes
SELECT * FROM posts 
ORDER BY created_at DESC 
LIMIT 20 OFFSET 10000;  -- Muy lento

-- ✅ Cursor-based pagination
SELECT * FROM posts 
WHERE created_at < '2025-01-15 10:30:00'
ORDER BY created_at DESC 
LIMIT 20;

-- ✅ Keyset pagination con índice
SELECT * FROM posts 
WHERE id < 12345 
ORDER BY id DESC 
LIMIT 20;

Agregaciones optimizadas:

-- ❌ Agregación costosa
SELECT user_id, COUNT(*) as post_count
FROM posts 
GROUP BY user_id;

-- ✅ Con índice optimizado
CREATE INDEX idx_posts_user_count ON posts (user_id);

-- ✅ Materialized view para agregaciones frecuentes
CREATE MATERIALIZED VIEW user_post_counts AS
SELECT user_id, COUNT(*) as post_count
FROM posts 
GROUP BY user_id;

-- Refresh periódico
REFRESH MATERIALIZED VIEW user_post_counts;

Proyección de columnas:

// ❌ Over-fetching
const users = await User.findAll(); // Trae todas las columnas

// ✅ Solo campos necesarios
const users = await User.findAll({
    attributes: ['id', 'name', 'email']
});

// ✅ Exclude campos pesados
const users = await User.findAll({
    attributes: { exclude: ['password_hash', 'profile_data'] }
});

:counterclockwise_arrows_button: Problema #5: Transacciones Mal Manejadas y Deadlocks

:cross_mark: Síntomas de problemas transaccionales:

  • Deadlock errors esporádicos

  • Transactions que nunca terminan

  • Inconsistencia de datos bajo carga

:magnifying_glass_tilted_left: Debugging de deadlocks:

PostgreSQL:

-- Monitoring de locks activos
SELECT pid, state, query_start, query 
FROM pg_stat_activity 
WHERE state != 'idle'
ORDER BY query_start;

-- Configurar deadlock logging
ALTER SYSTEM SET log_lock_waits = on;
ALTER SYSTEM SET deadlock_timeout = '1s';
SELECT pg_reload_conf();

MySQL:

-- Ver último deadlock
SHOW ENGINE INNODB STATUS\G

-- Monitoring de transacciones largas
SELECT trx_id, trx_started, trx_query
FROM information_schema.innodb_trx
WHERE trx_started < DATE_SUB(NOW(), INTERVAL 30 SECOND);

:white_check_mark: Mejores prácticas transaccionales:

Transacciones optimizadas:

// ❌ Transacción muy larga
const transaction = await sequelize.transaction();
try {
    // Muchas operaciones que toman tiempo
    const user = await User.create(userData, { transaction });
    await sendEmail(user.email); // Operación externa lenta
    await updateStatistics(); // Operación pesada
    await transaction.commit();
} catch (error) {
    await transaction.rollback();
}

// ✅ Transacción mínima y atomic
const transaction = await sequelize.transaction();
try {
    // Solo operaciones DB críticas
    const user = await User.create(userData, { transaction });
    const order = await Order.create(orderData, { transaction });
    await transaction.commit();
    
    // Operaciones externas después del commit
    await sendEmail(user.email);
    await updateStatistics();
} catch (error) {
    await transaction.rollback();
    throw error;
}

Prevención de deadlocks:

// Orden consistente de locks para prevenir deadlocks
async function transferMoney(fromUserId, toUserId, amount) {
    // Siempre lockear IDs en orden ascendente
    const [firstId, secondId] = [fromUserId, toUserId].sort();
    
    const transaction = await sequelize.transaction();
    try {
        const firstUser = await User.findByPk(firstId, { 
            transaction,
            lock: transaction.LOCK.UPDATE 
        });
        const secondUser = await User.findByPk(secondId, { 
            transaction,
            lock: transaction.LOCK.UPDATE 
        });
        
        // Lógica de transferencia
        await transaction.commit();
    } catch (error) {
        await transaction.rollback();
        throw error;
    }
}

:hammer_and_wrench: Herramientas de Monitoring y Debugging

Database-specific tools:

PostgreSQL:

-- Configurar pg_stat_statements
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- Top queries por tiempo total
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 10;

MySQL:

-- Performance Schema básico
SELECT EVENT_NAME, COUNT_STAR, SUM_TIMER_WAIT
FROM performance_schema.events_waits_summary_global_by_event_name
WHERE EVENT_NAME LIKE 'wait/io%'
ORDER BY SUM_TIMER_WAIT DESC;

APM Solutions:

// New Relic monitoring
const newrelic = require('newrelic');

// Custom metrics para DB performance
newrelic.recordMetric('Custom/Database/SlowQuery', queryTime);
newrelic.recordMetric('Custom/Database/ConnectionPoolUsage', 
    pool.size / pool.max * 100);

:bar_chart: Performance Monitoring Dashboard

Métricas clave a trackear:

// Health check comprehensivo
async function databaseHealthCheck() {
    const metrics = {
        connectionPool: {
            active: pool.size,
            idle: pool.available,
            usage: (pool.size / pool.max) * 100
        },
        queryPerformance: {
            avgResponseTime: await getAvgQueryTime(),
            slowQueryCount: await getSlowQueryCount(),
            errorRate: await getQueryErrorRate()
        },
        resources: {
            cpuUsage: await getDbCpuUsage(),
            memoryUsage: await getDbMemoryUsage(),
            diskIO: await getDiskIOStats()
        }
    };
    
    // Alertas automáticas
    if (metrics.connectionPool.usage > 80) {
        alert('Connection pool usage high');
    }
    
    if (metrics.queryPerformance.avgResponseTime > 1000) {
        alert('Average query time too high');
    }
    
    return metrics;
}

:light_bulb: Checklist de Database Performance

Antes de deploy:

  • ¿Todos los queries tienen EXPLAIN plans analizados?

  • ¿Índices apropiados para WHERE y JOIN clauses?

  • ¿Connection pool configurado para carga esperada?

  • ¿Queries paginadas para datasets grandes?

  • ¿Transacciones mínimas y atómicas?

En producción:

  • ¿Monitoring de slow queries activo?

  • ¿Alertas configuradas para métricas críticas?

  • ¿Logs de deadlocks habilitados?

  • ¿Backup y recovery procedures testeados?

:speech_balloon: Tu Experiencia con DB Debugging

¿Cuál ha sido el problema de performance más difícil que han debuggeado?

¿Qué herramientas usan para monitorear bases de datos en producción?

¿Han tenido experiencias con deadlocks o connection leaks? ¿Cómo los resolvieron?

Database performance debugging requiere paciencia y metodología sistemática. La clave está en combinar monitoring proactivo con análisis detallado cuando surgen problemas. Un 80% de los issues de performance suelen estar en un 20% de queries críticos.

Próximo martes: “Troubleshooting de APIs REST - Status codes, rate limiting y error handling” :rocket:

#TroubleshootingTuesday #Database #Performance #SQL debugging #PostgreSQL #MySQL #Optimization