Fecha: 2026-06-15 Estado: Aceptado Bloque: R7 (reparación post-P5) Origen: docs/ANALISIS_POST_P5.md §3 R7 — tensión #2.5 Refina: ADR-0071 (P5c) y ADR-0074 (R1)

Contexto

Hoy EXPLAIN tiene tres niveles de visibilidad sobre el uso de stats por P5c:

  1. Stats frescas (<7d) → P5c puede decidir skip-index. EXPLAIN dice: (P5c: hash-index 'idx_cat' disponible pero stats prefieren FullScan + post-filter).
  2. Stats stale (≥7d, R1) → P5c bow out automáticamente. EXPLAIN dice: hash-index equality 'cat' [... STALE].
  3. Sin stats → camino indexado siempre. EXPLAIN no anota nada de P5c.

La banda intermedia — stats entre 1 día y 7 días — es un punto ciego. P5c toma la decisión con stats que podrían estar bien o podrían haber quedado desfasadas por inserts/updates posteriores. El usuario no sabe si la decisión P5c es de confiar o si conviene re-ANALYZE.

Tensión #2.5 del análisis post-P5 lo nombra como cosmético, pero tiene costo real: el dev que ve “P5c skip-index” sin contexto puede asumir que “no debería usar índice nunca” cuando la realidad es “no debería usar índice según stats que ya tienen N días”.

Decisión

Cuando el path P5c skip se activa en classify_scan, anotar un sufijo "; sugerencia: re-ANALYZE (stats Xd Yh)" al mensaje SCAN si la edad de las stats está en [24h, 7d). Fuera de esa ventana:

Constante nueva

const STATS_REANALYZE_HINT_SECS: u64 = 24 * 60 * 60;

24h porque es el punto donde típicamente ya cambiaron suficientes filas como para que la heurística pueda haber quedado desfasada. Es menor que los 7d de STATS_STALE_THRESHOLD_SECS (R1) — la idea es que el hint aparece antes de que P5c se bypassee, no después.

Helper nuevo

fn p5c_reanalyze_hint(analyzed_at_nanos: u128) -> String {
    let age = stats_age_secs(analyzed_at_nanos);
    if !(STATS_REANALYZE_HINT_SECS..STATS_STALE_THRESHOLD_SECS).contains(&age) {
        return String::new();
    }
    format!("; sugerencia: re-ANALYZE (stats {})", format_stats_age(age))
}

Integración

En classify_scan, el hint se calcula una vez al detectar p5c_skip_index y se anexa al texto del path P5c en las 5 ramas que existen hoy:

Mensaje resultante con stats 2d:

SCAN `t` (P5c: hash-index `idx_cat` disponible pero stats prefieren
          FullScan + post-filter; sugerencia: re-ANALYZE (stats 2d 0h))
          [est.rows=10 cols=1 est.match=6 stats.age=2d 0h]

Consecuencias

Positivas

Negativas / deuda

Alternativas consideradas

  1. Hint permanente cuando P5c skip dispara (sin umbral de edad). Rechazado: el dev que acaba de correr ANALYZE no necesita el hint.
  2. Hint según delta de row_count vs estimado (en lugar de edad). Más preciso conceptualmente, pero requiere infraestructura nueva (contadores de mod por tabla); fuera de scope para R7.
  3. Hint solo si P5c skip ≠ R6 skip (composite vs single-col). Demasiado sutil — el problema es el mismo en ambos paths.

Tests

Tres tests nuevos (r7_* en tests/integration_test.rs):

Suite total: 798 → 801 (+3). Resto sin cambios.

Referencias