Member of The Internet Defense League Últimos cambios
Últimos Cambios
Blog personal: El hilo del laberinto Geocaching

Persistencia en la Base de Datos Distribuida

Última Actualización: 12 de Mayo de 2003 - Lunes

Este documento describe con detalle el sistema de persistencia para la base de Datos Distribuida de IRC-Hispano.


El problema

La BDD (Base de Datos Distribuida) se almacena en disco, pero se carga completamente en memoria cuando lanza el servidor. Los cambios subsiguientes se realizan simultaneamente tanto en disco duro como en memoria.

Este esquema permite que las búsquedas y las modificaciones sean muy rápidas, pero tiene dos problemas fundamentales:

  1. La carga en memoria de la BDD es un proceso muy lento, del tipo O(n), donde "n" es el número de registros en la BDD. Ese valor puede ser mucho mayor que el número de registros "activos" en la BDD, en función de las modificaciones, borrados y compactaciones que se realicen en la BDD.

  2. Al tener toda la BDD en memoria, se necesita disponer de RAM o, en su defecto, de una memoria virtual "decente". Esto no debería ser un problema real para las máquinas UNIX actuales.

Obviando el segundo punto, se ve claramente que el tiempo de arranque de un servidor de IRCD puede ser muy elevado, en función del número de registros en la BDD. En mi sistema de desarrollo, lento, cargar una BDD de unos 250.000 registros consume aproximadamente un minuto de CPU.

Esta situación no es tolerable, y lo será aún menos cuando el número de registros (en particular, modificaciones) crezca para acomodar las nuevas funcionalidades de registros distribuidos de canales.

Optimizar este proceso también reduce la necesidad y frecuencia de las compactaciones de la BDD, proceso lento y delicado.


La solución

La carga de la BDD en memoria se puede optimizar (por ejemplo, fusionando "malloc()"), pero siempre tendrá una dependencia O(n) del número de registros en la BDD. La alternativa evidente es utilizar una base de datos clave/registro clásica, como la BerkeleyDB, pero ello supone un mantenimiento extra (a nivel de BDD) que muchos administradores no tendrán conocimientos, tiempo o ganas de hacer.

Una solución más sencilla es utilizar tecnología de "persistencia".

Es decir, ya que cargamos la BDD en memoria al arrancar el servidor IRCD, y vamos siguiendo los cambios de la BDD en las estructuras en memoria, una solución sería que dicha memoria fuera "persistente" y mantuviese su valor entre "reinicios" del servidor IRCD (incluyendo reinicios de la propia máquina que soporta el IRCD). De esta forma podemos arrancar el servidor IRCD y encontrarnos con que ya tenemos toda la BDD en memoria, tal y como la dejamos al cerrar la instancia previa del programa. No necesitamos recrear ninguna estructura o releer las tablas en disco duro.

En un entorno así es imprescindible, no obstante, asegurarnos de que las estructuras que "persisten" no estén incompletas o corruptas, como ocurriría si el servidor IRCD "muere" en un momento inconveniente (bug) o hay un corte de suministro eléctrico. Es decir, el sistema de persistencia debe utilizar también un sistema de control de integridad y "frescura" de la información, para asegurarnos que las estructuras en memoria se corresponden, escrupulosamente con la última versión de la BDD.


Implementación

El primer paso para implementar una BDD persistente es definir un mecanismo persistente para crear, borrar y modificar registros. El mecanismo ya existe, en forma de "malloc()". En vez de reescribir el sistema de BDD para emplear otro sistema, lo que hacemos es dividir los "malloc()" en dos tipos: "normales" y "persistentes".

Los "malloc()" "normales" son las funciones de toda la vida, que proporcionan memoria que se pierde cuando el programa muere.

Definimos un nuevo tipo de "malloc()", "persistente", cuyo contenido "persiste" incluso entre reinicios del sistema operativo. Esto se hace utilizando una nueva librería "malloc" y empleando un fichero en disco, mapeado en memoria, para realizar los "malloc()" y "free()" persistentes. Dicho fichero en disco existe siempre, independientemente de que se esté ejecutando el IRCD.

Cuando el IRCD arranca, mapea el fichero persistente en memoria. Este mapa proporciona, así, de forma automática, el contenido del fichero en memoria, sin tener que leerlo manualmente (esto lo hace el sistema automáticamente, bajo "demand paging"). Ese mapa contiene una implementación "malloc" completa, con todos los punteros y estructuras como estaban cuando se cerró el IRCD. Es decir, tenemos todos los datos disponibles automáticamente y podemos hacer "malloc()" y "free()" sobre esa memoria como si no hubiera habido un corte intermedio en la ejecución del programa.

Es así como se obtiene el sistema de persistencia.

Naturalmente hay que asegurarse de que el fichero es consistente, reciente y utilizable. Puede ser, por ejemplo, que hubiese habido un corte en el suministro eléctrico mientras había bloques de memoria "sucios", aún no enviados al disco duro. O puede ser que el IRCD corrompa memoria y que, al reiniciarse, se encuentre con la memoria corrompida en el sistema de persistencia y sea incapaz de recuperarse.

Así pues, el siguiente paso consiste en asegurarnos que el fichero en disco es correcto, antes de utilizarlo. Si detectamos cualquier problema simplemente recrearemos el fichero desde cero, recargando la BDD de disco, como si no hubiese persistencia. Ello soluciona el problema y, además, inicializa el sistema de persistencia para que pueda utilizarse en el siguiente reinicio.

El sistema de verificación de integridad y "frescura" del fichero persistente se basa en tres componentes:

  1. Mapeo inicial del fichero de persistencia en memoria. Se comprueba:

    • Que el fichero se haya cerrado correctamente y no esté corrupto. Para ello se realiza una verificación criptográfica, cuya probabilidad de error es despreciable (mucho menor que 2-64).

    • Que el sistema de persistencia sea compatible con el IRCD actual, por si hay actualizaciones incompatibles.

    • Que no se hayan cambiado configuraciones del sistema de persistencia. En particular, que no se haya reconfigurado el tamaño del fichero MMAP.

    • Que los ficheros de BDD no hayan sido alterados.

    Exceptuando la verificación criptográfica, el resto de operaciones son prácticamente instantaneas. La verificación criptográfica en sí tiene un tiempo de ejecución O(m) proporcional a la cantidad de memoria persistente utilizada, no al número de registros en la BDD. En general m<<n. Además, el coeficiente O(m) es mucho menor que el de O(n), por lo que el tiempo de inicialización será muy bajo. En el caso descrito más arriba, el tiempo necesario para inicializar el sistema de persistencia es de unos 2 o 3 segundos.

  2. Cada vez que se modifica una BDD, se comprueba que la BDD en disco duro no haya sido alterada por ningún otro proceso.

  3. Cuando se cierra el IRCD nos aseguramos de que el sistema de persistencia tenga datos actualizados. Se genera una verificación criptográfica, por si el fichero no se actualiza correctamente. Por ejemplo, se puede cerrar el IRCD con páginas "sucias" en el sistema de persistencia. El sistema de memoria virtual del sistema operativo se asegura de que esas páginas sucias sean a) vistas por cualquier otro proceso del sistema, aunque no estén almacenadas aún en disco duro y b) almacenadas en disco duro tras un tiempo prudencial. Si antes de grabar en disco se produce un fallo eléctrico, el sistema de persistencia queda corrupto (le faltan páginas), y la suma criptográfica se asegura de que lo detectemos.

Un último detalle del sistema de persistencia es que algunos registros tienen efectos colaterales que debemos recrear. Por ejemplo, los canales persistentes. Para este caso concreto tenemos tres opciones:

  1. Migrar cada canal del sistema de persistencia al sistema "clásico" y viceversa, en función de sus visicitudes con los registros. Este proceso es complejo porque no sabemos cuántos punteros señalan un canal y, por tanto, resulta muy recomendable no cambiar su posición en memoria durante su existencia.

  2. Dado que en el futuro la inmensa mayoría de los canales existentes en la red serán persistentes, podríamos almacenar directamente todos los canales en el sistema de persistencia, independientemente de que sean persistentes o no. Ello supone recorrer la lista de canales cuando se arranca el programa, a la caza de canales no persistentes para borrarlos.

    Surge la duda también de dónde almacenar la información adicional de un canal, como su lista de usuarios, su "topic" o la la lista de "invites" o "baneos".

    Toda esa información puede introducir una carga apreciable en el sistema de persistencia, pero probablemente sea el camino a seguir en el futuro.

  3. Por último, la opción implementada en u2.10.H.06.99 simplemente recorre la BDD de canales persistentes y los recrea en memoria en el momento de reiniciar el servidor de IRCD. Es la opción apropiada cuando tenemos pocos canales persistentes. Será deseable migrar a la opción anterior a medida que el número de registros aumente.

Los programadores interesados en estudiar las interioridades del sistema de persistencia pueden curiosear los cambios en "s_bdd.c" y el nuevo fichero "persistent_malloc.c".


Historia

  • 12/May/03: Primera versión de este documento.



Python Zope ©2003 jcea@jcea.es

Más información sobre los OpenBadges

Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS