
20 errores de arquitectura en Moodle y cómo detectarlos
Decisiones razonables por separado que, al acumularse, dificultan las actualizaciones, la operación y la recuperación de la plataforma.
Tras varios años en producción, una plataforma Moodle suele acumular decisiones técnicas difíciles de revertir: las actualizaciones se posponen porque faltan pruebas, nadie se atreve a desinstalar ciertas extensiones, las tareas en segundo plano se ejecutan cada vez más tarde y cualquier cambio obliga a revisar varias integraciones. Es fácil concluir que Moodle «se ha quedado pequeño», aunque esa conclusión agrupa síntomas que pueden tener causas muy distintas.
Los problemas más difíciles de una plataforma Moodle no suelen proceder de una única decisión equivocada. Aparecen cuando datos, permisos, procesos e integraciones que podían gestionarse por separado terminan dependiendo unos de otros. El síntoma final puede ser la pérdida de rendimiento, la dificultad para actualizar o la incapacidad para recuperar la plataforma, pero la causa suele estar en el acoplamiento acumulado.
Las decisiones que analizo a continuación dependen del contexto. Una integración síncrona, una instalación en un único servidor o una categoría asociada a una unidad organizativa pueden ser soluciones razonables. Dejan de serlo cuando ya no responden al contexto que las justificaba, acumulan responsabilidades o crean dependencias que no se pueden modificar por separado.
Aquí me centro en detectar esas decisiones en una instalación que ya está en producción. En arquitectura Moodle desarrollo el marco general y su relación con el resto de la plataforma.
Las rutas, los ajustes y las capacidades concretas que cito corresponden a Moodle 5.2. Señalo las diferencias con Moodle 4.5 LTS cuando afectan a las opciones disponibles.
Cómo reconocer un problema de arquitectura
Un síntoma aislado no basta para identificar la causa. Una cola de tareas retrasada puede deberse a falta de procesos, a una tarea bloqueada o a una integración externa que responde tarde. Por eso relaciono cada síntoma con una evidencia que permita decidir qué investigar después.
| Síntoma | Qué compruebo |
|---|---|
| Las actualizaciones se aplazan una y otra vez | Pruebo cada plugin con la versión de destino, localizo los cambios sobre el núcleo y ejecuto en preproducción las pruebas de los flujos críticos. |
| El acceso tarda o falla de forma intermitente | Mido por separado la latencia del proveedor de identidad, el tiempo necesario para aprovisionar la cuenta, el que requieren las reglas de matriculación y la espera de la petición para adquirir el bloqueo de sesión. |
| El cron se ejecuta cada minuto, pero el trabajo llega tarde | Consulto cuánto lleva esperando la tarea más antigua, cuánto se aplazó tras un fallo y cuántas tareas se crean y terminan por minuto. |
| Al añadir nodos aparecen cierres de sesión o esperas | Compruebo dónde se guardan las sesiones, cuánto espera una petición para adquirir un bloqueo, cuál es el tiempo de caducidad del bloqueo y si todos los nodos acceden al mismo almacén. |
| Los informes perjudican la actividad lectiva | Registro la duración de las consultas y el retraso de la réplica, y repito la medición mientras los estudiantes envían respuestas a cuestionarios o entregan actividades. |
| Hay copias de seguridad, pero nadie confía en la recuperación | Restauro una copia completa en un entorno aislado y compruebo si el punto recuperado y el tiempo empleado cumplen el RPO y el RTO definidos. |
Con estas comprobaciones acoto la causa antes de cambiar la infraestructura solo porque la plataforma parece lenta.

Límites de Moodle: datos, identidad y estructura de aprendizaje
Mi punto de partida es separar lo que debe resolver Moodle de lo que corresponde a los demás sistemas de la organización. El LMS necesita los datos imprescindibles para organizar, impartir y evaluar la formación. Si se copia en él todo el modelo organizativo, aumenta el trabajo de mantenimiento sin que mejore el aprendizaje.
1. Replicar el modelo de datos corporativo en Moodle
Cuando Moodle reproduce el modelo completo de la organización, cada cambio en recursos humanos repercute en la sincronización del LMS. Una matrícula puede depender del centro de trabajo, el país o la categoría profesional, y un informe quizá necesite el centro de coste. Eso no justifica copiar historiales de contratación, bandas salariales ni relaciones societarias que no intervienen en la formación.
Un administrador puede guardar esos atributos en campos de perfil personalizados, pero esa posibilidad no aclara quién es responsable del dato. Si el sistema de recursos humanos es la referencia para la estructura organizativa, Moodle solo debería recibir los atributos que necesita para cumplir las tareas que le corresponden, ya sea matricular, autorizar, certificar o elaborar informes operativos. Antes de sincronizar un atributo conviene documentar de dónde procede, quién puede modificarlo y qué ocurre si ambos sistemas discrepan. De lo contrario, la integración puede escribir un valor, un administrador cambiarlo desde Moodle y los informes acabar mostrando datos distintos.
Lo detecto cuando Moodle conserva numerosos campos que no utiliza para el aprendizaje, la segmentación ni los permisos, cuando el mismo atributo se puede modificar en recursos humanos y en el LMS o cuando nadie sabe qué valor prevalece si difieren. Acumular decenas de atributos genéricos también complica las consultas y los informes porque obliga a reconstruir entidades a partir de información dispersa. No lo atribuyo a un problema de rendimiento hasta comprobar cómo se usan esos datos y qué consultas se ejecutan.
2. Reproducir el organigrama empresarial en el árbol de categorías
El árbol de categorías organiza los cursos y delimita la administración delegada. La asignación de un rol en CONTEXT_COURSECAT se aplica también a los cursos de esa categoría, como explica la Access API de Moodle.
Que una categoría coincida con un departamento no supone por sí solo un error. Puede tener sentido cuando esa estructura representa también una unidad estable del catálogo o de la administración delegada. La dificultad aparece cuando el árbol de categorías replica automáticamente el organigrama y cambia a su mismo ritmo, aunque los cursos, los permisos académicos y el histórico necesiten otra estructura. Una reorganización que obliga a mover cursos, revisar roles heredados o recolocar el histórico revela ese acoplamiento.
Al diseñar el árbol, intento que su estructura responda principalmente a la organización estable del catálogo y a los ámbitos de administración delegada. Para representar colectivos de usuarios puedo utilizar cohortes a nivel de sitio o categoría. Los grupos resuelven otra necesidad: organizan participantes dentro de un curso. Los campos de perfil describen atributos del usuario y pueden servir de base a reglas de aprovisionamiento o segmentación, pero no sustituyen a esos mecanismos.
Cuando la necesidad deja de ser la administración delegada y pasa a exigir aislamiento entre organizaciones —usuarios, cursos, configuración, informes o visibilidad de datos— ya no estoy resolviendo únicamente un problema de categorías. En ese punto evalúo una arquitectura con multitenencia o la separación de plataformas, según el grado de aislamiento necesario.
3. Acoplar autenticación federada, aprovisionamiento y matriculación
Autenticar a una persona, crear su cuenta, actualizar sus atributos y matricularla son procesos distintos. Moodle incorpora OAuth 2 y dispone del plugin de autenticación Shibboleth para entornos SAML, aunque el proveedor de servicio se instala y se configura en el servidor web. Otros escenarios SAML2 pueden requerir un plugin o una integración específica según el proveedor de identidad y la arquitectura desplegada; auth_saml2, que procesa SAML dentro de Moodle, es un plugin de terceros. Con el alta bajo demanda no hace falta crear por adelantado cuentas de personas que quizá nunca entren en la plataforma.
El problema empieza cuando el inicio de sesión ejecuta de forma síncrona toda la lógica académica:
- El proveedor de identidad (IdP) autentica a la persona y comunica su identidad mediante el protocolo configurado.
- El mecanismo de aprovisionamiento asocia esa identidad a una cuenta interna de Moodle.
- La sincronización actualiza los atributos de perfil autorizados.
- Los plugins de matriculación inscriben a la persona en los cursos correspondientes.
La autorización constituye otra capa. Moodle determina qué puede hacer cada persona según los roles, los permisos y el contexto en el que intenta realizar una operación. Por eso la identidad, el aprovisionamiento, la matriculación y la autorización deben poder evolucionar sin convertirse en un único proceso de acceso.
Si Moodle debe esperar una API externa lenta o recalcular la pertenencia a cohortes muy grandes antes de completar el acceso, el usuario tarda más en entrar. Un fallo temporal del servicio externo puede incluso impedir el inicio de sesión. El acoplamiento se hace visible cuando el tiempo de acceso varía con la latencia del ERP o del sistema de recursos humanos, cuando una caída externa impide entrar o cuando durante el inicio de sesión se recalcula la pertenencia a cohortes y se aplican matriculaciones masivas. En ese proceso dejo solo lo imprescindible para autenticar a la persona y abrir la sesión; las matriculaciones complejas se ejecutan después mediante tareas en segundo plano.
Personalización de Moodle: plugins, núcleo y deuda técnica
Cada componente que se añade a Moodle exige mantenimiento, pruebas en cada actualización y revisiones de compatibilidad con las nuevas versiones de Moodle y PHP.
4. Instalar plugins sin saber quién los mantendrá
El riesgo de un plugin depende de la importancia de la función que cumple y de si su desarrollador continúa manteniéndolo. Cada extensión puede añadir tablas, tareas, observadores de eventos, bibliotecas JavaScript o consultas SQL en operaciones frecuentes. Si deja de ser compatible, puede retrasar una actualización completa de Moodle.
Las notas de Moodle 5.0 documentan la retirada del plugin de autenticación CAS y de todos los plugins MNet. Antes de actualizar, compruebo qué funciones dependen de esos plugins y si existe un sustituto con mantenimiento activo. Si no existe, dejo de usar esa función antes del cambio de versión.
Antes de instalarlo, compruebo qué proceso dejaría de funcionar al retirarlo, quién podría mantener el código si desaparece el soporte y con qué podría sustituirlo. También comparo el plugin con la configuración nativa, un desarrollo propio de alcance limitado, una integración LTI o un servicio externo conectado mediante una API.
En los plugins propios no considero suficiente que version.php declare compatibilidad con una versión nueva. Mantengo una matriz de versiones de Moodle y PHP y ejecuto las comprobaciones antes de actualizar producción. Según el componente, compruebo el cumplimiento de los estándares de código y utilizo PHPUnit y Behat para probar los flujos que pueden verse afectados; moodle-plugin-ci permite realizar estas comprobaciones en la integración continua.
Una dependencia no está controlada cuando nadie mantiene un plugin crítico, una actualización se bloquea por falta de una versión compatible o no existe una alternativa documentada si el componente desaparece.
5. Modificar directamente el código del núcleo
Un cambio directo en lib/moodlelib.php o en cualquier otro archivo del núcleo resuelve una necesidad inmediata a costa de tener que reaplicarlo, adaptarlo o retirarlo en cada actualización. Si una versión nueva modifica la misma zona, será necesario entender las diferencias, resolver los conflictos y repetir las pruebas.
Moodle ofrece numerosos puntos de extensión: tipos de plugin específicos, eventos, callbacks, hooks y las API de los distintos subsistemas. Una avería crítica puede exigir un parche temporal en el núcleo, pero ese parche debe quedar versionado, explicado y acompañado de un criterio para retirarlo. Si ya no es posible reconstruir el entorno a partir de una versión estándar de Moodle, sus extensiones y los parches registrados, la instalación se ha convertido en una variante propia difícil de mantener.
Un git diff frente a la versión oficial descubre el problema con más fiabilidad que la memoria: cambios sin explicación, parches que se reaplican a mano y modificaciones cuyo motivo ya nadie conoce indican que el núcleo se ha convertido en una dependencia propia.
6. Centralizar personalizaciones en un único plugin local
Evitar cambios en el núcleo sirve de poco si toda la lógica propia termina en un único plugin local como local_empresa. En él pueden acabar mezclándose la sincronización con recursos humanos, las tareas programadas, los servicios web, los cambios de interfaz y las reglas de matrícula. Entonces, corregir un informe obliga a desplegar el mismo módulo del que depende el acceso de los usuarios.
Los plugins local sirven para personalizaciones transversales que no encajan en otros tipos nativos. Aun así, una integración con recursos humanos, una pasarela de pago y un informe a medida no requieren los mismos permisos, no fallan del mismo modo ni cambian al mismo ritmo. Separarlos en componentes con responsabilidades claras permite actualizar o retirar uno sin comprometer los demás.
La señal más clara aparece cuando autenticación, informes, sincronización y matrícula comparten componente, despliegue y versión. Si un cambio de interfaz obliga a repetir las pruebas de una integración corporativa que no guarda relación con ese cambio, el plugin ha dejado de tener una responsabilidad clara.
7. Escribir en la base de datos desde sistemas externos
El esquema de la base de datos no es la API de integración de Moodle. Escribir desde fuera en tablas como mdl_user o mdl_user_enrolments omite las validaciones del dominio y puede dejar sin actualizar permisos, eventos, cachés y estados derivados. El resultado puede parecer correcto en la tabla modificada y fallar más tarde en otra parte de la plataforma.
Tampoco hay que confundir DML con una API de dominio. $DB proporciona la capa correcta para acceder a la base de datos desde código Moodle, en especial a las tablas propias de un plugin, pero crear una cuenta, matricular a una persona, modificar una calificación o manipular una actividad debe pasar por la API del subsistema correspondiente siempre que exista. En esa API están implementadas las reglas y los efectos asociados a la operación.
El acceso y la modificación de datos deben pasar por las API oficiales:
| Tipo de operación | Mecanismo recomendado | Qué compruebo |
|---|---|---|
| Operación síncrona en tiempo real | Servicios web externos de Moodle (External API) | Autenticación mediante token, permisos de servicio e idempotencia |
| Carga periódica por lotes | Comando CLI o tarea en segundo plano con procesamiento por lotes | Límites de tiempo de ejecución, transacciones SQL acotadas y gestión de memoria |
| Integración asíncrona con sistemas externos | Tabla intermedia propia del plugin o cola externa | Registro de estado, orden de procesamiento y control de duplicados |
| Generación de informes complejos | Réplica de lectura de la base de datos o proceso de extracción hacia un almacén de datos | Retraso de replicación y aislamiento de la carga analítica |
El código PHP que trabaja con datos de Moodle debe usar la DML API y las funciones del subsistema correspondiente. En una importación, una opción segura es guardar primero los registros recibidos en una tabla intermedia del plugin y procesarlos después con una tarea interna que llame a las API oficiales de usuarios y cursos.
Detecto la escritura externa cuando un sistema dispone de credenciales para modificar la base de datos de Moodle, aparecen scripts SQL que «corrigen» cuentas, matrículas o calificaciones, o los registros parecen válidos en una tabla y Moodle muestra un estado incoherente.
Integraciones de Moodle: contratos, asincronía y concurrencia
Cuando crece el volumen de una integración, las llamadas directas y síncronas entre plataformas empiezan a comprometer el servicio: ocupan procesos web, consumen conexiones y provocan reintentos sin coordinación.
8. Ocupar procesos de PHP con llamadas síncronas
Una petición web síncrona mantiene ocupado un proceso de PHP-FPM hasta que responde el servidor remoto. La espera puede estar justificada cuando el usuario necesita una confirmación inmediata. Para sincronizar miles de matrículas o exportar calificaciones en bloque conviene separar el trabajo: si el sistema remoto se ralentiza y no se han fijado tiempos de espera estrictos, esas llamadas pueden agotar los procesos disponibles y dejar al servidor sin capacidad para atender la navegación normal.
Lo detecto cuando Moodle tarda más cada vez que el sistema remoto responde con retraso, cuando los procesos de PHP permanecen a la espera de operaciones externas o cuando una sincronización masiva empeora la navegación.
Las operaciones de gran volumen necesitan procesamiento asíncrono:
- Idempotencia: Cada operación necesita un identificador estable; el lote también debe registrar qué elementos se completaron. Así puede reanudarse un proceso interrumpido sin repetir las matrículas ya aplicadas.
- Tolerancia a la indisponibilidad: Si el sistema externo no responde, las operaciones pendientes quedan registradas en una cola o una tabla intermedia hasta que se recupera la comunicación.
- Separación de prioridades: El usuario debe recibir de inmediato la confirmación de que su solicitud se ha registrado; las sincronizaciones masivas de atributos pueden ejecutarse en momentos de menor concurrencia.
9. Ejecutar trabajo pesado en los observadores de eventos
Los observadores se ejecutan en el mismo proceso de PHP que dispara el evento. Dentro de una transacción, los declarados con internal => true se ejecutan de inmediato y pueden prolongar los bloqueos; los demás esperan a que la transacción se confirme y no llegan a ejecutarse si la transacción se revierte. Por eso un observador no interno no sirve para registrar el propio fallo de la transacción. Si el observador llama a una API externa, genera un documento pesado o hace un cálculo complejo, el proceso no queda libre hasta que termina ese trabajo.
Un evento indica que algo ha ocurrido, pero no convierte al observador en un proceso asíncrono. La política de comunicación entre componentes de Moodle advierte además del coste de mantener código que depende en exceso de estas ejecuciones indirectas. Cuando no necesito responder al evento de inmediato, el observador se limita a encolar una tarea con los datos necesarios. Si el volumen, el aislamiento operativo o los requisitos de entrega superan lo que conviene gestionar con la Task API, puedo incorporar una cola externa a la arquitectura de integración.
La Task API de Moodle permite trasladar esas operaciones a tareas programadas o ad hoc ejecutadas por procesos CLI. Las tareas ad hoc, sin embargo, se guardan en mdl_task_adhoc, dentro de la base de datos principal. Encolar muchas tareas de una sola vez puede aumentar la carga, consumir conexiones y competir con las peticiones web.
Las tareas ad hoc encajan bien en trabajos internos acotados, como enviar una notificación o procesar una entrega. Una importación masiva de catálogos puede necesitar una cola externa y procesos CLI que ajusten el tamaño de cada lote a la memoria, el tiempo de ejecución y las conexiones disponibles. Si al terminar una actividad se lanzan llamadas HTTP o se generan documentos y la persona sigue esperando aunque la operación principal ya haya concluido, compruebo primero qué hace el observador.
10. Dejar que dos sistemas decidan sobre el mismo estado
También hay problemas cuando dos sistemas aplican reglas distintas sobre el mismo estado. El sistema de gestión académica puede desmatricular a una persona según sus registros y Moodle volver a matricularla a partir de una regla local. Un sistema deshace periódicamente los cambios del otro y los registros dejan de coincidir.
En el contrato de integración indico cuál es el sistema de referencia para cada dato y dónde se permite modificarlo:
- El sistema corporativo decide la matrícula y envía a Moodle el identificador de la persona, el curso, el rol y las fechas de vigencia.
- Como alternativa, Moodle recibe los atributos autorizados y aplica sus propias reglas de matriculación.
- Si interviene un servicio intermedio, el contrato indica qué valor prevalece, cómo se detectan los duplicados y cómo se corrige una discrepancia. Una marca de tiempo aporta contexto, pero no sustituye esas reglas.
En cada intercambio uso un identificador común que enlaza los registros y deja constancia del resultado. También documento quién vigila la integración, quién atiende los errores y cómo se corrige o se repite una operación sin duplicarla.
Una matrícula que aparece y desaparece, una sincronización que deshace periódicamente los cambios de otra o una discrepancia para la que nadie puede señalar qué sistema prevalece son señales de que el contrato no asigna una autoridad única sobre ese estado.
Escalabilidad de Moodle: estado, ejecución y almacenamiento
Al añadir nodos web, diseño conjuntamente cómo se guardan las sesiones, qué cachés se comparten y dónde quedan los archivos. Antes de repartir el tráfico, compruebo que cualquier nodo pueda recuperar la sesión, consultar las cachés compartidas y acceder a los mismos archivos.
11. Añadir nodos sin rediseñar el estado
Con poca demanda, reunir el servidor web, la base de datos y moodledata en una máquina virtual puede ser una opción suficiente y económica. Al añadir nodos web detrás de un balanceador aparecen otros requisitos:
- Persistencia de sesiones: Las sesiones de usuario deben residir en un almacenamiento accesible por todos los nodos (como una instancia de Redis dedicada o la base de datos) para evitar la pérdida de sesión si el balanceador redirige el tráfico entre servidores.
- Distribución de cachés: Algunas cachés deben compartirse entre los nodos; otras funcionan mejor en el almacenamiento local de cada servidor.
- Almacenamiento compartido y local: Todos los nodos deben compartir
$CFG->dataroot,$CFG->tempdir,$CFG->cachediry$CFG->backuptempdir.$CFG->localcachedirno necesita compartirse y puede situarse en el almacenamiento local rápido de cada nodo, como explica la documentación de servidores en clúster de Moodle.
Esta arquitectura debe probarse con una carga representativa antes de llegar a producción. Añadir nodos web no sirve de mucho si muchas peticiones quedan a la espera de bloqueos de sesión o si los procesos de todos los nodos agotan las conexiones de la base de datos.
Las discusiones públicas sobre instalaciones en clúster documentan casos con varios frontales web, un balanceador y una caché compartida en los que la mejora esperada no llega porque todos los nodos dependen de un almacenamiento compartido lento. Aumentar la capacidad de ejecución de PHP no reduce la latencia de ese almacenamiento ni aumenta su capacidad de lectura y escritura. Antes de añadir otro nodo sigo el estado de la petición y localizo qué recursos continúan siendo compartidos.
La afinidad de sesión en el balanceador puede ocultar durante un tiempo que las sesiones siguen siendo locales. También reviso este diseño si distintos nodos responden de forma diferente o si moodledata y las cachés se comparten —o se aíslan— con un alcance inadecuado.
12. Mezclar Redis, MUC y sesiones sin delimitar su función
En un despliegue distribuido, Redis puede almacenar las sesiones PHP y las cachés de MUC. Si una misma instancia reúne las sesiones, la caché de aplicación y otras cachés efímeras, puede haber contención porque todas comparten memoria, políticas de expulsión y operaciones de purga. La Moodle Universal Cache (MUC) distingue las cachés de aplicación, sesión y petición. En ese despliegue decido por separado dónde se guardan las sesiones PHP, las cachés de MUC y $CFG->localcachedir:
| Capa de datos | Ámbito de persistencia | Criterio de configuración |
|---|---|---|
| Caché MUC de aplicación | Compartida entre todos los usuarios del sistema | La definición establece sus requisitos de persistencia y almacenamiento. Si se utiliza Redis, hay que dimensionar la memoria para que no expulse entradas de forma imprevista. Solo puede añadirse una caché local si la definición admite ese uso. |
| Caché MUC de sesión | Vinculada a la sesión de un usuario específico | Si se activa $CFG->enable_read_only_sessions, estas definiciones deben asignarse a un almacenamiento independiente de $SESSION; Moodle lanza una excepción que identifica las que siguen dentro de la sesión. |
| Caché MUC de petición | Limitada a la duración de la petición HTTP | Se guarda en la memoria del proceso de PHP y evita accesos innecesarios a un almacén remoto. |
| Sesiones PHP | Compartidas entre los nodos del clúster | Requieren un almacenamiento común, generalmente Redis. Los tiempos de bloqueo y espera deben calibrarse según la concurrencia. |
$CFG->localcachedir | Almacenamiento local en cada nodo web | Guarda artefactos regenerables. No necesita compartirse y puede situarse en el almacenamiento local rápido de cada nodo. |
APCu reduce el tiempo de acceso a las cachés locales de un nodo, pero cada servidor conserva una copia independiente. Antes de asignar una definición de MUC a APCu, compruebo que Moodle permita usar un almacén local y que ningún nodo pueda leer datos obsoletos por el desfase entre las copias.
En el controlador de sesiones Redis de Moodle, ajusto session_redis_acquire_lock_timeout, session_redis_acquire_lock_retry y session_redis_lock_expire a la concurrencia real. Si no defino session_redis_lock_expire, el controlador calcula la caducidad a partir de max_execution_time de PHP y la limita con $CFG->sessiontimeout. Si la caducidad es demasiado corta, el bloqueo puede vencer antes de que termine una operación; si es demasiado larga, otros procesos pueden seguir esperando después de que falle el proceso que conservaba el bloqueo.
Una señal especialmente útil es la diferencia entre el tiempo de ejecución y el tiempo total de una petición. Si PHP realiza poco trabajo pero la petición sigue detenida, reviso los bloqueos de sesión y las dependencias externas antes de añadir capacidad de cómputo. Reviso también la separación de funciones si Redis expulsa entradas de caché que afectan a las sesiones, si aumenta la espera por los bloqueos o si una sola instancia concentra cargas con requisitos distintos.
Los bloqueos de sesión pueden producir un diagnóstico engañoso: una petición larga conserva el bloqueo mientras otra petición del mismo usuario espera para adquirirlo. El servidor puede tener capacidad disponible y, aun así, la segunda petición no puede continuar. Cuando una petición tarda, necesito saber en qué se ha empleado ese tiempo antes de ampliar la infraestructura. Los casos de espera por bloqueos de sesión publicados por la comunidad muestran por qué el tiempo total no debe confundirse con el tiempo de ejecución de PHP.
En Moodle 5.2, los nombres no coinciden: config-dist.php documenta session_redis_lock_retry y session_redis_maxretries, mientras que el controlador lee session_redis_acquire_lock_retry y session_redis_max_retries. Con los nombres de config-dist.php, el controlador ignora ambos ajustes y conserva los valores predeterminados. Redis tampoco corrige una consulta SQL costosa ni la lentitud de un servicio remoto.
13. Ejecutar cron sin comprobar su capacidad
admin/cli/cron.php debe ejecutarse cada minuto. Además, la infraestructura necesita capacidad suficiente para vaciar las colas de trabajo. Cada proceso de cron ejecuta una tarea a la vez. El correo, las sincronizaciones, las copias de seguridad y las conversiones de documentos comparten esa capacidad. Si se crean más tareas de las que los procesos pueden terminar, el trabajo pendiente se acumula y los certificados o las notificaciones llegan tarde.
Moodle permite ejecutar varios procesos de cron y dedicar procesos a las tareas ad hoc con admin/cli/adhoc_task.php --execute --keep-alive=59. El ajuste de administración task_adhoc_concurrency_limit limita el total de tareas ad hoc; $CFG->task_concurrency_limit_default fija el valor general por clase y, por ejemplo, $CFG->task_concurrency_limit['local_empresa\task\sincronizar_usuarios'] permite cambiarlo para una tarea concreta. La clave debe coincidir con el nombre que devuelve get_class(), sin barra inicial. Al aumentar el paralelismo, compruebo las conexiones disponibles y la carga de la base de datos; si se agotan las conexiones, las tareas tardarán más aunque haya más procesos.
Aumentar el número de procesos no significa que cualquier tarea propia pueda ejecutarse en paralelo de forma segura. Si dos ejecuciones pueden trabajar sobre el mismo recurso lógico, también controlo la concurrencia. La Lock API de Moodle permite adquirir bloqueos válidos entre nodos distintos del clúster. La idempotencia evita que repetir una operación produzca efectos duplicados; el bloqueo resuelve otro problema: impide que dos procesos modifiquen a la vez un estado que necesita exclusión.
Es posible que cron se ejecute cada minuto y que, pese a ello, algunos trabajos permanezcan horas o días en la cola. El proceso está activo, pero no tiene capacidad para completar el trabajo al mismo ritmo al que se generan nuevas tareas. Este caso publicado en la comunidad de Moodle muestra por qué la última ejecución de cron, por sí sola, no demuestra que la cola esté bajo control.
La edad de la tarea pendiente más antigua, la duración por tipo, los reintentos y los errores muestran si el cron da abasto y si el trabajo acumulado crece o disminuye. Si aparecen ejecuciones duplicadas, estados intermedios imposibles o fallos que solo se reproducen al aumentar los procesos, reviso qué parte necesita exclusión y cómo se gestionan los bloqueos. También compruebo si el paralelismo agota las conexiones de la base de datos. Con mucho volumen, puede ser necesario ejecutar las tareas en instancias o contenedores que no atiendan tráfico web.
14. Ejecutar informes pesados sobre la base de datos operativa
Los informes analíticos complejos pueden ralentizar la base de datos si se ejecutan durante la actividad lectiva. Las agregaciones y las uniones sobre tablas grandes, como mdl_logstore_standard_log, compiten por memoria y capacidad de cálculo con operaciones que afectan directamente al estudiante, entre ellas el guardado de respuestas de un cuestionario.
En escenarios de alta concurrencia reviso los tiempos de espera, la duración de las transacciones, las consultas lentas, las conexiones ocupadas y los bloqueos antes de atribuir el problema a la CPU o añadir nodos web. La coincidencia entre consultas de informes prolongadas y un aumento de la latencia en cuestionarios o entregas constituye una señal más útil que el tamaño de una tabla por sí solo.
Si la carga analítica sigue interfiriendo con las operaciones después de optimizar las consultas, conviene trasladarla fuera de la base de datos principal:
- Informes operativos: Configuro una o varias réplicas de solo lectura en el gestor de base de datos y las declaro en
$CFG->dboptions['readonly']. Conlatencyfijo cuántos segundos debe seguir leyendo Moodle desde la base de datos principal después de escribir en una tabla. Enexclude_tablesenumero las tablas que Moodle debe leer siempre en la base de datos principal. - Analítica corporativa y cuadros de mando: Extraigo los datos de forma incremental y los transfiero a un almacén analítico independiente.
Conservar indefinidamente los registros de eventos agranda las tablas y sus índices. También alarga las copias de seguridad y las restauraciones.
15. Dejar crecer el almacenamiento sin una estrategia
La File API de Moodle identifica el contenido mediante resúmenes SHA-1 y guarda una sola copia física aunque varios cursos utilicen el mismo archivo. Los vídeos de alta definición, las grabaciones extensas y otros paquetes pesados pueden hacer crecer moodledata/filedir hasta complicar las copias de seguridad y las migraciones.
En un clúster, un volumen NFS compartido para filedir puede limitar el rendimiento de lectura y escritura y también debe diseñarse con alta disponibilidad. La API del sistema de archivos admite implementaciones alternativas; para utilizar Amazon S3, Azure Blob Storage u otro almacén de objetos hace falta un componente compatible. Aun así, todos los nodos deben compartir $CFG->dataroot; $CFG->tempdir y $CFG->cachedir también tienen sus propios requisitos de almacenamiento compartido, como explica la documentación de servidores en clúster.
Para decidir dónde almacenar cada tipo de contenido, tengo en cuenta el control de acceso, la privacidad, la forma de entrega y el coste. Moodle puede gestionar los recursos ligados a permisos o calificaciones. Para los vídeos de gran tamaño puede encajar mejor una plataforma especializada con transcodificación adaptativa. Un repositorio documental debe conectarse mediante la API o el conector que ofrezca; LTI solo encaja si también funciona como herramienta de aprendizaje.
Si compruebo que las copias de seguridad, las migraciones o las lecturas tardan principalmente por el tamaño de filedir, ampliar la capacidad de los nodos web no resolverá ese límite. También reviso la distribución del almacenamiento cuando el vídeo y las grabaciones concentran la mayor parte del volumen o el NFS empieza a introducir latencia bajo carga.
16. Dimensionar por el número de usuarios registrados
El número de usuarios registrados es insuficiente para calcular la infraestructura. Una plataforma con muchas cuentas y pocos accesos simultáneos puede consumir menos recursos que otra con menos estudiantes concentrados en un examen cronometrado. En el segundo caso, el autoguardado y los envíos frecuentes pueden generar muchas escrituras y accesos continuos a la sesión.
Dimensiono la plataforma a partir del uso real:
- Tasa de peticiones por segundo y percentiles p50, p95 y p99 de las operaciones relevantes durante los intervalos de máxima concurrencia.
- Proporción de lecturas y escrituras, sobre todo durante el envío de cuestionarios o la entrega de tareas.
- Duración media de las sesiones de usuario y frecuencia de interacción.
- Coincidencia entre los picos de uso y los procesos de sincronización masiva o las tareas programadas.
La media de uso de CPU oculta los picos. Las pruebas de carga deben reproducir flujos completos: navegar por un curso, responder a una evaluación y entregar un trabajo con la concurrencia prevista. Si los valores medios parecen normales pero se producen fallos durante un examen masivo, reviso si las pruebas reprodujeron una concurrencia realista. Dimensionar solo con el total de cuentas deja fuera el dato decisivo: cuántas personas actúan a la vez y qué operaciones realizan. No fijo un umbral universal: comparo la línea base, la tendencia y el comportamiento bajo carga.
Mantenibilidad de Moodle: permisos, recuperación y actualizaciones
Una plataforma mantenible se puede recuperar después de un fallo y actualizar sin convertir cada cambio de versión en una operación de alto riesgo.
17. Construir permisos mediante excepciones
Moodle permite asignar roles en distintos contextos y anular permisos en cursos, categorías o actividades. Esa flexibilidad se vuelve difícil de revisar cuando el acceso depende de muchas excepciones individuales. Ante una incidencia, cuesta averiguar qué asignación o anulación concede el permiso concreto.
Los roles deben representar responsabilidades reconocibles y asignarse en el contexto más limitado que permita realizar el trabajo. Si hace falta una excepción temporal, debe quedar anotado el motivo, quién la autorizó y cuándo debe revisarse o retirarse.
Reviso el diseño cuando explicar el acceso de una persona exige reconstruir varias anulaciones, existen excepciones individuales sin fecha de retirada o el nombre del rol ya no permite anticipar sus permisos efectivos.
18. Confiar en copias sin probar la restauración
Compruebo las copias de seguridad restaurándolas en un entorno aislado y ejecutando el procedimiento completo; verificar que los archivos no están dañados, por sí solo, no demuestra que sea posible restablecer el servicio.
Una restauración exige coordinar estos componentes:
- Una versión del código compatible con la base de datos, incluidos los plugins y los parches desplegados.
- Un volcado consistente de la base de datos relacional.
- Todo el almacenamiento persistente, tanto
$CFG->dataroot/filedircomo cualquier sistema de archivos alternativo. - Archivos de configuración y claves criptográficas (
config.php, certificados). - Credenciales y configuración de las integraciones externas (proveedores de identidad, pasarelas, sistemas de gestión).
La copia de la base de datos y la del almacenamiento persistente deben corresponder al mismo punto de recuperación. Si se obtienen en momentos distintos mientras hay actividad, la restauración puede dejar referencias sin archivo o archivos sin referencia. El RPO fija la pérdida máxima de datos aceptable, expresada en tiempo; el RTO, el plazo máximo para restablecer el servicio. Ambos deben comprobarse mediante restauraciones periódicas, no solo figurar en un documento.
Al restaurar una copia generada la noche anterior puedo descubrir que el tamaño de un curso, la memoria disponible, el almacenamiento temporal o una dependencia ausente impiden completar el proceso en el plazo previsto. La guía oficial para cursos grandes recomienda ejecutar por línea de comandos tanto la copia como la restauración. Por eso pregunto cuándo se restauró por última vez y cuánto se tardó, no solo si hay una copia reciente.
19. Configurar producción manualmente
Los cambios manuales en producción —instalar una extensión de PHP, retocar config.php o ejecutar una sentencia SQL— dejan la configuración repartida entre el servidor, la base de datos y la memoria de quien hizo el cambio. El entorno de preproducción deja entonces de ser una réplica fiable.
A partir de Moodle 5.1, el servidor web debe usar public/ como raíz; config.php y los comandos de admin/cli/ permanecen fuera. Al actualizar una instalación 4.5 LTS, adapto a esa estructura las imágenes de contenedor, las reglas del servidor web, el WAF y los automatismos de despliegue.
Documento cómo se reconstruye cada parte del entorno:
- El código, los plugins y la configuración no sensible deben estar versionados y desplegarse de forma automatizada.
- La infraestructura puede declararse con Terraform; el sistema operativo y los servicios, con Ansible o una herramienta equivalente; y el entorno de ejecución, mediante imágenes de contenedor si esa es la arquitectura elegida.
- Las credenciales y las claves privadas deben guardarse en un gestor de secretos, no en archivos sin control.
Si puedo reconstruir un nodo de forma automática, tardo menos en sustituirlo después de un fallo y no dependo de que una sola persona recuerde cómo estaba configurado.
La falta de reproducibilidad se hace visible cuando preproducción no permite reproducir un fallo que sí aparece en producción, cuando esta contiene extensiones o ajustes aplicados a mano o cuando reconstruir un nodo exige preguntar qué se hizo la vez anterior.
20. Preparar las actualizaciones demasiado tarde
Si se espera hasta el final del periodo de soporte para preparar una actualización, suelen coincidir los cambios de PHP, los plugins aún incompatibles con la nueva versión y el código propio basado en funciones obsoletas.
La actualización se prepara durante toda la vida de la plataforma:
- Mantener un inventario actualizado de plugins que identifique el responsable de su mantenimiento, su historial de compatibilidad y el plan de contingencia si el desarrollo cesa.
- Desarrollar personalizaciones propias apoyándose en las API oficiales y consultando las guías de obsolescencia publicadas por Moodle en cada versión.
- Ejecutar pruebas previas en réplicas del entorno de producción siguiendo la guía oficial de actualización de Moodle 5.2, verificando los flujos de autenticación, navegación, cuestionarios, calificaciones y ejecución de tareas.
En los componentes propios ejecuto de forma continua las pruebas contra las versiones de Moodle y PHP previstas para la siguiente actualización. Descubrir una incompatibilidad cuando empieza la ventana de actualización significa que la compatibilidad se ha tratado como una fase puntual y no como una propiedad que se mantiene durante el ciclo de vida.
En una instalación con años de servicio puede existir una versión de destino compatible y, aun así, la actualización puede quedar bloqueada por plugins incompatibles, extensiones abandonadas o cambios directos sobre el núcleo. La comunidad recoge casos de actualizaciones condicionadas por plugins no compatibles. Mientras la plataforma sigue funcionando, esa deuda puede pasar inadvertida; se hace visible al intentar cambiarla.
Si la actualización comienza con el descubrimiento de plugins incompatibles, coincide con cambios de PHP, sistema operativo y código propio, o no existe una prueba recurrente contra versiones futuras soportadas, la preparación ha empezado demasiado tarde.
Observabilidad de Moodle: supervisión de la cadena completa
Las métricas de CPU, memoria, disco y red no explican por sí solas por qué el usuario percibe lentitud. Una página puede tardar aunque quede capacidad de CPU porque la petición espera un bloqueo de sesión en Redis o la respuesta de un servicio externo. También puede ocurrir lo contrario: la web responde bien mientras crece el retraso de las tareas en segundo plano.
Las señales anteriores deben relacionarse entre sí. Una cola retrasada puede deberse a falta de procesos, pero también a consultas lentas, servicios externos o contención. Una petición puede tardar porque PHP está ejecutando código o porque el proceso pasa la mayor parte del tiempo esperando un bloqueo. Con los datos de observabilidad distingo ambos casos antes de modificar la arquitectura.
En la aplicación mido tres aspectos del servicio:
- Calculo los percentiles p50, p95 y p99 de las operaciones relevantes y, para explicar esos tiempos, separo la ejecución de PHP, las consultas SQL, la espera por bloqueos de sesión, el acceso a las cachés de MUC y las llamadas a servicios externos.
- Registro la latencia de las operaciones que realizan los estudiantes, el número y la duración de las consultas SQL que generan y las conexiones ocupadas durante acciones como entregar una actividad o enviar una evaluación.
- Superviso la edad de las tareas pendientes, la duración por tipo, el número de reintentos y la tasa de errores.
Sin desplegar todavía un sistema de trazas, consulto algunos de esos indicadores con la Check API de Moodle. Con cronrunning compruebo cuándo se ejecutó el cron por última vez; con adhocqueue, cuánto lleva esperando la tarea ad hoc más antigua; con longrunningtasks, qué tareas superan los umbrales configurados; y con maxfaildelay, cuál es el mayor aplazamiento provocado por fallos consecutivos. También puedo obtener estos resultados mediante core_check_get_result_admintree, sin revisar a mano la interfaz.
Moodle 5.2 incorpora instrumentación automática con OpenTelemetry; Moodle 4.5 LTS no incluye esa capacidad. La actualización a Moodle 5.2 no activa por sí sola la instrumentación: requiere la extensión de OpenTelemetry para PHP, el paquete moodle-package-otel y un exportador fuera de Moodle, como detalla la documentación de telemetría. Con esa configuración es posible seguir las peticiones y tareas dentro de Moodle; para relacionarlas con otros servicios, estos también deben emitir trazas compatibles. En observabilidad en Moodle con OpenTelemetry explico cómo administrar y conservar esas trazas sin perder de vista la privacidad ni el coste de almacenamiento.
Cómo abordo una arquitectura que ya ha acumulado estos problemas
No empiezo desinstalando plugins ni reescribiendo integraciones. Primero identifico las dependencias de cada pieza y defino interfaces que me permitan sustituirla sin provocar una avería en otra parte.
- Inventario antes de cambiar. Identifico los componentes y plugins desplegados y las integraciones externas. Después determino quién es responsable de cada dato, qué tareas se ejecutan, cómo se conceden los permisos y dónde se guarda el estado persistente. El inventario incluye quién mantiene cada pieza y qué operación dejaría de funcionar si desapareciera.
- Reconstruyo las dependencias. Documento quién escribe y consume cada dato, qué procesos dependen de él y dónde se toma cada decisión. Así puedo distinguir una relación necesaria de otra que existe solo por una implementación antigua.
- Defino interfaces antes de migrar. Siempre que es posible, empiezo por contratos claros y API. Para el trabajo en segundo plano utilizo tablas intermedias y tareas; para aislar los cambios de implementación recurro a adaptadores. Compruebo el nuevo flujo antes de sustituir la implementación que esas interfaces aíslan.
- Migro de forma incremental. Evito que una misma intervención cambie a la vez la organización académica, los permisos, las integraciones y la infraestructura. Cada paso tiene un criterio de aceptación, una comparación con la situación anterior y un procedimiento probado para volver atrás.
- Retiro la solución anterior. La migración termina cuando elimino el mecanismo sustituido y sus restos: sincronizaciones antiguas, campos sin responsable y plugins que ya no se usan. Después retiro los permisos excepcionales, los scripts manuales y la configuración obsoleta. Mantener ambos mecanismos obliga a operar dos arquitecturas a la vez.
Marco de decisión para la arquitectura de Moodle
Reviso la arquitectura en este orden:
| Decisión | Pregunta que debe quedar resuelta |
|---|---|
| 1. Responsabilidades | ¿Qué corresponde a Moodle y qué debe resolver cada sistema corporativo? |
| 2. Datos y permisos | ¿De dónde procede cada atributo y por qué tiene cada persona sus privilegios? |
| 3. Integraciones | ¿Qué API, identificador y regla de reintento se usan en cada intercambio? |
| 4. Asincronía y capacidad | ¿Qué debe terminar antes de responder al usuario y qué puede ejecutarse después? |
| 5. Estado y almacenamiento | ¿Dónde se guardan las sesiones, las cachés y los archivos, y cómo se recuperan? |
| 6. Mantenimiento | ¿Puede reconstruirse y actualizarse el entorno sin cambios manuales ocultos? |
Empiezo siguiendo de principio a fin una operación crítica —iniciar sesión, matricularse, entregar una actividad, completar un examen o recuperar el servicio— y anoto dónde se bloquea la petición, dónde se toma cada decisión y qué datos cambian.
Después reviso las tareas, las integraciones, las cachés, los archivos y el plan de recuperación que intervienen en esa operación. No doy por cerrada una decisión hasta haber asignado un responsable, definido cómo comprobaré que funciona y probado el procedimiento de reversión. Con esta revisión distingo un límite real de Moodle de un problema creado por las integraciones, el almacenamiento o la forma de operar la plataforma.
Considero mantenible una arquitectura Moodle cuando puedo explicar quién es responsable de cada dato, qué sistema toma cada decisión, qué ocurre cuando una dependencia falla y qué necesito reconstruir para restablecer el servicio. Usar más componentes o incorporar más tecnologías no aporta esa claridad por sí solo.
Más de 22 años construyendo y evolucionando plataformas de aprendizaje en producción.
Sobre Alberto Lara y su trayectoria profesional →Lecturas recomendadas

OpenTelemetry en Moodle 5.2: cómo gobernar la telemetría en producción
Qué trazas de Moodle 5.2 conviene conservar, cómo recogerlas y cuánto cuesta almacenarlas.

LTI 1.3, API o xAPI: qué integración conviene en cada caso
Cómo separar las responsabilidades de LTI 1.3, una API y xAPI antes de diseñar una integración.

MCP y Moodle: arquitectura segura para agentes
Cómo limitar lo que un agente puede hacer en Moodle y dejar constancia de cada acción.