From ffb8c3c934a5cbb55d04e732e89b71e5654299a4 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Tue, 11 Aug 2026 21:08:14 -0500 Subject: [PATCH] Partir el backfill en dos fases para que no muera a mitad MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- scripts/backfill_bsuid.php | 56 +++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/scripts/backfill_bsuid.php b/scripts/backfill_bsuid.php index 07bb87b..f9e2bc7 100644 --- a/scripts/backfill_bsuid.php +++ b/scripts/backfill_bsuid.php @@ -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) {