Partir el backfill en dos fases para que no muera a mitad

La ejecución real falló con "MySQL server has gone away" tras escribir 4.522
de 5.663: leer los 228.887 registros tarda más de media hora, y para cuando
empezaba a escribir el servidor ya había cerrado esa conexión. El singleton
de Database no sabe reconectar.

Ahora la lectura guarda el mapa en disco y termina; la segunda ejecución lo
toma de la caché en un segundo y escribe con la conexión sana. Reintentar
sale gratis, que era el otro problema: cada intento costaba media hora.

También informa cuántos teléfonos tienen más de un BSUID. La simulación
decía cero conflictos, pero no podía detectarlos: como no escribe, el bsuid
del usuario siempre estaba vacío y la comparación nunca daba positivo. Al
aplicar de verdad aparecieron. Ahora se cuentan al leer, sin depender de
lo ya escrito.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-11 21:08:14 -05:00
co-authored by Claude Opus 5
parent 6b74db3e4f
commit ffb8c3c934
+52 -4
View File
@@ -11,23 +11,42 @@
*
* Es idempotente: se puede correr las veces que haga falta.
*
* La lectura del histórico tarda más de media hora (son ~230.000 registros de
* un servidor remoto), y mantener la conexión abierta todo ese rato la mataba
* antes de empezar a escribir: "MySQL server has gone away". Por eso el mapa se
* guarda en disco y la escritura usa una conexión nueva. De paso, reintentar
* sale gratis: si el mapa ya está en caché, no se vuelve a leer la tabla.
*
* Uso:
* php scripts/backfill_bsuid.php --simular (no escribe, solo informa)
* php scripts/backfill_bsuid.php
* php scripts/backfill_bsuid.php --releer (ignora la caché y relee el histórico)
*/
require_once __DIR__ . '/../config/config.php';
$simular = in_array('--simular', $argv, true);
$releer = in_array('--releer', $argv, true);
echo $simular ? "Modo simulación: no se escribe nada.\n\n" : "Aplicando cambios.\n\n";
$db = Database::getInstance();
$cache = sys_get_temp_dir() . '/backfill_bsuid_mapa.json';
// --- 1. Recorrer el histórico y armar el mapa BSUID → teléfono ---
echo "Leyendo webhook_logs...\n";
$mapa = [];
$sinTelefono = [];
if (!$releer && is_readable($cache)) {
$guardado = json_decode(file_get_contents($cache), true) ?: [];
$mapa = $guardado['mapa'] ?? [];
$sinTelefono = $guardado['sinTelefono'] ?? [];
printf("Mapa tomado de la caché (%s, del %s).\n", basename($cache), date('Y-m-d H:i', filemtime($cache)));
printf("Para releer el histórico: --releer\n\n");
}
if (!$mapa) {
echo "Leyendo webhook_logs (tarda; es una sola vez)...\n";
$db = Database::getInstance();
// Una sola pasada sin buffer: son más de 400.000 registros, así que ni caben
// en memoria de golpe ni conviene reconsultar la tabla por bloques (cada bloque
// volvía a recorrerla entera y tardaba una eternidad).
@@ -82,15 +101,44 @@ try {
}
$stmt->closeCursor();
} finally {
// Dejar la conexión como estaba: las escrituras siguientes la necesitan con buffer
// Dejar la conexión como estaba
$conn->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, $bufferPrevio);
}
printf("\n registros revisados: %d\n", $leidos);
printf(" equivalencias encontradas: %d\n", count($mapa));
printf(" personas que llegaron sin teléfono: %d\n\n", count($sinTelefono));
file_put_contents($cache, json_encode(['mapa' => $mapa, 'sinTelefono' => $sinTelefono]));
printf(" mapa guardado en %s\n", $cache);
// Se termina aquí a propósito. Tras media hora de lectura el servidor ya cerró
// esta conexión, y el singleton de Database no sabe reconectar: escribir ahora
// falla con "MySQL server has gone away" a mitad del recorrido, que es justo lo
// que pasó la primera vez. La segunda ejecución toma el mapa de la caché en un
// segundo y escribe con la conexión sana.
echo "\nMapa listo. Ejecute otra vez el comando para aplicarlo.\n";
exit(0);
}
printf(" equivalencias encontradas: %d\n", count($mapa));
printf(" personas que llegaron sin teléfono: %d\n", count($sinTelefono));
// --- 1b. ¿Hay teléfonos con más de un BSUID? ---
// Importa saberlo antes de escribir: si un mismo número aparece con dos
// identificadores es porque cambió de dueño, o porque la persona volvió a
// registrarse. Se queda el último visto, pero conviene que quede dicho.
$porTelefono = [];
foreach ($mapa as $b => $t) $porTelefono[$t][] = $b;
$ambiguos = array_filter($porTelefono, fn($bs) => count($bs) > 1);
printf(" teléfonos con más de un BSUID: %d\n\n", count($ambiguos));
foreach (array_slice($ambiguos, 0, 10, true) as $t => $bs) {
printf(" %s → %s\n", substr($t, 0, -4) . '****', implode(', ', $bs));
}
if (count($ambiguos) > 10) printf(" (y %d más)\n", count($ambiguos) - 10);
if ($ambiguos) echo "\n";
// --- 2. Escribir el BSUID en el usuario que corresponde ---
$db = Database::getInstance();
$marcados = 0; $noExisten = 0; $yaEstaban = 0; $conflictos = 0;
foreach ($mapa as $bsuid => $telefono) {