Cómo usamos la replicación diferida para la recuperación de emergencia con PostgreSQL

Cómo usamos la replicación diferida para la recuperación de emergencia con PostgreSQL
La replicación no es una copia de seguridad. ¿O sí? Aquí te mostramos cómo utilizamos la replicación retrasada para la recuperación al eliminar accidentalmente accesos directos.

Especialistas en infraestructura en GitLab se encargan del funcionamiento GitLab.com — el mayor ejemplar de GitLab en la naturaleza. Aquí hay 3 millones de usuarios y casi 7 millones de proyectos, y es uno de los sitios de código abierto SaaS más grandes con una arquitectura dedicada. Sin un sistema de bases de datos PostgreSQL, la infraestructura de GitLab.com no avanzaría mucho, y hacemos todo lo posible para garantizar la disponibilidad en caso de fallos que puedan llevar a la pérdida de datos. Es poco probable que ocurra tal catástrofe, pero estamos bien preparados y contamos con varios mecanismos de copia de seguridad y replicación.

La replicación no es un medio de copia de seguridad de bases de datos (ver más abajo). Pero ahora veremos cómo recuperar rápidamente datos eliminados accidentalmente utilizando la replicación retrasada: en GitLab.com usuario eliminó el acceso directo para el proyecto gitlab-ce y perdió la conexión con las solicitudes de fusión y tareas.

Con la replicación retrasada, restauramos los datos en solo 1.5 horas. Mira cómo fue.

Recuperación a un punto en el tiempo con PostgreSQL

PostgreSQL tiene una función incorporada que restaura el estado de la base de datos a un punto en el tiempo específico. Se llama Recuperación a un Punto en el Tiempo (PITR) y utiliza los mismos mecanismos que mantienen la validez de la réplica: comenzando con una instantánea confiable de todo el clúster de bases de datos (copia de seguridad base), aplicamos una serie de cambios de estado hasta un punto en el tiempo determinado.

Para usar esta función para una copia de seguridad en frío, hacemos copias de seguridad base de la base de datos regularmente y las archivamos (los archivos de respaldo de GitLab residen en almacenamiento en la nube de Google). También rastreamos los cambios de estado de la base de datos archivando el registro de escritura anticipada (write-ahead log, WAL). Y con todo esto, podemos realizar PITR para la recuperación de desastres: comenzamos con una instantánea tomada antes del error y aplicamos cambios del archivo WAL hasta el fallo.

¿Qué es la replicación retrasada?

La replicación retrasada es la aplicación de cambios del WAL con un retraso. Es decir, la transacción ocurrió a la hora X, pero en la réplica aparecerá con un retraso d de una hora X + d.

En PostgreSQL hay 2 formas de configurar una réplica física de la base de datos: recuperación desde archivo y replicación en streaming. Recuperación desde archivo, en esencia, funciona como PITR, pero de forma continua: estamos extrayendo constantemente cambios del archivo WAL y aplicándolos a la réplica. A replicación en streaming extrae directamente el flujo WAL del host de base de datos superior. Preferimos la recuperación desde el archivo, ya que es más fácil de administrar y tiene un rendimiento normal que no se queda atrás del clúster de trabajo.

Cómo configurar la recuperación retrasada desde el archivo

Opciones de recuperación se describen en el archivo recovery.conf. Ejemplo:

standby_mode = 'on'
restore_command = ' /usr/bin/envdir /etc/wal-e.d/env /opt/wal-e/bin/wal-e wal-fetch -p 4 "%f" "%p"'
recovery_min_apply_delay = '8h'
recovery_target_timeline = 'latest'

Con estas configuraciones, hemos configurado una réplica retrasada con recuperación desde el archivo. Aquí se utiliza wal-e para extraer segmentos WAL (restore_command) del archivo, y los cambios se aplicarán en ocho horas (recovery_min_apply_delay). La réplica estará al tanto de los cambios en la línea de tiempo del archivo, por ejemplo, debido a la recuperación de fallos en el clúster (recovery_target_timeline).

Con recovery_min_apply_delay se puede configurar la replicación en streaming con retraso, pero hay un par de trampas relacionadas con los slots de replicación, la retroalimentación de respaldo en caliente, etc. El archivo WAL permite evitarlas.

Parámetro recovery_min_apply_delay apareció solo en PostgreSQL 9.3. En versiones anteriores, para la replicación retrasada se debía configurar una combinación de funciones de control de recuperación (pg_xlog_replay_pause(), pg_xlog_replay_resume()) o mantener segmentos WAL en el archivo durante el período de retraso.

¿Cómo lo hace PostgreSQL?

Es interesante ver cómo PostgreSQL implementa la recuperación retrasada. Observemos cómo recoveryApplyDelay(XlogReaderState). Se llama desde el ciclo principal de repetición para cada registro del WAL.

static bool
recoveryApplyDelay(XLogReaderState *record)
{
    uint8       xact_info;
    TimestampTz xtime;
    long        secs;
    int         microsecs;

    /* no hay nada que hacer si no se ha configurado un retraso */
    if (recovery_min_apply_delay <= 0)
        return false;

    /* no se aplica ningún retraso en una base de datos que aún no es consistente */
    if (!reachedConsistency)
        return false;

    /*
     * ¿Es un registro de COMMIT?
     *
     * Elegimos deliberadamente no retrasar abortos ya que no tienen efecto en
     * MVCC. Ya permitimos la reproducción de registros que no tienen una marca de tiempo,
     * así que ya hay una oportunidad para que surjan problemas causados por conflictos tempranos en
     * réplicas.
     * /
    if (XLogRecGetRmid(record) != RM_XACT_ID)
        return false;

    xact_info = XLogRecGetInfo(record) & XLOG_XACT_OPMASK;

    if (xact_info != XLOG_XACT_COMMIT &&
        xact_info != XLOG_XACT_COMMIT_PREPARED)
        return false;

    if (!getRecordTimestamp(record, &xtime))
        return false;

    recoveryDelayUntilTime =
        TimestampTzPlusMilliseconds(xtime, recovery_min_apply_delay);

    /*
     * Salir sin armar la cerradura si ya ha pasado el tiempo para aplicar este
     * registro
     */
    TimestampDifference(GetCurrentTimestamp(), recoveryDelayUntilTime,
                        &secs, &microsecs);
    if (secs <= 0 && microsecs <= 0)
        return false;

    while (true)
    {
        // Abreviado:
        // Usa WaitLatch hasta que lleguemos a recoveryDelayUntilTime
        // y luego
        break;
    }
    return true;
}

La esencia es que el retraso se basa en el tiempo físico registrado en la marca de tiempo del commit de la transacción (xtime). Como se puede ver, el retraso se aplica solo a los commits y no afecta a otros registros; todos los cambios se aplican directamente, y el commit se retrasa, por lo que veremos los cambios solo después del retraso configurado.

Cómo usar una réplica retrasada para la recuperación de datos

Supongamos que tenemos un clúster de bases de datos en producción y una réplica con ocho horas de retraso. Veamos cómo recuperar datos usando el ejemplo de eliminación accidental de etiquetas.

Cuando nos enteramos del problema, suspendimos la recuperación desde el archivo para la réplica retrasada:

SELECT pg_xlog_replay_pause();

Con la pausa, no corrimos el riesgo de que la réplica repitiera la consulta ELIMINAR. Es algo útil si necesitas tiempo para aclarar todo.

La clave es que la réplica retrasada debe llegar al momento justo antes de la consulta ELIMINAR. Sabíamos aproximadamente el tiempo físico de la eliminación. Eliminamos recovery_min_apply_delay y añadimos recovery_target_time en recovery.conf. Así, la réplica llega al momento deseado sin retrasos:

recovery_target_time = '2018-10-12 09:25:00+00'

Con las marcas de tiempo, es mejor reducir, para no fallar. Sin embargo, cuanto más reduzcas, más datos perderás. Nuevamente, si pasamos la consulta ELIMINAR, todo se eliminará nuevamente y tendrás que empezar desde el principio (o incluso tomar una copia de seguridad fría para PITR).

Reiniciamos la instancia de Postgres diferida y los segmentos WAL se repitieron hasta el tiempo indicado. Se puede rastrear el progreso en esta etapa con la consulta:

SELECT
  -- ubicación actual en WAL
  pg_last_xlog_replay_location(),
  -- marca de tiempo de la transacción actual (estado de la réplica)
  pg_last_xact_replay_timestamp(),
  -- tiempo físico actual
  now(),
  -- la cantidad de tiempo que aún debe aplicarse hasta que se alcance recovery_target_time
  '2018-10-12 09:25:00+00'::timestamptz - pg_last_xact_replay_timestamp() as delay;

Si la marca de tiempo ya no cambia, la recuperación está completa. Se puede configurar la acción recovery_target_action, para cerrar, avanzar o pausar la instancia después del reinicio (por defecto se pausa).

La base de datos ha regresado al estado anterior a la desafortunada consulta. Ahora se puede, por ejemplo, exportar datos. Exportamos los datos eliminados sobre la etiqueta y todas las relaciones con tareas y solicitudes de fusión y los transferimos a la base de datos activa. Si las pérdidas son grandes, se puede simplemente avanzar la réplica y utilizarla como principal. Pero entonces se perderán todos los cambios realizados después del punto hasta donde recuperamos.

En lugar de marcas de tiempo, es mejor usar los ID de transacciones. Es útil registrar estos ID, por ejemplo, para los operadores DDL (tipo DROP TABLE), mediante log_statements = 'ddl'. Si tuviéramos el ID de transacción, tomaríamos recovery_target_xid y ejecutaríamos todo hasta la transacción anterior a la consulta. ELIMINAR.

Regresar al trabajo es muy sencillo: elimine todos los cambios de recovery.conf y reinicie Postgres. Pronto habrá una vez más un retraso de ocho horas en la réplica, y estaremos listos para futuros problemas.

Ventajas de la recuperación

Con una réplica diferida en lugar de una copia de seguridad fría, no es necesario restaurar todo el snapshot del archivo durante horas. Por ejemplo, necesitamos cinco horas para recuperar toda la copia de seguridad base de 2 TB. Y luego además habrá que aplicar todo el WAL diario para restaurar al estado necesario (en el peor de los casos).

Una réplica diferida es mejor que una copia de seguridad fría por dos razones:

  1. No es necesario extraer toda la copia de seguridad base del archivo.
  2. Hay una ventana fija de ocho horas de segmentos WAL que deben ser repetidos.

Además, estamos verificando constantemente si se puede hacer PITR desde el WAL, y notaríamos rápidamente daños u otros problemas con el archivo WAL, siguiendo el retraso de la réplica diferida.

En este ejemplo, tardamos 50 minutos en recuperar, es decir, la velocidad fue de 110 GB de datos WAL por hora (el archivo todavía estaba en AWS S3). En total, resolvimos el problema y restauramos los datos en 1,5 horas.

Conclusiones: dónde puede ser útil la replicación retrasada (y dónde no)

Utiliza la replicación retrasada como un medio de primeros auxilios si accidentalmente has perdido datos y has notado este problema dentro del retraso configurado.

Pero ten en cuenta: la replicación no es una copia de seguridad.

Las copias de seguridad y la replicación tienen diferentes objetivos. Una copia de seguridad fría será útil si accidentalmente hiciste ELIMINAR o DROP TABLE. Hacemos una copia de seguridad desde un almacenamiento frío y restauramos el estado anterior de la tabla o de toda la base de datos. Pero en este caso, la consulta DROP TABLE se reproduce casi instantáneamente en todas las réplicas del clúster de trabajo, por lo que la replicación normal aquí no ayudará. La replicación en sí misma mantiene la base de datos disponible cuando se entregan servidores individuales y distribuye la carga.

Incluso con una réplica retrasada, a veces necesitamos mucho la copia de seguridad fría en un lugar seguro, si de repente ocurre una falla en el centro de datos, un daño oculto u otros eventos que no notarás de inmediato. Aquí la replicación por sí sola no es suficiente.

Nota. En GitLab.com actualmente solo protegemos contra la pérdida de datos a nivel de sistema y no restauramos los datos a nivel de usuario.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster