Portear MariaDB a IBM AIX | 3 semanas de sufrimiento

Llevamos MariaDB a AIX (Parte 1)

Hay decisiones en la vida que tomas sabiendo perfectamente que te van a doler. Casarte. Tener hijos. Correr una maratón. Portar MariaDB 11.8 a IBM AIX.

Esta (Parte 1) es la historia de la última decisión.

MariaDB en AIX

“¿Tan difícil puede ser llevar MariaDB a AIX?”

Todo empezó con una pregunta inocente durante una reunión de equipo: “¿Por qué no tenemos MariaDB en nuestros sistemas AIX?”.

Esto es lo que pasa con AIX que la gente que nunca ha trabajado con él no entiende: AIX no se anda con chiquitas. Cuando los bancos necesitan un uptime de “cinco nueves” para sus sistemas core, usan AIX. Cuando las aerolíneas necesitan sistemas de reservas que no pueden fallar, usan AIX. Cuando Oracle, Informix o DB2 necesitan ofrecer un rendimiento brutal para cargas OLTP de misión crítica, corren sobre AIX.

AIX no es “trendy”. AIX no tiene una mascota cuqui. AIX no será el tema de los blogs tecnológicos sobre “disrupción”. Pero cuando las cosas no pueden fallar bajo ningún concepto, AIX está ahí, haciendo su trabajo en silencio mientras todos los demás están ocupados reiniciando sus contenedores.

Entonces, ¿por qué MariaDB no soporta oficialmente AIX? Simple economía: la comunidad Open Source se ha centrado en Linux, y la portabilidad requiere conocimientos muy específicos de la plataforma. MariaDB soporta Linux, Windows, FreeBSD, macOS y Solaris. AIX no está en la lista, no porque sea una mala plataforma, sino porque nadie había hecho el trabajo sucio todavía. Hasta que… entró LibrePower .

Mi primer error fue pensar: “Probablemente sea solo cuestión de compilar y ajustar un par de cosas”.

Lección nº 1: Cuando alguien dice “solo hay que compilarlo” refiriéndose a software en AIX, está a punto de recibir una clase magistral de humildad y programación de sistemas.

Capítulo 2: CMake y los tres invitados inesperados

El primer día de compilación fue… educativo. Usar CMake en AIX es como jugar a las cartas con alguien que tiene un reglamento totalmente diferente al tuyo y espera que adivines las reglas sobre la marcha.

El bug de la función fantasma

AIX tiene una característica curiosa: declara funciones en las cabeceras por compatibilidad, incluso cuando esas funciones no existen realmente en tiempo de ejecución. Es como si tu GPS te dijera “gira a la derecha en 200 metros” pero la calle fuera un muro de ladrillos.

CMake ejecuta un CHECK_C_SOURCE_COMPILES para comprobar si pthread_threadid_np() existe. El código compila. CMake dice “¡Genial, lo tenemos!”. El binario arranca y… BOOM. Symbol not found.

Resulta que pthread_threadid_np() es exclusivo de macOS. AIX lo declara en los headers porque… bueno, aún no lo sé. ¿Quizás por alguna oscura compatibilidad POSIX de hace décadas? Sea cual sea la razón, GCC lo compila felizmente y el linker no se queja hasta que intentas ejecutarlo.

Lo mismo ocurre con getthrid(), que es específico de OpenBSD.

La solución:

IF(NOT CMAKE_SYSTEM_NAME MATCHES "AIX")
  CHECK_C_SOURCE_COMPILES("..." HAVE_PTHREAD_THREADID_NP)
ELSE()
  SET(HAVE_PTHREAD_THREADID_NP 0)  # Trust but verify... okay, just verify
ENDIF()

poll.h: A jugar al escondice

AIX tiene <sys/poll.h>. Está ahí. Puedes hacerle un cat. Pero CMake no lo detecta.

Después de tres horas depurando un error “POLLIN undeclared” en viosocket.c, descubrí que la solución era simplemente forzar la definición a mano:

cmake ... -DHAVE_SYS_POLL_H=1

Tres horas. Por un flag.

(esto es un problema de detección de plataforma de CMake, no de AIX. Los checks de CMake asumen estructuras de directorios estilo Linux).

Los malditos plugins

Al 98% de compilación — el plugin wsrep_info explotó con símbolos indefinidos. ¿La razón? Depende de Galera. Que no estamos usando. Pero CMake intenta compilarlo de todos modos.

Lo mismo pasó con S3 (requiere símbolos Aria), Mroonga (requiere Groonga) y RocksDB (profundamente ligado a optimizaciones específicas de Linux).

Configuración final de CMake (“La poda”):

-DPLUGIN_MROONGA=NO -DPLUGIN_ROCKSDB=NO -DPLUGIN_SPIDER=NO 
-DPLUGIN_TOKUDB=NO -DPLUGIN_OQGRAPH=NO -DPLUGIN_S3=NO -DPLUGIN_WSREP_INFO=NO

Parece una amputación, pero en realidad es eliminar paja. Estos plugins son casos de uso muy específicos (edge cases) que pocas implementaciones necesitan.

Capítulo 3: Thread Pool, o cómo aprendí a dejar de preocuparme y amar el Mutex

Aquí es donde las cosas se pusieron interesantes. Y por “interesantes” quiero decir “casi me provocan un tic nervioso permanente”.

MariaDB tiene dos modos de gestión de conexiones:

  • one-thread-per-connection: Un hilo por cliente. Simple. Escala igual de bien que un coche subiendo una pared vertical.
  • pool-of-threads: Un conjunto fijo de hilos gestiona todas las conexiones. Elegante. Eficiente. Y no disponible en AIX.

¿Por qué? Porque el Thread Pool requiere APIs de multiplexación de E/S específicas de la plataforma:

PlataformaAPIEstado
LinuxepollSoportado
FreeBSD/macOSkqueueSoportado
Solarisevent portsSoportado
WindowsIOCPSoportado
AIXpollsetNo soportado (hasta ahora)

Así que… ¿tan difícil puede ser implementar el soporte para pollset?

El problema de ONESHOT

El epoll de Linux tiene un flag maravilloso llamado EPOLLONESHOT. Garantiza que un descriptor de fichero dispare eventos una sola vez hasta que lo vuelvas a armar explícitamente. Esto impide que dos hilos procesen la misma conexión simultáneamente.

El Pollset de AIX es level-triggered (activado por nivel). Solo por nivel. Sin opciones. Si hay datos disponibles, te avisa. Y te vuelve a avisar. Una y otra vez. Como ese compañero de trabajo “servicial” que no para de recordarte el email que aún no has contestado.

Once versiones para alcanzar la sabiduría

Lo que siguió fueron once iteraciones de código, cada una más compleja que la anterior, intentando simular el comportamiento de ONESHOT:

v1-v5 (La edad de la inocencia)
Probé a modificar los flags de eventos con PS_MOD. “Si cambio el evento a 0, dejará de saltar”, pensé. Spoiler: no dejó de saltar.

v6-v7 (La era de las máquinas de estado)
“Lo tengo! Mantendré un estado interno y filtraré los eventos duplicados”. El problema: existe una ventana de tiempo (race condition) entre que el kernel te da el evento y tú actualizas tu estado. En esa ventana, otro hilo puede recibir el mismo evento.

v8-v9 (La fase de negación)
“Pondré el estado en PENDING antes de procesar”. Funcionó… más o menos… hasta que dejó de funcionar.

v10 (Esperanza)
Por fin encontré la solución: PS_DELETE + PS_ADD. Cuando recibas un evento, borra inmediatamente el file descriptor del pollset. Cuando estés listo para más datos, vuélvelo a añadir.

// Al recibir eventos: REMOVE
for (i = 0; i < ret; i++) {
    pctl.cmd = PS_DELETE;
    pctl.fd = native_events[i].fd;
    pollset_ctl(pollfd, &pctl, 1);
}

// Cuando estemos listos: ADD
pce.command = PS_ADD;
pollset_ctl_ext(pollfd, &pce, 1);

¡Funcionó! Con compilación -O2.

Con -O3segfault.

Se acerca la noche y sigo teniendo bugs (El Bug de -O3)

Imagínate mi cara. Tengo el código funcionando perfecto en desarrollo (`-O2`). Habilito `-O3` para las pruebas de producción y el servidor explota con “Got packets out of order” o un fallo de segmentación en CONNECT::create_thd().

Pasé dos días convencido de que era un bug del compilador. GCC 13.3.0 en AIX. Culpé al compilador. Culpé al linker. Culpé a todo el universo excepto a mi propio código.

El problema era más sutil: MariaDB tiene dos rutas de código concurrentes que llaman a io_poll_wait en el mismo pollset:

  • El listener bloquea con timeout=-1.
  • El worker llama con timeout=0 para comprobaciones no bloqueantes.

Con -O2, los tiempos eran tales que rara vez colisionaban. Con -O3, el código era más rápido, las colisiones ocurrían más a menudo, y boom: condición de carrera.

v11 (Iluminación)
La solución fue un mutex dedicado protegiendo tanto pollset_poll como todas las operaciones pollset_ctl:

static pthread_mutex_t pollset_mutex = PTHREAD_MUTEX_INITIALIZER;

int io_poll_wait(...) {
    pthread_mutex_lock(&pollset_mutex);
    ret = pollset_poll(pollfd, native_events, max_events, timeout);
    // ... procesar y borrar eventos ...
    pthread_mutex_unlock(&pollset_mutex);
}

Sí, esto serializa el acceso al pollset. Sí, teóricamente añade latencia. ¿Pero sabes qué tiene más latencia? Un servidor que se cae.

El código final de la v11 superó 72 horas de pruebas de estrés con 1.000 conexiones simultáneas. Cero caídas. Cero fugas de memoria. Cero paquetes desordenados.

Capítulo 4: La cosa del -blibpath (que en realidad es una feature)

Una característica genuina de AIX: tienes que especificar explícitamente la ruta de las librerías en tiempo de enlace con -Wl,-blibpath:/tu/ruta. Si no lo haces, el binario no encontrará libstdc++ aunque esté en el mismo directorio.

Al principio esto parece molesto. Luego te das cuenta: AIX prefiere rutas explícitas y deterministas a búsquedas implícitas “mágicas”. En entornos de producción crítica donde “en mi máquina funcionaba” no es una excusa válida, eso es una feature, no un bug.

Capítulo 5: Estabilidad – Los números que importan

Después de todo este sufrimiento, ¿dónde estamos realmente?

El RPM está publicado en aix.librepower.org y desplegado en un sistema IBM POWER9 (12 cores, SMT-8). MariaDB 11.8.5 corre en AIX 7.3 con el Thread Pool activado. El servidor ha superado una batería de QA brutal:

PruebaResultado
100 conexiones concurrentes
500 conexiones concurrentes
1.000 conexiones
30 minutos de carga sostenida
Más de 11 millones de queries
Memory leaksCERO

1.648.482.400 bytes de memoria, constantes durante 30 minutos. Ni un solo byte de deriva (drift). El servidor funcionó durante 39 minutos bajo carga continua y realizó un apagado limpio.

Funciona. Es estable. Está listo para producción.

El impacto del Thread Pool

El trabajo en el pool de hilos proporcionó ganancias masivas para cargas de trabajo concurrentes:

Configuración100 clientes mixtosvs. Baseline
Original -O2 un hilo por conexión11.34s
-O3 + Thread Pool v111.96s83% más rápido

Para cargas de trabajo OLTP de alta concurrencia, esta es la diferencia entre arrastrarse y volar.

Lo que he aprendido (hasta ahora)

  1. CMake asume que eres Linux. En sistemas no-Linux, verifica manualmente la detección de características. Los falsos positivos te morderán en tiempo de ejecución.
  2. La E/S level-triggered requiere disciplina. EPOLLONESHOT existe por una razón. Si tu sistema no lo tiene, prepárate para implementar tu propia serialización.
  3. -O3 expone errores latentes. Si tu código “funciona con -O2 pero no con -O3”, tienes una condición de carrera. El compilador está haciendo su trabajo; el fallo es tuyo.
  4. Los mutex son tus amigos. Sí, tienen overhead. ¿Pero sabes qué tiene más overhead? Depurar condiciones de carrera a las 3 de la mañana.
  5. AIX premia la comprensión profunda. Es un sistema que no perdona los atajos, pero una vez entiendes sus convenciones, es predecible y robusto como una roca. Hay una razón por la que los bancos lo siguen usando.
  6. El ecosistema importa. Proyectos como linux-compat de LibrePower hacen viable el desarrollo moderno en AIX.

¿Qué sigue? La incógnita del rendimiento

El servidor es estable. El Thread Pool funciona. Pero hay una pregunta en el aire que aún no he respondido:

¿Es rápido comparado con Linux?

Ejecuté un benchmark de búsqueda vectorial (el tipo de operación que potencia la IA moderna). Índice MHNSW de MariaDB, 100.000 vectores, 768 dimensiones.

  • Linux en hardware POWER9 idéntico: 971 queries por segundo.
  • AIX con nuestra nueva build: 42 queries por segundo.

23 veces más lento.

Se me cayó el alma a los pies. ¿Tres semanas de trabajo para ser 23 veces más lentos que Linux en el mismo hardware?

Pero esto es lo bonito de la ingeniería: cuando los números no tienen sentido, siempre hay una razón. Y a veces, esa razón resulta ser una noticia sorprendentemente buena.

En la Parte 2, os contaré:

  • Cómo descubrimos que la diferencia de 23x era mayormente un error de configuración.
  • El compilador que lo cambió todo.
  • Por qué “AIX es lento” resultó ser un mito.
  • El “Museo de los Fracasos”: optimizaciones que no sirvieron para nada.

Los RPMs están publicados en aix.librepower.org. La build GCC es funcionalmente estable.

¿Pero la historia del rendimiento? Ahí es donde la cosa se pone realmente interesante.

Pronto la Parte 2.

  • MariaDB 11.8.5 ahora funciona en AIX 7.3 con Thread Pool nativo.
  • Primera implementación de un pool de hilos para AIX usando pollset (necesité 11 iteraciones para simular ONESHOT correctamente).
  • El servidor es estable: 1.000 conexiones, 11M+ queries, cero memory leaks.
  • El Thread Pool mejora un 83% el rendimiento en cargas concurrentes.
  • El benchmark inicial de vectores muestra una diferencia de 23x vs Linux… pero no es lo que parece.
  • RPMs disponibles en aix.librepower.org

¿Preguntas? ¿Ideas? ¿Quieres contribuir al ecosistema Open Source de AIX?

Este trabajo es parte de LibrePower – Desbloqueando IBM Power a través del Open Source. RAS inigualable. TCO superior. Huella mínima 🌍

SIXE