Projects Query Stats Report¶
Tabla: Projects
Proyecto: Calmia Nexus
Fecha: 2026-02-01
Estado: Análisis inicial
1. Resumen Ejecutivo¶
| Métrica | Valor |
|---|---|
| Sequential Scans | 524,856 |
| Index Hit Rate | 0.1% |
| Diagnóstico | CRÍTICO: Index hit rate < 1% |
| Acción Requerida | Crear índices para patrones de consulta frecuentes |
2. Estructura de la Entidad¶
Archivo: Shared/Shared.Admin/Entities/Project.cs
| Columna | Tipo | Uso en Consultas |
|---|---|---|
Id |
Guid | PK, FindAsync |
Slug |
string | WHERE (único) |
Name |
string | ORDER BY |
Description |
string? | - |
RepoPath |
string? | - |
IsActive |
bool | WHERE filter |
CreatedAt |
DateTime | - |
UpdatedAt |
DateTime | - |
OrganizationId |
Guid? | WHERE filter (multi-tenant) |
3. Índices Actuales¶
Basado en NexusDbContext.cs:
| Índice | Columna(s) | Tipo |
|---|---|---|
Projects_pkey |
Id | PRIMARY KEY |
IX_Projects_Slug |
Slug | UNIQUE |
| (FK automático) | OrganizationId | BTREE |
Problema: Solo hay 2-3 índices, pero las consultas usan combinaciones de columnas no indexadas.
4. Patrones de Consulta Identificados¶
4.1 Alta Frecuencia (cada request)¶
| Patrón | Archivo | Línea | Columnas | Índice Usado |
|---|---|---|---|---|
| Lista proyectos por organización | ProjectsController.cs |
58-101 | OrganizationId, IsActive, Name | NINGUNO (Seq Scan) |
| Multi-tenant filter | ProjectsController.cs |
71 | OrganizationId IS NULL OR IN(...) | NINGUNO |
SQL Equivalente:
SELECT * FROM "Projects"
WHERE ("OrganizationId" IS NULL OR "OrganizationId" IN (...))
AND "IsActive" = true
ORDER BY "Name";
4.2 Media Frecuencia¶
| Patrón | Archivo | Línea | Columnas | Índice Usado |
|---|---|---|---|---|
| Buscar por Slug | ProjectsController.cs |
188 | Slug | IX_Projects_Slug ✓ |
| Buscar por ID | ProjectsController.cs |
112, 160, 237 | Id | PK ✓ |
| Verificar slug único | ProjectsController.cs |
204 | Slug | IX_Projects_Slug ✓ |
4.3 Baja Frecuencia¶
| Patrón | Archivo | Línea | Columnas | Índice Usado |
|---|---|---|---|---|
| Delete con Include | ProjectsController.cs |
273-276 | Id | PK ✓ |
| Listar activos | ProjectMetricsService.cs |
48 | IsActive | NINGUNO |
| Verificar proyectos por org | OrganizationService.cs |
154 | OrganizationId | Parcial |
5. Análisis del Problema¶
Causa del Bajo Index Hit Rate¶
- Consulta principal (
GetAll) ejecuta Seq Scan porque: - Filtro
OrganizationId IS NULL OR OrganizationId IN (...)no puede usar índice simple - Combinación
IsActive + ORDER BY Nameno tiene índice -
Sin índice en
OrganizationId + Namepara ORDER BY -
Volumen de llamadas:
- 524,856 seq scans sugiere ~500K llamadas sin índice
-
Cada vista de lista de proyectos genera un seq scan
-
Tamaño de tabla pequeño:
- PostgreSQL puede preferir Seq Scan en tablas pequeñas
- Si hay < 100 proyectos, el optimizador puede ignorar índices
6. Consultas Frecuentes Detalladas¶
Consulta 1: Listado Multi-tenant (CRÍTICA)¶
Ubicación: ProjectsController.cs:58-101
var query = _context.Projects
.Include(p => p.Organization)
.AsQueryable();
// Multi-tenant filtering
query = query.Where(p =>
p.OrganizationId == null ||
accessibleOrgIds.Contains(p.OrganizationId.Value));
if (isActive.HasValue)
query = query.Where(p => p.IsActive == isActive.Value);
if (organizationId.HasValue)
query = query.Where(p => p.OrganizationId == organizationId.Value);
var projects = await query
.OrderBy(p => p.Name)
.Select(...)
.ToListAsync();
SQL Generado:
SELECT p.*, o.*
FROM "Projects" p
LEFT JOIN "Organizations" o ON p."OrganizationId" = o."Id"
WHERE (p."OrganizationId" IS NULL OR p."OrganizationId" IN (@p0, @p1, ...))
AND p."IsActive" = @isActive
ORDER BY p."Name"
Problema: OR condition + ORDER BY Name = Seq Scan garantizado.
Consulta 2: Filtro por organización específica¶
Ubicación: ProjectsController.cs:79-80
Índice Recomendado:
Consulta 3: Proyectos activos¶
Ubicación: ProjectMetricsService.cs:48
Índice Recomendado:
7. Índices Recomendados¶
P1 - Alta Prioridad¶
| Índice | Definición | Consulta Optimizada |
|---|---|---|
IX_Projects_Org_Name |
("OrganizationId", "Name") |
WHERE OrgId ORDER BY Name |
IX_Projects_OrgNull_Name |
("Name") WHERE "OrganizationId" IS NULL |
Proyectos sin organización |
P2 - Media Prioridad¶
| Índice | Definición | Consulta Optimizada |
|---|---|---|
IX_Projects_Active_Name |
("Name") WHERE "IsActive" = true |
Proyectos activos ordenados |
IX_Projects_Org_Active_Name |
("OrganizationId", "IsActive", "Name") |
Filtro combinado completo |
P3 - Baja Prioridad (ya existen)¶
| Índice | Estado |
|---|---|
IX_Projects_Slug |
✓ Existe (UNIQUE) |
Projects_pkey |
✓ Existe (PK) |
8. Script SQL de Estadísticas¶
Archivo: scripts/database/projects-query-analysis.sql
Ejecutar para obtener métricas actuales:
# En PostgreSQL
psql -d nexus_db -f scripts/database/projects-query-analysis.sql > projects_query_stats.log 2>&1
El script incluye: 1. Estadísticas generales de la tabla 2. Listado de índices y su uso (idx_scan) 3. Definición de índices existentes 4. Índices no utilizados 5. Análisis de columnas indexadas 6. EXPLAIN ANALYZE de consultas típicas 7. Recomendaciones de índices 8. Resumen ejecutivo
9. Próximos Pasos¶
- Ejecutar script de análisis en entorno de desarrollo
- Verificar estadísticas reales de idx_scan
- Crear índices P1 como primera optimización
- Medir impacto después de aplicar índices
- Documentar resultados en ADR
10. Referencias¶
- Entidad:
Shared/Shared.Admin/Entities/Project.cs - DbContext:
Shared/Shared.Admin/Data/NexusDbContext.cs:109-120 - Controller:
Orchestrator/src/Orchestrator.Api/Controllers/ProjectsController.cs - Servicios:
ProjectMetricsService.csOrganizationService.csUserContextService.cs