Replicación Síncrona en He3DB: Mecanismos Internos y Flujo Operativo

Fundamentos de Replicación Síncrona

He3DB incorpora un sistema avanzado de replicación para garantizar alta disponibilidad y consistencia de datos. El modo síncrono asegura que cada transacción confirmada persiste simultáneamente en el servidor primario y en al menos un nodo standby antes de notificar éxito al cliente.

Servidor Primario: Procesa operaciones de lectura y escritura, registrando cada modificación en el registro de adelanto de escritura (WAL).

Servidor Standby: Mantiene copia sincronizada mediante recepción y aplicación continua de registros WAL, operando en modo solo-lectura para consultas y distribución de carga.

Estructuras de Datos Fundamentales

1. Puntero de Registro WAL

El tipo PunteroRegistro representa posiciones lógicas dentro del flujo de transacciones:

typedef uint64 PunteroRegistro;

2. Encabezado de Registro de Transacción

La estructura EncabezadoRegistro define la composición de cada entrada WAL:

typedef struct EncabezadoRegistro
{
    uint32 longitud_total;      /* Tamaño completo del registro */
    IdTransaccion id_xact;      /* Identificador de transacción */
    PunteroRegistro ptr_anterior; /* Referencia al registro previo */
    uint8 banderas_info;        /* Bits de configuración */
    IdGestorRecursos id_gestor; /* Gestor de recursos asociado */
    pg_crc32c codigo_crc;       /* Valor de verificación CRC */
} EncabezadoRegistro;

3. Mecanismo de Sincronización por Latch

El sistema de CerraduraLatch protege estructuras compartidas en entornos multiproceso:

  • Protección de recursos: Evita acceso concurrente a datos críticos
  • Carácter no preemptivo: El proceso mantiente la cerradura hasta liberarla explícitamente
  • Jerarquización: Permite granularidad fina en el control de acceso
  • Implementación en memoria: No depende de primitivas del kernel
  • Reentrancia: Un mismo proceso puede adquirir múltiples veces la misma cerradura
typedef struct CerraduraLatch
{
    sig_atomic_t indicador_activo;
    sig_atomic_t posible_dormido;
    bool modo_compartido;
    int pid_propietario;
#ifdef WIN32
    HANDLE evento_sistema;
#endif
} CerraduraLatch;

4. Número de Secuencia de Log (NSL)

El NSL identifica univocamente cada posición en el archivo WAL mediante offset de bytes:

  • Composición: Timestamp de 64 bits + offset dentro del segmento
  • Consistencia transaccional: Rastrea persistencia de datos en disco
  • Recuperación ante caídas: Permite reestablecer estado consistente
  • Replicación en streaming: Sincroniza avance entre primario y standby
  • Diagnóstico: Facilita monitoreo de frecuencia de cambios

Campo clave en estructuras de proceso:

PunteroRegistro nsl_esperado;  /* NSL mínimo para desbloqueo */

Flujo Operativo del Mecanismo Síncrono

El protocolo de confirmación síncrona sigue esta secuencia:

  1. El servidor primario genera registro WAL al insertar datos y fuerza escritura a disco
  2. El backend ejecutor invoca ProcesarEsperaSincrona, registrándose en la cola compartida
  3. El proceso walreceiver del standby persiste el WAL recibido y notifica al walsender prmiario
  4. El walsender procesa la confirmación y ejecuta DespertarProcesosCola para notificar backends en espera
  5. El backend original recibe la señal, completa la transacción y libera recursos

Gestión de Colas de Sincronización

El sistema mantiene en memoria compartida la estructura ControlEnvioWAL con estas primitivas:

  • InsertarEnColaSincrona: Registra procesos en espera ordenados por NSL
  • CancelarEsperaSincrona: Elimina procesos y libera recursos asociados
  • DespertarProcesosCola: Actualiza estados y desbloquea procesos cuya condición se satisface

Aálisis de Funciones Clave

Confirmación de Transacción

/* En xact.c - ConfirmarTransaccion */
if (!genero_wal) {
    goto limpieza;
}

if (genero_wal && marcar_xid_confirmado) {
    EsperarReplicacionSincrona(ultimo_registro_xact);
}

Lógica de Espera Síncrona

void EsperarReplicacionSincrona(PunteroRegistro punto_confirmacion)
{
    if (!ReplicacionSincronaActivada() || !StandbysSincronosConfigurados())
        return;

    CerraduraLatchAdquirir(BloqueoSyncRep, MODO_EXCLUSIVO);
    
    if (!ControlEnvioWAL->standbys_sincronos_definidos ||
        punto_confirmacion <= ControlEnvioWAL->punto_sincronia[modo]) {
        CerraduraLatchLiberar(BloqueoSyncRep);
        return;
    }

    ProcesoActual->nsl_esperado = punto_confirmacion;
    ProcesoActual->estado_sincronia = ESPERA_SINCRO_ACTIVA;
    InsertarEnColaSincrona(modo);
    
    CerraduraLatchLiberar(BloqueoSyncRep);

    while (true) {
        ReiniciarLatch(ProcesoActual->latch);
        
        if (ProcesoActual->estado_sincronia == ESPERA_SINCRO_COMPLETA)
            break;
            
        if (SenalTerminacionPendiente) {
            ReportarAdvertencia();
            CancelarEsperaSincrona();
            break;
        }
        
        if (CancelacionConsultaPendiente) {
            CancelacionConsultaPendiente = false;
            ReportarAdvertenciaUsuario();
            CancelarEsperaSincrona();
            break;
        }
        
        int resultado = EsperarLatch(ProcesoActual->latch, 
                                   WL_LATCH_SET | WL_POSTMASTER_DEATH,
                                   -1, EVENTO_ESPERA_SYNC_REP);
                                   
        if (resultado & WL_POSTMASTER_DEATH) {
            SenalTerminacionPendiente = true;
            CancelarEsperaSincrona();
            break;
        }
    }

    BarreraLectura();
    ProcesoActual->estado_sincronia = ESPERA_SINCRO_INACTIVA;
    ProcesoActual->nsl_esperado = 0;
}

Inserción Ordenada en Cola

void InsertarEnColaSincrona(int modo)
{
    PGPROC *proc_iterador;
    
    proc_iterador = (PGPROC*) SHMQueuePrev(
        &(ControlEnvioWAL->ColaSyncRep[modo]),
        &(ControlEnvioWAL->ColaSyncRep[modo]),
        offsetof(PGPROC, enlaces_sync_rep));
    
    while (proc_iterador) {
        if (proc_iterador->nsl_esperado < ProcesoActual->nsl_esperado)
            break;
            
        proc_iterador = (PGPROC*) SHMQueuePrev(
            &(ControlEnvioWAL->ColaSyncRep[modo]),
            &(proc_iterador->enlaces_sync_rep),
            offsetof(PGPROC, enlaces_sync_rep));
    }
    
    if (proc_iterador)
        SHMQueueInsertAfter(&(proc_iterador->enlaces_sync_rep), 
                           &(ProcesoActual->enlaces_sync_rep));
    else
        SHMQueueInsertAfter(&(ControlEnvioWAL->ColaSyncRep[modo]), 
                           &(ProcesoActual->enlaces_sync_rep));
}

Procesamiento de Confirmaciones Standby

/* En walsender.c - ProcesarMensajeStandby */
if (!es_replicacion_cascada) {
    LiberarProcesosEnEspera();
}

Liberación de Esperas Pendientes

void LiberarProcesosEnEspera(void)
{
    if (ControlEnvioWAL->punto_sincronia[ESPERA_ESCRITURA] < MiEnvioWAL->punto_escritura) {
        ControlEnvioWAL->punto_sincronia[ESPERA_ESCRITURA] = MiEnvioWAL->punto_escritura;
        procesos_despertados = DespertarProcesosCola(false, ESPERA_ESCRITURA);
    }
    
    if (ControlEnvioWAL->punto_sincronia[ESPERA_FLUSH] < MiEnvioWAL->punto_flush) {
        ControlEnvioWAL->punto_sincronia[ESPERA_FLUSH] = MiEnvioWAL->punto_flush;
        procesos_despertados = DespertarProcesosCola(false, ESPERA_FLUSH);
    }
}

Despertar Procesos por NSL

int DespertarProcesosCola(bool todos, int modo)
{
    PGPROC *proc_actual, *proc_siguiente;
    
    proc_siguiente = (PGPROC*) SHMQueueNext(
        &(ControlEnvioWAL->ColaSyncRep[modo]),
        &(ControlEnvioWAL->ColaSyncRep[modo]),
        offsetof(PGPROC, enlaces_sync_rep));
    
    while (proc_siguiente) {
        if (!todos && ControlEnvioWAL->punto_sincronia[modo] < proc_siguiente->nsl_esperado)
            return contador_procesos;
            
        proc_actual = proc_siguiente;
        proc_siguiente = (PGPROC*) SHMQueueNext(
            &(ControlEnvioWAL->ColaSyncRep[modo]),
            &(proc_siguiente->enlaces_sync_rep),
            offsetof(PGPROC, enlaces_sync_rep));
            
        proc_actual->estado_sincronia = ESPERA_SINCRO_COMPLETA;
        SetLatch(&(proc_actual->latch));
        contador_procesos++;
    }
    
    return contador_procesos;
}

Los procesos en espera solo pueden desbloquearse mediante cancelación explícita, terminación del backend o caída del postmaster principal.

Etiquetas: He3DB replicacion-sincrona wal postgres alta-disponibilidad

Publicado el 9-7 19:33