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:
- El servidor primario genera registro WAL al insertar datos y fuerza escritura a disco
- El backend ejecutor invoca
ProcesarEsperaSincrona, registrándose en la cola compartida - El proceso
walreceiverdel standby persiste el WAL recibido y notifica alwalsenderprmiario - El
walsenderprocesa la confirmación y ejecutaDespertarProcesosColapara notificar backends en espera - 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 NSLCancelarEsperaSincrona: Elimina procesos y libera recursos asociadosDespertarProcesosCola: 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.