MariaDB & ProxySQL: Enrutamiento L7, Connection Pooling & Read/Write Split
En la arquitectura de alto rendimiento de Mañuti, ProxySQL 2.6+ opera como un middleware proxy de capa 7 de ultra baja latencia ubicado entre el aplicativo (Laravel / Octane) y los nodos de persistencia relacional MariaDB 11.4 LTS. Permite multiplexación masiva de conexiones, separación inteligente de lecturas y escrituras mediante expresiones regulares, caché de consultas en memoria y blindaje mediante firewall SQL.
flowchart TD
APP["Mañuti Application Tier<br/>(Laravel 12/13 / Octane Workers)"]
subgraph PROXY ["ProxySQL Middleware"]
POOL["Connection Pooler<br/>(Multiplexing en RAM)"]
FW["L7 SQL Firewall<br/>(Regex Query Rules)"]
CACHE["In-Memory Query Cache"]
MONITOR["Health Monitor Core"]
%% Enlaces invisibles para forzar la estructura visual en cuadrícula 2x2
POOL ~~~ CACHE
FW ~~~ MONITOR
end
DB_PRI[("MariaDB Primary<br/>(10.0.10.11:3306)")]
DB_REP[("MariaDB Replica<br/>(10.0.10.12:3306)")]
APP -->|"TCP Port 6033 (Data Traffic)"| PROXY
PROXY -->|"Hostgroup 10 (Primary R/W)<br/>Escrituras (INSERT/UPDATE)<br/>Transacciones con Bloqueo"| DB_PRI
PROXY -->|"Hostgroup 20 (Read-Only)<br/>Lecturas (SELECT)"| DB_REP
DB_PRI <-->|"WSREP"| DB_REP
1. Los Cinco Pilares de ProxySQL en Mañuti
| Pilar | Mecanismo Técnico | Beneficio Operativo |
|---|---|---|
| Read/Write Split | Análisis léxico de consultas mediante Regex (match_digest). |
Deriva lecturas (SELECT) a réplicas, preservando el nodo maestro solo para escrituras (INSERT, UPDATE, DELETE). |
| Connection Pooling | Multiplexación transparente de sockets backend pre-abiertos. | Miles de clientes web concurrentes se despachan con menos de 100 conexiones físicas a MariaDB, evitando Too many connections. |
| SQL Firewall | Intercepción proactiva por patrones de texto antes de la ejecución. | Bloquea sentencias lesivas en producción (por ejemplo, DELETE o UPDATE sin cláusula WHERE). |
| Query Cache en RAM | Almacenamiento clave-valor de result-sets parametrizados. | Respuestas sub-milisegundo para catálogos y selects repetitivos sin tocar el disco del motor de datos. |
| Failover Transparente | Chequeo continuo de salud vía threads de ping y conexión. | Conmutación automática hacia nodos de respaldo si el primario pasa a estado SHUNNED u OFFLINE. |
2. Preparación del Servidor e Instalación (Ubuntu 24.04 LTS)
2.1 Aprovisionamiento de MariaDB 11.4 LTS
# 1. Dependencias y Repositorio Oficial
sudo apt update && sudo apt upgrade -y
sudo apt install -y apt-transport-https curl software-properties-common
curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version="mariadb-11.4"
sudo apt update
sudo apt install -y mariadb-server mariadb-client
sudo systemctl enable --now mariadb
# 2. Hardening inicial de instalación
sudo mariadb-secure-installation
Respuestas recomendadas para el script de aseguramiento:
Switch to unix_socket authentication: NChange root password: Y (Definir credencial maestra:DbMasterSecurePass_2025!)Remove anonymous users: YDisallow root login remotely: YRemove test database: YReload privilege tables: Y
2.2 Apertura de Escucha de Red en MariaDB
Por defecto, MariaDB escucha en 127.0.0.1. Debe configurarse para responder en interfaces privadas:
sudo sed -i 's/bind-address.*/bind-address = 0.0.0.0/' /etc/mysql/mariadb.conf.d/50-server.cnf
sudo systemctl restart mariadb
# Validar que escucha en el puerto 3306
ss -tlnp | grep 3306
3. Instalación de ProxySQL 2.6+
# 1. Importar llave GPG del repositorio oficial
curl -fL https://repo.proxysql.com/ProxySQL/proxysql-2.6.x/repo_pub_key | sudo gpg --dearmor -o /usr/share/keyrings/proxysql-keyring.gpg
# 2. Registrar origen APT oficial
echo "deb [signed-by=/usr/share/keyrings/proxysql-keyring.gpg] https://repo.proxysql.com/ProxySQL/proxysql-2.6.x/$(lsb_release -sc)/ ./" | sudo tee /etc/apt/sources.list.d/proxysql.list
# 3. Instalación y arranque del servicio
sudo apt update
sudo apt install -y proxysql
sudo systemctl enable --now proxysql
4. Configuración de Credenciales en Capas
El modelo de seguridad requiere registrar credenciales tanto en el motor MariaDB (destino final) como en la aduana de ProxySQL (interfaz de acceso).
4.1 En MariaDB Backend: Usuario de Monitoreo y Aplicativo
Acceder a MariaDB con credenciales maestras:
mariadb -u root -pDbMasterSecurePass_2025!
-- Usuario de Monitoreo interno para ProxySQL (Health Checks)
CREATE USER 'monitor_user'@'%' IDENTIFIED BY 'MonitorSecureKey_2025!';
GRANT SELECT ON sys.* TO 'monitor_user'@'%';
-- Usuario de Aplicación (utilizado por Laravel / Navicat)
CREATE USER 'manuti_app'@'%' IDENTIFIED BY 'AppClientSecurePass_2025!';
GRANT ALL PRIVILEGES ON manuti_core.* TO 'manuti_app'@'%';
FLUSH PRIVILEGES;
EXIT;
5. Configuración de ProxySQL (Consola de Administración 6032)
ProxySQL se gestiona mediante una interfaz SQL interna en el puerto 6032 con tres niveles de persistencia de configuración: RUNTIME (Memoria activa), DISK (Base SQLite local) y MEMORY (Copia de trabajo).
Conexión a la consola administrativa:
mysql -u admin -padmin -h 127.0.0.1 -P 6032 --prompt='ProxySQLAdmin> '
5.1 Enlace del Agente de Monitoreo
-- Asignar credenciales para chequeos de salud internos
UPDATE global_variables SET variable_value='monitor_user' WHERE variable_name='mysql-monitor_username';
UPDATE global_variables SET variable_value='MonitorSecureKey_2025!' WHERE variable_name='mysql-monitor_password';
-- Reducir intervalo de ping para detección rápida (opcional, en microsegundos)
UPDATE global_variables SET variable_value='2000' WHERE variable_name='mysql-monitor_ping_interval';
LOAD MYSQL VARIABLES TO RUNTIME;
SAVE MYSQL VARIABLES TO DISK;
5.2 Emulación de Versión para Clientes Externos (Navicat / DBeaver)
Si ProxySQL responde con un banner genérico no reconocido, las herramientas GUI de administración pueden rechazar el handshake inicial. Se fuerza la versión reportada:
UPDATE global_variables SET variable_value='11.4.0-MariaDB' WHERE variable_name='mysql-server_version';
LOAD MYSQL VARIABLES TO RUNTIME;
SAVE MYSQL VARIABLES TO DISK;
5.3 Registro de Servidores Backend (Hostgroups)
Definimos los siguientes grupos lógicos:
- Hostgroup 10: Nodo Primario de Escritura (
Read/Write). - Hostgroup 20: Nodos de Lectura (
Read-Only).
-- Registrar Nodo Primario en Escritura (Hostgroup 10)
INSERT INTO mysql_servers(hostgroup_id, hostname, port, max_connections, weight)
VALUES (10, '10.0.10.11', 3306, 1000, 100);
-- Registrar Réplica en Lectura (Hostgroup 20)
INSERT INTO mysql_servers(hostgroup_id, hostname, port, max_connections, weight)
VALUES (20, '10.0.10.12', 3306, 1000, 100);
LOAD MYSQL SERVERS TO RUNTIME;
SAVE MYSQL SERVERS TO DISK;
5.4 Registro de Usuarios de Tráfico
ProxySQL valida las credenciales de entrada en el puerto 6033 antes de enviarlas al backend:
-- Registrar usuario de aplicación y vincularlo por defecto al Hostgroup de Escritura (10)
INSERT INTO mysql_users(username, password, default_hostgroup)
VALUES ('manuti_app', 'AppClientSecurePass_2025!', 10);
LOAD MYSQL USERS TO RUNTIME;
SAVE MYSQL USERS TO DISK;
5.5 Reglas de Enrutamiento Inteligente (Query Rules)
-- Purgar reglas anteriores
DELETE FROM mysql_query_rules;
-- Regla 1: Tráfico transaccional crítico que solicita bloqueos explícitos -> Grupo 10 (Escritura)
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply)
VALUES (1, 1, '^SELECT.*FOR UPDATE$', 10, 1);
-- Regla 2: Todas las consultas SELECT genéricas -> Grupo 20 (Lectura)
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, destination_hostgroup, apply)
VALUES (2, 1, '^SELECT.*', 20, 1);
-- Regla 3: Bloqueo de seguridad (SQL Firewall preventivo contra operaciones destructivas sin filtro)
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, error_msg, apply)
VALUES (3, 1, '^DELETE FROM [^ ]+$', 'ERROR DE SEGURIDAD MAÑUTI: DELETE sin clausula WHERE no permitido.', 1);
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;
6. Diagnóstico y Troubleshooting Operativo
6.1 Síntoma: Monitoreo en Empty Set en connect_log
Al consultar SELECT * FROM monitor.mysql_server_connect_log;, la salida devuelve Empty set.
Causa y Verificación: Esto ocurre cuando la conexión inicial de monitoreo se mantiene abierta persistentemente (reutilización de socket) y aún no registra ciclos de reconexión forzada ni errores.
Comprobación de Salud Real:
-
Consultar el estado operativo del nodo en la tabla viva:
SELECT hostgroup_id, hostname, port, status FROM mysql_servers;El valor de
statusdebe ser estrictamenteONLINE. -
Inspeccionar el log de latidos ICMP/Ping (que se ejecuta a mayor frecuencia):
SELECT hostgroup_id, hostname, port, success_time, error_start_us FROM monitor.mysql_server_ping_log ORDER BY time_start_us DESC LIMIT 5; -
Verificar métricas del Connection Pool activo:
SELECT hostgroup, srv_host, srv_port, status, ConnUsed, ConnOK, ConnErrors FROM stats.stats_mysql_connection_pool;
6.2 Síntoma: Error Access denied for user al conectar por el puerto 6033
- ProxySQL Rechaza: El usuario no está en
mysql_usersde ProxySQL o la contraseña difiere.-- En puerto 6032: SELECT username, default_hostgroup FROM mysql_users; - MariaDB Backend Rechaza: El usuario no está autorizado para conectarse desde la IP donde corre ProxySQL.
-- En MariaDB (3306): SELECT user, host FROM mysql.user WHERE user='manuti_app';
7. Hardening de Firewall (UFW)
El puerto administrativo 6032 debe restringirse rigurosamente a 127.0.0.1 o IPs de administración autorizadas; el puerto 6033 procesa el tráfico de datos:
# Permitir puerto de tráfico de datos de ProxySQL desde la red interna
sudo ufw allow from 10.0.0.0/16 to any port 6033 comment 'ProxySQL Traffic Port'
# Permitir acceso administrativo exclusivo desde el bastión / IP de DBA
sudo ufw allow from 10.0.1.50 to any port 6032 comment 'ProxySQL Admin Console'
# Cerrar acceso público directo al puerto nativo de MariaDB
sudo ufw deny 3306 comment 'Bloquear acceso directo a MariaDB'
sudo ufw reload
8. Integración en el Entorno Mañuti (.env)
Laravel interactúa con la base de datos a través de la interfaz de ProxySQL en el puerto 6033:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=6033
DB_DATABASE=manuti_core
DB_USERNAME=manuti_app
DB_PASSWORD=AppClientSecurePass_2025!
Para verificar qué nodo físico responde dinámicamente según la regla de enrutamiento:
# Prueba de Lectura (debe responder el hostname del nodo réplica en HG 20)
mariadb -u manuti_app -pAppClientSecurePass_2025! -h 127.0.0.1 -P 6033 -e "SELECT @@hostname;"