Implementación de LWLock en He3DB para PostgreSQL

He3DB, una base de datos inspirada en la arquitectura de Aurora y basada en PostgreSQL, integra una separación de cómputo y almacenamiento, junto con una estratificación de datos fríos y calientes para optimizar la relación costo-rendimiento. En entornos de bases de datos como He3DB para PostgreSQL, donde múltiples sesiones pueden intentar acceder simultáneamente a los mismos recursos, la gestión de la concurrencia es fundamental para garentizar tanto la eficiencia como la integridad de los datos. Si bien el control de concurrencia multiversión (MVCC) aborda gran parte de estas necesidades, no cubre todos los escenarios. Por ello, es indispensable complementar MVCC con mecanismos de bloqueo tradicionales para situaciones que requieren un control explícito sobre puntos de conflicto específicos o que no demandan un aislamiento transaccional completo.

La estrategia de gestión de bloqueos en He3DB se articula en torno a tres componentes principales: módulos de funcionalidad de bloqueo, gestión de niveles de bloqueo y manejo de interbloqueos. El módulo de funcionalidad de bloqueo soporta tres tipos fundamentales: spinlocks, bloqueos ligeros (LWLock) y bloqueos transaccionales. La gestión de niveles de bloqueo opera en distintas granularidades, incluyendo tablas, páginas, tuplas y transacciones. Finalmente, el módulo de interbloqueos se encarga de la detección y resolución de estos.

Fundamentos de LWLock

Los Lightweight Locks (LWLock) están diseñados principalmente para proporcionar acceso mutuo a estructuras de datos compartidas dentro del sistema. Ofrecen dos modos de operación: exclusivo y compartido. A diferencia de los bloqueos transaccionales, los LWLock se caracterizan por tener colas de espera, no realizar detección de interbloqueos y liberarse automáticamente en caso de fallos del proceso, lo que previene problemas de bloqueos persistentes.

Estructuras de Datos Centrales

A continuación, se describen las estructuras de datos fundamentales para la implementación de LWLock en He3DB:

Modos de Bloqueo Ligeros (LWLockMode)

Este enumerado define los posibles modos en los que un LWLock puede ser adquirido:

typedef enum LWLockMode
{
    MODO_EXCLUSIVO,             // Bloqueo en modo exclusivo
    MODO_COMPARTIDO,            // Bloqueo en modo compartido
    MODO_ESPERA_HASTA_LIBRE    // Modo especial para indicar espera hasta que el bloqueo esté libre
} LWLockMode;

Definición de Bloqueo Ligero (LWLock)

La estructura LWLock encapsula la información esencial de cada bloqueo ligero:

typedef struct LWLock
{
    uint16          segmentoID;       // Identificador del segmento o tramo
    pg_atomic_uint32 estadoBloqueo;   // Estado actual del bloqueo (exclusivo/compartido)
    proclist_head   colaEsperando;    // Lista de procesos PGPROC esperando este bloqueo
#ifdef LOCK_DEBUG
    pg_atomic_uint32 numEsperando;    // Número de procesos en espera
    struct PGPROC  *propietarioExclusivo; // Último propietario exclusivo del bloqueo
#endif
} LWLock;

Estadísticas de LWLock (lwlock_stats)

Esta estructura se utiliza para registrar métricas de rendimiento y uso de los LWLock, almacenadas típicamente en una tabla hash:

typedef struct lwlock_stats
{
    lwlock_stats_key clave;              // Clave para la tabla hash
    int              adquisicionesCompartidas; // Conteo de adquisiciones en modo compartido
    int              adquisicionesExclusivas; // Conteo de adquisiciones en modo exclusivo
    int              conteoBloqueos;         // Conteo de veces que el bloqueo causó espera
    int              desencoladosPropios;    // Conteo de procesos que se desencolaron a sí mismos
    int              retrasosSpinlock;     // Conteo de retrasos debido a spinlocks
} lwlock_stats;

Manejador de LWLock (LWLockHandle)

Define una estructura para almacenar los LWLock que un proceso ha adquirido:

typedef struct LWLockHandle
{
    LWLock     *bloqueoPtr; // Puntero al objeto LWLock
    LWLockMode  modoAdquisicion; // Modo en el que se adquirió el bloqueo
} LWLockHandle;

Estado de Espera de LWLock (LWLockWaitState)

Enumera los posibles estados de un proceso mientras espera un LWLock:

typedef enum LWLockWaitState
{
    NO_ESPERANDO,        // No está esperando / ha sido despertado
    ACTUALMENTE_ESPERANDO, // Actualmente en estado de espera
    DESPERTAR_PENDIENTE  // Eliminado de la lista de espera, pero aún no señalado
} LWLockWaitState;

He3DB gestiona todos los LWLock a través de un arreglo global, MainLWLockArray. Estos bloqueos están categorizados y definidos en un archivo como lwlocknames.txt, permitiendo más de cuarenta tipos diferentes de LWLock para una segmentación granular y la posibilidad de definir tipos personalizados.

Principios Operativos de LWLock

Los LWLock facilitan el acceso concurrente a estructuras de datos compartidas. Su diseño permite dos modos: exclusivo y compartido. Carecen de detección de interbloqueos, pero la gestión de estos bloqueos se recupera automáticamente en caso de errores, asegurando que no queden bloqueos huérfanos. Se apoyan en spinlocks para escenarios de baja contención, lo que permite una adquisición y liberación rápida. Sin embargo, cuando hay contención, los procesos se bloquean de manera similar a un semáforo, evitando el consumo innecesario de CPU y procesando las solicitudes en un orden FIFO.

Ciclo de Vida de los LWLock

El manejo de LWLock comprende varias fases cruciales:

  1. Asignación de Memoria Compartida: Al iniciar el proceso postmaster, se calcula el número de LWLock necesarios y se asigna el espacio correspondiente en la memoria compartida.
  2. Inicialización de LWLock: Después de la asignación, cada LWLock se inicializa a un estado "desbloqueado". También se inicializan contadores para la gestión dinámica de los LWLock en la cola.
  3. Obtención de un LWLock Disponible: Esta operación recupera un LWLock de un pool predefinido de bloqueos inactivos en la memoria compartida. Se actualizan los contadores y se devuelve un puntero al LWLock libre.
  4. Adquisición de LWLock: La función LWLockAcquire intenta obtener un LWLock específico en un modo dado (compartido o exclusivo). Si el bloqueo no está disponible, el proceso se une a una cola de espera y entra en un estado de suspensión hasta ser notificado. El flujo es el siguiente: se habilitan las interrupciones, se intenta adquirir el bloqueo, si está libre, se concede y el proceso continúa; de lo contrario, el proceso se añade a la cola de espera y espera a ser despertado para repetir el intento. He3DB también ofrece LWLockConditionalAcquire, una variante no bloqueante que devuelve TRUE si se adquiere el bloqueo inmediatamente, y FALSE en caso contrario.
  5. Liberación de LWLock: La función LWLockRelease libera un bloqueo específico. El proceso implica asegurar el bloqueo internamente, verificar si hay procesos en espera que puedan ser despertados, recorrer la cola de espera para despertar a los procesos que solicitan acceso compartido y luego a los que solicitan acceso exclusivo, y finalmente liberar el LWLock. Existe también LWLockReleaseAll, una función para liberar todos los LWLock retenidos por un proceso, típicamente usada durante la recuperación de errores.

Funciones de Interfaz Clave

Las siguientes funciones son las interfaces principales para interactuar con los LWLock:

Función de Interfaz Descripción
void InicializarLWLocks(void) Prepara el subsistema de LWLock.
Size CalcularTamañoShmemLWLock(void) Determina el tamaño de la memoria compartida necesaria para los LWLock.
bool AdquirirLWLock(LWLock *bloqueo, LWLockMode modo) Intenta adquirir un LWLock en el modo especificado, bloqueando si es necesario.
bool AdquirirLWLockCondicional(LWLock *bloqueo, LWLockMode modo) Intenta adquirir un LWLock de forma no bloqueante.
void LiberarLWLock(LWLock *bloqueo) Libera un LWLock previamente adquirido.
void LiberarTodosLWLocks(void) Libera todos los LWLock que el proceso actual tiene en posesión.

Etiquetas: He3DB PostgreSQL LWLock ConcurrencyControl DatabaseInternals

Publicado el 7-27 21:28