
La replicación no es una copia de seguridad. O no? Así es como usamos la replicación diferida para la recuperación al eliminar accesos directos accidentalmente.
Los especialistas en infraestructura de GitLab son responsables de ejecutar GitLab.com , la mayor instancia de GitLab en la naturaleza. Hay 3 millones de usuarios y casi 7 millones de proyectos, y este es uno de los sitios SaaS de código abierto más grandes con una arquitectura dedicada. Sin el sistema de base de datos PostgreSQL, la infraestructura de GitLab.com no irá muy lejos, y simplemente no lo hacemos por tolerancia a fallas en caso de fallas cuando se pueden perder datos. Es poco probable que ocurra tal catástrofe, pero estamos bien preparados y equipados con diferentes mecanismos de copia de seguridad y replicación.
La replicación no es una herramienta de respaldo de la base de datos para usted ( ver más abajo ). Pero ahora veremos cómo recuperar rápidamente los datos eliminados accidentalmente mediante la replicación retrasada: en GitLab.com, el usuario eliminó el acceso directo para el proyecto gitlab-ce
y perdió contacto con solicitudes y tareas de fusión.
Con una réplica retrasada, recuperamos datos en solo 1.5 horas. Mira cómo fue.
Recuperación de punto en el tiempo con PostgreSQL
PostgreSQL tiene una función integrada que restaura el estado de la base de datos en un momento específico. Se llama Point-in-Time Recovery (PITR) y utiliza los mismos mecanismos que mantienen la relevancia de la réplica: comenzando con una instantánea confiable de todo el clúster de la base de datos (copia de seguridad básica), aplicamos una serie de cambios de estado hasta cierto punto en el tiempo.
Para usar esta función para una copia de seguridad en frío, regularmente hacemos una copia de seguridad básica de la base de datos y la almacenamos en un archivo (los archivos de GitLab viven en el almacenamiento en la nube de Google ). También supervisamos los cambios en el estado de la base de datos archivando el registro de escritura anticipada (WAL). Y con todo esto, podemos realizar PITR para la recuperación ante desastres: comenzamos con la imagen tomada antes del error y aplicamos los cambios desde el archivo WAL hasta la falla.
¿Qué es la replicación diferida?
La replicación diferida es la aplicación de cambios WAL retrasados. Es decir, la transacción ocurrió en la hora X
, pero aparecerá en la réplica con un retraso de d
en la hora X + d
.
PostgreSQL tiene 2 formas de configurar la réplica física de la base de datos: restaurar desde el archivo y la replicación de transmisión. La restauración desde el archivo , de hecho, funciona como PITR, pero continuamente: extraemos constantemente los cambios del archivo WAL y los aplicamos a la réplica. Y la replicación de transmisión recupera directamente la transmisión WAL del host de la base de datos ascendente. Preferimos la recuperación del archivo: es más fácil de administrar y tiene un rendimiento normal, que no va a la zaga del clúster de trabajo.
Cómo configurar la recuperación diferida del archivo
Las opciones de recuperación se describen en el archivo recovery.conf
. Un ejemplo:
standby_mode = 'on' restore_command = '/usr/bin/envdir /etc/wal-ed/env /opt/wal-e/bin/wal-e wal-fetch -p 4 "%f" "%p"' recovery_min_apply_delay = '8h' recovery_target_timeline = 'latest'
Con estos parámetros, configuramos una réplica perezosa con recuperación del archivo. Aquí wal-e se usa para extraer segmentos WAL ( restore_command
) del archivo, y los cambios se aplicarán después de ocho horas ( recovery_min_apply_delay
). La réplica supervisará los cambios en la línea de tiempo en el archivo, por ejemplo, debido a la conmutación por error en el clúster ( recovery_target_timeline
).
Con recovery_min_apply_delay
puede configurar la replicación de transmisión diferida, pero hay un par de trucos asociados con las ranuras de replicación, retroalimentación de repuesto dinámico, etc. El archivo WAL le permite evitarlos.
El parámetro recovery_min_apply_delay
apareció solo en PostgreSQL 9.3. En versiones anteriores, para la replicación diferida, debe configurar una combinación de funciones de administración de recuperación ( pg_xlog_replay_pause(), pg_xlog_replay_resume()
) o mantener segmentos WAL en el archivo para el retraso de tiempo.
¿Cómo hace esto PostgreSQL?
Curioso por ver cómo PostgreSQL implementa la recuperación diferida. Veamos recoveryApplyDelay(XlogReaderState)
. Se llama desde el bucle principal para cada entrada en el WAL.
static bool recoveryApplyDelay(XLogReaderState *record) { uint8 xact_info; TimestampTz xtime; long secs; int microsecs; /* nothing to do if no delay configured */ if (recovery_min_apply_delay <= 0) return false; /* no delay is applied on a database not yet consistent */ if (!reachedConsistency) return false; /* * Is it a COMMIT record? * * We deliberately choose not to delay aborts since they have no effect on * MVCC. We already allow replay of records that don't have a timestamp, * so there is already opportunity for issues caused by early conflicts on * standbys. */ 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); /* * Exit without arming the latch if it's already past time to apply this * record */ TimestampDifference(GetCurrentTimestamp(), recoveryDelayUntilTime, &secs, µsecs); if (secs <= 0 && microsecs <= 0) return false; while (true) { // Shortened: // Use WaitLatch until we reached recoveryDelayUntilTime // and then break; } return true; }
La conclusión es que el retraso se basa en el tiempo físico registrado en la marca de tiempo de confirmación de transacción ( xtime
). Como puede ver, la demora se aplica solo a las confirmaciones y no toca otros registros: todos los cambios se aplican directamente y la confirmación se retrasa, de modo que veremos los cambios solo después de que se configure la demora.
Cómo usar la réplica perezosa para recuperar datos
Digamos que tenemos un clúster de base de datos en producción y una réplica con un retraso de ocho horas. Veamos cómo recuperar datos usando el ejemplo de eliminar accesos directos accidentalmente .
Cuando descubrimos el problema, detuvimos la recuperación del archivo para la réplica diferida:
SELECT pg_xlog_replay_pause();
Con una pausa, no teníamos riesgo de que la réplica repitiera la solicitud DELETE
. Lo útil si necesita tiempo para resolverlo.
La conclusión es que la réplica diferida debe llegar al punto anterior a la solicitud DELETE
. Aproximadamente conocimos el tiempo físico de la remoción. Eliminamos recovery_min_apply_delay
y agregamos recovery_target_time
a recovery.conf
. Entonces la réplica llega sin demora al momento correcto:
recovery_target_time = '2018-10-12 09:25:00+00'
Con marcas de tiempo, es mejor reducir el exceso para no perderse. Es cierto que cuanto mayor es la disminución, más datos perdemos. Una vez más, si pasamos por DELETE
solicitud DELETE
, todo se eliminará nuevamente y tendrá que comenzar de nuevo (o incluso tomar una copia de seguridad en frío para PITR).
Reiniciamos la instancia diferida de Postgres, y los segmentos WAL se repitieron hasta el tiempo especificado. Puede realizar un seguimiento del progreso en esta etapa mediante solicitud:
SELECT -- current location in WAL pg_last_xlog_replay_location(), -- current transaction timestamp (state of the replica) pg_last_xact_replay_timestamp(), -- current physical time now(), -- the amount of time still to be applied until recovery_target_time has been reached '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. Puede configurar la acción recovery_target_action
para cerrar, avanzar o pausar una instancia después de una reproducción (de forma predeterminada, se detiene).
La base de datos llegó a un estado anterior a esa solicitud desafortunada. Ahora puede, por ejemplo, exportar datos. Exportamos los datos eliminados sobre el acceso directo y todas las conexiones con tareas y solicitudes de fusión y los transferimos a la base de datos de trabajo. Si las pérdidas son a gran escala, simplemente puede promocionar la réplica y usarla como la principal. Pero entonces todos los cambios se perderán después del momento en que nos hayamos recuperado.
En lugar de marcas de tiempo, es mejor usar ID de transacción. Es útil escribir estos ID, por ejemplo, para sentencias DDL (como DROP TABLE
), usando log_statements = 'ddl'
. Si tuviéramos una ID de transacción, tomaríamos recovery_target_xid
y ejecutaremos todo hasta la transacción antes de la solicitud DELETE
.
Volver al trabajo es muy simple: elimine todos los cambios de recovery.conf
y reinicie Postgres. Pronto, una demora de ocho horas aparecerá nuevamente en la réplica, y estamos listos para futuros problemas.
Beneficios de recuperación
Con una réplica retrasada, en lugar de una copia de seguridad en frío, no tiene que restaurar la imagen completa del archivo durante horas. Por ejemplo, necesitamos cinco horas para obtener la copia de seguridad básica completa de 2 TB. Y luego todavía tiene que aplicar el WAL diario completo para recuperarse al estado deseado (en el peor de los casos).
Una réplica retrasada es mejor que una copia de seguridad en frío de dos maneras:
- No es necesario obtener toda la copia de seguridad base del archivo.
- Hay una ventana fija de ocho horas de segmentos WAL que deben repetirse.
También verificamos constantemente si es posible hacer PITR desde WAL, y notamos rápidamente daños u otros problemas con el archivo WAL, monitoreando el retraso de la réplica demorada.
En este ejemplo, nos tomó 50 minutos recuperarnos, es decir, la velocidad era 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.
En pocas palabras: donde la réplica retrasada es útil (y donde no)
Utilice la replicación diferida como primeros auxilios si pierde accidentalmente datos y nota este desastre dentro del retraso configurado.
Pero tenga en cuenta: la replicación no es una copia de seguridad.
La copia de seguridad y la replicación tienen diferentes objetivos. Una copia de seguridad en frío es útil si accidentalmente realizó una DELETE
o DROP TABLE
. Hacemos una copia de seguridad desde el almacenamiento en frío y restauramos el estado anterior de la tabla o la base de datos completa. Pero al mismo tiempo, la consulta DROP TABLE
se reproduce casi instantáneamente en todas las réplicas en el clúster de trabajo, por lo que la replicación regular no lo salvará aquí. La replicación en sí misma mantiene la base de datos accesible cuando se alquilan servidores separados y distribuye la carga.
Incluso con una réplica retrasada, a veces realmente necesitamos una copia de seguridad fría en un lugar seguro si un centro de datos falla, daños ocultos u otros eventos que no se notan de inmediato. No tiene sentido una réplica.
Nota En GitLab.com, ahora protegemos contra la pérdida de datos solo a nivel de sistema y no restauramos datos a nivel de usuario.