Parte de nuestra serie Performance & Scalability
Leer la guía completaUn solo índice faltante puede convertir una consulta de 2 milisegundos en un escaneo de tabla de 20 segundos. A medida que su base de datos crece de miles a millones de filas, la diferencia entre una consulta optimizada y no optimizada es la diferencia entre una aplicación responsiva y una que agota el tiempo de espera bajo carga. La optimización de la base de datos ofrece el mayor retorno del tiempo de ingeniería de cualquier trabajo de rendimiento que pueda realizar.
Conclusiones clave
- EXPLAIN ANALYZE es su herramienta de diagnóstico más poderosa: aprenda a leer los planes de ejecución antes de optimizar cualquier cosa.
- Elija tipos de índice estratégicamente: árbol B para igualdad y rango, GIN para texto completo y JSONB, índices parciales para subconjuntos filtrados
- Las consultas N+1 son el factor de deterioro del rendimiento más común en aplicaciones basadas en ORM: detectelas temprano con el registro de consultas.
- La partición de tablas se vuelve esencial cuando las tablas superan entre 10 y 50 millones de filas, lo que reduce el tiempo de planificación de consultas y permite una gestión eficiente del ciclo de vida de los datos.
Lectura de planes de ejecución con EXPLAIN ANALYZE
Antes de optimizar cualquier consulta, debe comprender cómo la ejecuta PostgreSQL actualmente. EXPLAIN ANALYZE ejecuta la consulta y muestra el plan de ejecución real con datos de tiempo reales.
Un resultado básico de EXPLAIN ANALYZE le muestra la estrategia elegida por el planificador, los recuentos de filas estimados versus reales y el tiempo dedicado a cada paso. Las métricas clave en las que centrarse son:
- Seq Scan: la base de datos lee cada fila de la tabla. Aceptable para tablas pequeñas (menos de 10.000 filas), pero es una señal de alerta para las más grandes.
- Escaneo de índice: la base de datos utiliza un índice para encontrar filas coincidentes de manera eficiente. Esto es lo que desea para consultas filtradas en tablas grandes.
- Escaneo solo de índice: la base de datos responde la consulta completamente desde el índice sin tocar la tabla. El tipo de escaneo más rápido.
- Bucle anidado: une tablas escaneando la tabla interior una vez por fila en la tabla exterior. Eficiente cuando el escaneo interno utiliza un índice.
- Unión hash: crea una tabla hash desde un lado de la unión y luego la prueba con el otro. Eficiente para conjuntos de resultados más grandes.
- Ordenar: un paso de clasificación explícito, a menudo para ORDENAR POR. Esté atento a las clasificaciones que se derraman en el disco (indicadas por "Método de clasificación: combinación externa").
Qué buscar
La señal más importante en un plan de ejecución es la brecha entre las filas estimadas y reales. Cuando PostgreSQL estima 10 filas pero encuentra 100.000, eligió el plan equivocado. Esto sucede cuando las estadísticas de la tabla están obsoletas: ejecute ANALYZE en la tabla para actualizarlas.
Esté atento a escaneos secuenciales en tablas grandes, ordenaciones sin índices y bucles anidados con escaneos secuenciales en la tabla interna. Cada uno de estos patrones indica que falta un índice o que es necesario reescribir una consulta.
Tipos de índices y cuándo usarlos
PostgreSQL ofrece varios tipos de índices, cada uno optimizado para diferentes patrones de consulta. Elegir el tipo correcto es fundamental: un índice GIN en una columna que solo necesita comprobaciones de igualdad desperdicia almacenamiento y ralentiza las escrituras sin mejorar las lecturas.
| Tipo de índice | Mejor para | Caso de uso de ejemplo | Gastos generales de almacenamiento |
|---|---|---|---|
| Árbol B (predeterminado) | Igualdad, rango, clasificación, prefijo LIKE | DONDE estado = 'activo', DONDE creado_en > '2026-01-01' | Bajo a moderado |
| Hachís | Sólo igualdad (sin rango) | WHERE uuid = '...' (raro, el árbol B suele ser suficiente) | Bajo |
| GIN (Generalizado Invertido) | Búsqueda de texto completo, contención JSONB, matrices | WHERE etiquetas @> '\\\\\\\\{urgente\\\\\\\\}', WHERE documento @@ to_tsquery('término de búsqueda') | Alto |
| GiST (árbol de búsqueda generalizada) | Datos geométricos, tipos de rango, vecino más cercano | DONDE ubicación <-> punto (x,y), DONDE rango de fechas && '[2026-01-01, 2026-03-01]' | Moderado |
| BRIN (Índice de rango de bloques) | Datos ordenados naturalmente (marcas de tiempo, secuencias) | DONDE creado_en ENTRE '2026-01-01' Y '2026-01-31' en tablas de solo anexar | Muy bajo |
| Parcial | Subconjuntos de datos filtrados | WHERE estado = 'pendiente' (indexar solo filas pendientes) | Bajo |
Índices de árbol B
El árbol B es el tipo de índice predeterminado y más versátil. Admite igualdad (=), rango (<, >, ENTRE), clasificación (ORDER BY) y coincidencia de patrones de prefijo (LIKE 'abc%'). Para la mayoría de las columnas de las cláusulas WHERE, JOIN y ORDER BY, un índice de árbol B es la opción correcta.
Los índices compuestos combinan varias columnas en un único árbol B. El orden de las columnas es importante: el índice en (estado, creado_at) admite de manera eficiente el filtrado de consultas solo por estado o tanto por estado como por creado_at, pero no solo por creado_at. Coloque la columna más selectiva primero y la columna utilizada para el filtrado de rango al final.
Índices GIN
Los índices GIN destacan en la búsqueda dentro de valores compuestos. Son esenciales para la búsqueda de texto completo (columnas tsvector), consultas de contención JSONB (@>, ?) y consultas de superposición de matrices (&&, @>). Los índices GIN son más grandes y más lentos de actualizar que los índices del árbol B, así que utilícelos solo cuando el árbol B no pueda atender el patrón de consulta.
Para las columnas JSONB que almacenan atributos flexibles, un índice GIN en toda la columna admite cualquier consulta basada en claves. Para las columnas en las que solo se consultan claves específicas, un índice de árbol B en una columna o expresión generada es más eficiente.
Índices parciales
Los índices parciales solo indexan filas que coinciden con una condición WHERE. Son potentes para tablas donde las consultas filtran constantemente un pequeño subconjunto de datos.
Por ejemplo, si su tabla de pedidos tiene 10 millones de filas pero consulta casi exclusivamente pedidos activos (5% de la tabla), un índice parcial en (customer_id, create_at) WHERE status = 'active' es 20 veces más pequeño que un índice completo e igual de rápido para sus consultas reales.
Detección y reparación de consultas N+1
El problema de consulta N+1 es el problema de rendimiento más común en aplicaciones que utilizan ORM. Ocurre cuando el código carga una lista de N registros y luego ejecuta una consulta adicional por registro para cargar datos relacionados, lo que da como resultado N+1 consultas en total en lugar de 1-2.
Cómo ocurren las consultas N+1
Considere cargar una lista de pedidos con los nombres de sus clientes. Una implementación ingenua carga la lista de pedidos (1 consulta), luego, para cada pedido, carga el cliente (N consultas). Con 100 pedidos, esto genera 101 viajes de ida y vuelta a la base de datos. A 1 ms por consulta, es decir, 101 ms, pero bajo carga simultánea con contención del grupo de conexiones, puede llegar fácilmente a 500 ms o más.
Métodos de detección
- Registro de consultas: habilite el registro de consultas de PostgreSQL temporalmente y busque consultas idénticas repetidas con diferentes valores de parámetros
- Registro a nivel de ORM: Drizzle ORM, Prisma y TypeORM admiten el registro de consultas que muestra cada instrucción SQL ejecutada.
- Herramientas APM: Datadog, New Relic y Sentry pueden agrupar consultas por punto final y resaltar patrones N+1 automáticamente
- pg_stat_statements: esta extensión de PostgreSQL rastrea las estadísticas de ejecución de consultas y revela plantillas de consultas idénticas ejecutadas con frecuencia.
Corrección de consultas N+1
La solución depende de su ORM y patrón de consulta:
- Carga ansiosa: indique al ORM que cargue datos relacionados en la consulta inicial utilizando JOIN. En Drizzle, use la opción
withen los creadores de consultas. - Carga por lotes: recopile todos los ID de clave externa y luego cargue los registros relacionados en una única consulta WHERE id IN (...). Este es el patrón DataLoader.
- Desnormalización: para casos de uso con mucha lectura, almacene los datos relacionados directamente en el registro principal. Cambie la complejidad de escritura por el rendimiento de lectura.
Técnicas de reescritura de consultas
A veces, la consulta en sí necesita una reestructuración, no solo mejores índices.
Subconsulta para UNIRSE a la conversión
Las subconsultas correlacionadas se ejecutan una vez por fila en la consulta externa. Convertirlos a JOIN permite a PostgreSQL utilizar estrategias de unión más eficientes.
En lugar de seleccionar pedidos con una subconsulta que busca la fecha del último pedido por cliente, reescríbalo como JOIN con una tabla derivada o una función de ventana. La versión JOIN permite a PostgreSQL elegir entre bucle anidado, unión hash y unión fusionada según la distribución de datos.
Expresiones de tabla comunes (CTE)
En PostgreSQL 12 y versiones posteriores, los CTE están integrados de forma predeterminada, lo que significa que el optimizador puede insertar predicados en ellos. Utilice CTE para mejorar la legibilidad sin preocuparse por las barreras de rendimiento. Para los casos en los que desee explícitamente la materialización (para evitar la reejecución de costosas subconsultas), agregue la palabra clave MATERIALIZED.
Funciones de ventana frente a GROUP BY
Cuando necesita filas de detalles y agregados, las funciones de ventana evitan la necesidad de una autounión o una subconsulta. Calcular un total acumulado, clasificar dentro de grupos o comparar cada fila con el promedio del grupo son más eficientes con funciones de ventana que con subconsultas correlacionadas.
Estrategias de partición de tablas
Cuando las tablas superan los 10-50 millones de filas, incluso las consultas bien indexadas se ralentizan debido a la profundidad del índice, la sobrecarga del vacío y la complejidad del planificador. La partición divide una tabla grande en partes físicas más pequeñas mientras se mantiene una única interfaz de tabla lógica.
Tipos de partición
| Estrategia | Mecanismo | Mejor para |
|---|---|---|
| División de rango | Partición por rangos de valores (rangos de fechas, rangos de ID) | Datos de series temporales, registros, pedidos por fecha |
| Partición de listas | Partición por valores discretos | Datos de múltiples inquilinos por id_organización, pedidos por región |
| Partición hash | Partición por hash de una columna | Distribución uniforme cuando no existe un rango natural o clave de lista |
División de rango por fecha
El patrón más común es la partición mensual mediante una columna de marca de tiempo. Los datos de cada mes viven en su propia partición. Las consultas que filtran por fecha escanean automáticamente solo las particiones relevantes (poda de partición).
Beneficios de la partición basada en el tiempo:
- Rendimiento de consultas: las consultas de datos recientes solo analizan particiones recientes
- Mantenimiento -- VACUUM y ANALYZE se ejecutan más rápido en particiones más pequeñas
- Ciclo de vida de los datos: eliminar particiones antiguas es instantáneo en comparación con eliminar millones de filas.
- Eficiencia de la copia de seguridad: haga una copia de seguridad solo de las particiones recientes para una recuperación en un momento dado
Consideraciones de partición
La partición añade complejidad. Cada consulta debe incluir la clave de partición en su cláusula WHERE para que funcione la poda de partición. Las restricciones únicas deben incluir la clave de partición. Las claves externas que hacen referencia a tablas particionadas tienen limitaciones. Comience a particionar sólo cuando haya medido que el tamaño de la tabla está provocando una degradación del rendimiento.
Ajuste de configuración de PostgreSQL
Default PostgreSQL configuration is conservative, designed to run on minimal hardware. Las cargas de trabajo de producción se benefician del ajuste de parámetros clave.
| Parámetro | Predeterminado | Recomendado (servidor de 16 GB de RAM) | Propósito |
|---|---|---|---|
| buffers_compartidos | 128MB | 4GB (25% de RAM) | Caché en memoria para datos de tablas e índices |
| tamaño_caché_efectivo | 4GB | 12 GB (75% de RAM) | Sugerencia del planificador para la disponibilidad de caché de archivos del sistema operativo |
| memoria_trabajo | 4 MB | 64MB | Memoria por operación de clasificación/hash (cuidado con la concurrencia) |
| mantenimiento_trabajo_mem | 64MB | 1GB | Memoria para VACÍO, CREAR ÍNDICE, ALTERAR TABLA |
| costo_página_aleatoria | 4.0 | 1.1 (almacenamiento SSD) | Estimación de costos para E/S aleatorias (menor para SSD) |
| concurrencia_io_efectiva | 1 | 200 (almacenamiento SSD) | Operaciones de E/S simultáneas para escaneos de montón de mapas de bits |
| conexiones_max | 100 | 200 (con PgBouncer) | Utilice la agrupación de conexiones para mantener esto razonable |
Estas configuraciones deben ajustarse a su hardware y carga de trabajo específicos. Supervise pg_stat_bgwriter, pg_stat_activity y pg_stat_user_tables para validar que los cambios mejoren el rendimiento.
Preguntas frecuentes
¿Cuántos índices debe tener una tabla?
No hay un límite fijo, pero cada índice ralentiza las operaciones INSERTAR, ACTUALIZAR y ELIMINAR porque se debe mantener el índice. Una buena regla general es crear índices para las columnas que aparecen en las cláusulas WHERE, JOIN ON y ORDER BY de sus consultas más frecuentes. Utilice pg_stat_user_indexes para buscar índices no utilizados que se puedan eliminar.
¿Debo usar UUID o claves primarias enteras para mejorar el rendimiento?
Las claves primarias enteras (BIGSERIAL) son más rápidas para uniones e indexación porque son más pequeñas (8 bytes frente a 16 bytes) y están ordenadas de forma natural. Los UUID proporcionan singularidad global sin coordinación, lo cual es importante para los sistemas distribuidos. Para la mayoría de las aplicaciones, utilice UUID para identificadores externos y números enteros para uniones internas.
¿Cuándo debo cambiar de una base de datos única para leer réplicas?
Cuando su carga de trabajo de lectura excede el 70-80% de la capacidad de su base de datos, o cuando las consultas de informes compiten con las consultas transaccionales por los recursos. Las réplicas de lectura manejan la carga de lectura, mientras que la principal se centra en las escrituras. Por lo general, esto es necesario entre 5000 y 10 000 usuarios simultáneos para una aplicación web típica.
¿Cómo manejo consultas lentas en producción sin tiempo de inactividad?
Cree índices con la opción CONCURRENTLY para evitar bloquear la tabla. Utilice pg_stat_statements para identificar las consultas más lentas. Implemente optimizaciones de consultas detrás de indicadores de funciones. Para cambios de esquema que reescriben tablas, utilice herramientas como pg_repack para reorganizar tablas sin bloquearlas.
¿Qué sigue?
La optimización de la base de datos es la base del rendimiento de la plataforma. Comience habilitando pg_stat_statements, identifique sus consultas más lentas y trabaje con ellas sistemáticamente con EXPLAIN ANALYZE. Agregue índices faltantes, corrija patrones N+1 y considere particionar sus tablas más grandes.
Para obtener un panorama más amplio del rendimiento, consulte nuestra guía fundamental sobre ampliar su plataforma empresarial desde una startup hasta una empresa. Para obtener información sobre la siguiente capa de optimización, lea nuestra guía sobre estrategias de almacenamiento en caché con Redis, CDN y almacenamiento en caché HTTP.
ECOSIRE proporciona optimización de bases de datos experta para plataformas respaldadas por PostgreSQL, incluido Odoo ERP y aplicaciones personalizadas. Contáctenos para una auditoría de rendimiento de la base de datos.
Publicado por ECOSIRE: ayuda a las empresas a escalar con soluciones impulsadas por IA en Odoo ERP, Shopify eCommerce y OpenClaw AI.
Escrito por
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
ECOSIRE
Haga crecer su negocio con ECOSIRE
Soluciones empresariales en ERP, comercio electrónico, inteligencia artificial, análisis y automatización.
Artículos relacionados
Requisitos de alojamiento de Odoo en 2026: tamaño del servidor por número de usuarios (con configuraciones reales)
Requisitos de alojamiento de Odoo por número de usuarios: vCPU, RAM, almacenamiento y configuración de trabajadores para 5 a más de 250 usuarios, además de valores de ajuste de PostgreSQL de implementaciones reales.
Optimización de la velocidad de Shopify: una lista de verificación técnica que realmente mueve los elementos básicos de la web (2026)
Una lista de verificación de velocidad de Shopify probada en campo para 2026: qué realmente mejora LCP, INP y CLS en tiendas reales, qué es una pérdida de tiempo y cómo auditar aplicaciones y temas.
Odoo 19 RRHH: Matriz de Habilidades, Planes de Carrera, Ciclos de Desempeño
Actualización de recursos humanos de Odoo 19: matriz de habilidades nativas, planificación de trayectoria profesional, ciclos de revisión del desempeño, cuadrícula de 9 casillas, planificación de sucesión, integración HRIS.
Más de Performance & Scalability
Optimización de la velocidad de Shopify: una lista de verificación técnica que realmente mueve los elementos básicos de la web (2026)
Una lista de verificación de velocidad de Shopify probada en campo para 2026: qué realmente mejora LCP, INP y CLS en tiendas reales, qué es una pérdida de tiempo y cómo auditar aplicaciones y temas.
Lista de verificación de auditoría técnica de SEO 2026: 47 comprobaciones que realizamos en el sitio de cada cliente
La lista de verificación de auditoría técnica de SEO de 47 puntos que ejecutamos en el sitio de cada cliente en 2026: rastreabilidad, indexación, canónicos, hreflang, Core Web Vitals y registros.
Odoo 19 RRHH: Matriz de Habilidades, Planes de Carrera, Ciclos de Desempeño
Actualización de recursos humanos de Odoo 19: matriz de habilidades nativas, planificación de trayectoria profesional, ciclos de revisión del desempeño, cuadrícula de 9 casillas, planificación de sucesión, integración HRIS.
Puntos de referencia de rendimiento de Odoo 19: números de ajuste de PostgreSQL 17
Puntos de referencia de rendimiento de Odoo 19 en el mundo real: velocidad del cliente web, rendimiento de ORM, configuración de ajuste de PG17, agrupación de conexiones, recuento de trabajadores, umbrales de escala.
Optimización de costos de OpenClaw y eficiencia de tokens a escala
Optimización de costos de tokens OpenClaw: almacenamiento en caché de avisos, enrutamiento de modelos, almacenamiento en caché de respuestas, API por lotes y barreras de costos por inquilino para agentes de producción.
Actualización incremental de Power BI para tablas de más de 10 millones de filas
Guía de actualización incremental de Power BI para tablas de más de 10 millones de filas: diseño de particiones, RangeStart/RangeEnd, políticas de actualización, plegado de consultas e híbridos de DirectQuery.