Casos de uso
Casos de auditoría: qué parecía, qué era y cómo lo comprobé
Cada caso conserva el síntoma, la comprobación, la decisión y su límite. Retiro las combinaciones de entidad y cifra que podrían identificar a terceros; las magnitudes solo sitúan la escala del problema.
Caso
Miles de procesos regeneraban a la vez los mismos objetos de caché
Durante una prueba de carga, el clúster caía al poco de comenzar y devolvía errores de servidor. Medí la latencia en el balanceador, las conexiones activas a Redis y la E/S de disco en PostgreSQL. Al principio pensé en falta de escalado en la base de datos o en los nodos de PHP-FPM, hasta que vi que la saturación coincidía con miles de consultas idénticas y con el agotamiento de conexiones a Redis. Era una estampida de regeneración: muchas claves de la caché de aplicación expiraban a la vez y cientos de procesos PHP reconstruían los mismos objetos de contexto y de plugins. Separé las sesiones y la caché de aplicación en almacenes de Redis independientes, mantuve la caché de petición dentro de cada proceso, escaloné las caducidades y añadí un bloqueo por clave con actualización en segundo plano: mientras un proceso regenera, los demás reciben temporalmente la copia caducada. Activé además conexiones persistentes con serialización binaria para evitar una nueva conexión TCP en cada petición. En el recorrido documentado en el informe confidencial, la plataforma sostuvo una concurrencia de decenas de miles de sesiones. Las cifras detalladas de latencia y uso de CPU pertenecen a ese escenario y no constituyen una capacidad general de la plataforma.
Caso
El autoguardado concentraba las escrituras de decenas de miles de cuestionarios
En una convocatoria con decenas de miles de cuestionarios a la vez, los intentos y las respuestas se bloqueaban, los interbloqueos se repetían y el rendimiento se desplomaba a mitad de examen. Medí las consultas lentas, los hilos en espera de bloqueo, la memoria de la base de datos y la frecuencia de autoguardado. Lo primero que me pidieron fue una instancia con más CPU y memoria. Los datos decían otra cosa: la carga se concentraba en escrituras síncronas periódicas y en la competencia entre lecturas y escrituras sobre el mismo nodo. El origen era el autoguardado de respuestas, que se ejecutaba cada minuto mientras las consultas de intentos y de navegación seguían leyendo del nodo de escritura. Ajusté el intervalo de autoguardado y las peticiones de finalización. Desvié a réplicas únicamente las lecturas que toleraban retraso y mantuve en el nodo principal las que debían reflejar de inmediato una escritura. También afiné max_connections, shared_buffers, work_mem y el autovacuum. En la repetición documentada desaparecieron los interbloqueos y se alcanzó la concurrencia prevista; las métricas detalladas pertenecen al informe confidencial del caso.
Caso
Una consulta sin índice recorría millones de filas cada vez que se mostraba un bloque
Después de una migración de versión, abrir la página principal de un curso pasó de responder en menos de un segundo a necesitar varios segundos bajo carga. Perfilé las peticiones con instrumentación de PHP, conté las consultas de cada una y medí su duración. Sospeché primero de la memoria de los nodos PHP y de la acumulación histórica en la base de datos. El perfilado lo desmintió al aislar una sola consulta cuyo plan recorría la tabla entera. Venía de una tabla propia de un plugin de seguimiento con millones de filas y sin un índice compuesto por curso y usuario. Activé el registro de consultas lentas, analicé el plan de ejecución, creé el índice que faltaba y reescribí el código para sustituir las consultas dentro del bucle por una sola consulta agregada. La prueba interna confirmó una reducción de un orden de magnitud en el tiempo de respuesta y muchas menos lecturas de disco; la serie pertenece al informe confidencial.
Caso
Los informes expulsaban de memoria los datos operativos al recorrer decenas de millones de registros
La plataforma se congelaba cada mañana cuando se generaban los informes de seguimiento. Medí las consultas lentas, los aciertos de caché y los bloqueos de metadatos. Parecía que faltaban procesos PHP para atender un pico de actividad, pero los bloqueos coincidían con consultas por fecha y contexto sobre un registro con decenas de millones de filas sin particionar. Los informes ocupaban memoria de la base de datos y expulsaban del área de trabajo las páginas que utilizaba la actividad diaria. Separé los registros hacia un almacén de solo lectura, dejé de guardar eventos sin uso, particioné por rangos de fecha y programé el archivado según la política de retención. Se acabaron las congelaciones durante los informes y la base operativa quedó reducida a una fracción de su tamaño anterior; no publico la serie completa.
Caso
Borrar una parte considerable de los datos no redujo las páginas que la base de datos seguía leyendo
La base de datos respondía más despacio después de purgar decenas de miles de usuarios inactivos y reiniciar miles de cursos, aunque contenía muchos menos datos lógicos. Medí el tamaño físico en disco, la fragmentación de los índices y las lecturas de página por consulta. Parecía corrupción de la base de datos o un fallo del almacenamiento, y lo descarté porque los ficheros conservaban prácticamente el mismo tamaño y las consultas seguían recorriendo páginas vacías. Los borrados masivos habían dejado huecos en las páginas de tablas e índices, pero el motor mantenía ese espacio fragmentado. Escribí una comprobación para localizar las tablas con una proporción elevada de espacio libre, reconstruí tablas e índices durante una ventana de mantenimiento y ajusté el factor de llenado para las tablas con muchas lecturas y pocas actualizaciones. La prueba interna confirmó una recuperación importante de espacio y una reducción clara del tiempo de las consultas por índice; no publico las magnitudes exactas.
Caso
Un servicio no respetaba los límites de acceso previstos
Una revisión detectó un control de autorización insuficiente en un servicio propio. Contrasté el análisis del código con pruebas dentro del alcance acordado. Corregí la validación de permisos y contextos, reduje la información expuesta y comprobé de nuevo los límites de acceso. La reevaluación confirmó el cierre del problema observado. Conservo los detalles de reproducción en el informe reservado.
Caso
La conformidad exigía corregir barreras que el escaneo automático no detectaba
La plataforma tenía que cumplir los requisitos de accesibilidad definidos para el encargo y la revisión inicial encontró barreras. Combiné el escaneo automático con una revisión manual, con lectores de pantalla y navegando solo con el teclado. Así vi que los cuestionarios atrapaban el foco, el tema corporativo no llegaba al contraste mínimo y varios componentes de terceros no funcionaban con lectores de pantalla. Sobre el papel podía parecer suficiente superar el estándar técnico más conocido, pero la norma europea y la legislación española aplicables añadían requisitos propios. El uso real destapó problemas que el escaneo no encontraba. Reescribí el CSS y las plantillas para corregir el contraste, añadí los atributos que anuncian los cambios de estado en menús, acordeones y ventanas modales, y recuperé el indicador de foco que alguien había suprimido. Resolví además la trampa de foco de las preguntas de arrastrar y soltar con una alternativa por teclado. En la versión, la muestra y los criterios evaluados, la revisión final no dejó hallazgos abiertos en la interfaz base ni en los tipos de cuestionario incluidos.
Caso
Los datos no fiables atravesaban una segunda etapa sin conservar su límite de confianza
Una revisión de seguridad detectó entradas sospechosas en un desarrollo propio. Parecían neutralizadas en el punto de entrada, pero seguí el recorrido completo y comprobé que otra etapa volvía a interpretarlas fuera del límite de confianza y podía producir una ejecución no autorizada. Sustituí la construcción dinámica de las operaciones por la API parametrizada de la plataforma y añadí a la integración continua una regla que rechazaba ese patrón. Eliminé el vector y la auditoría externa terminó sin hallazgos críticos. Omito las entradas, las funciones afectadas y la cadena de explotación para impedir que el caso permita reconstruir el ataque.
Caso
Una extensión sin mantenimiento interpretaba datos guardados como objetos ejecutables
Durante una auditoría interna detecté que una extensión sin mantenimiento permitía superar los permisos previstos. El análisis estático localizó una interpretación insegura de datos guardados que podía reconstruir objetos ejecutables. Sustituí ese formato por una representación limitada a datos y, durante la transición de los registros antiguos, bloqueé la creación de objetos. Eliminé el vector y la extensión quedó conforme al estándar de desarrollo seguro de la plataforma. Omito el rol, las entradas y la cadena de explotación para impedir que el caso permita reproducirla.