Completan la migración de una base de datos PostgreSQL con 11 segundos de downtime
(gds.blog.gov.uk)- GOV.UK Notify trasladó una base de datos PostgreSQL 11 de 400 GB a PostgreSQL 15 RDS en su propia cuenta de AWS antes del cierre de PaaS, reduciendo el downtime a unos 11 segundos
- En la base de datos de destino primero crearon solo las tablas y copiaron los datos con DMS full load; luego aplicaron índices y restricciones de claves para reducir el tiempo de carga masiva
- La base de datos de origen tenía alrededor de 1,300 millones de filas, 85 tablas, 185 índices y 120 claves foráneas; en días hábiles recibía unas 1,000 inserciones o modificaciones por segundo y una cantidad similar de lecturas
- Como el redespliegue de la aplicación tardaba unos 5 minutos, prepararon con anticipación las mismas credenciales y un cambio por ponderación de DNS en Route53 para reducir el tiempo real de conmutación
- Eligieron DMS porque era más fácil recibir soporte de PaaS y AWS, aunque para migraciones entre PostgreSQL pudo haber sido más simple usar alternativas como pglogical
Migración de la base de datos de Notify ante el cierre de PaaS
- GOV.UK Notify estaba alojado en GOV.UK Platform as a Service y, debido al cierre de PaaS, tuvo que trasladar toda su infraestructura a su propia cuenta de AWS
- La base de datos de Notify era AWS RDS PostgreSQL dentro de la cuenta de AWS de PaaS, y almacenaba desde datos de envío de notificaciones hasta el contenido de cientos de miles de plantillas usadas por los equipos de servicio
- Para la migración, distinguieron la base de datos existente como source database y la nueva como target database
- El desafío principal no era crear una nueva base de datos PostgreSQL, sino minimizar el downtime mientras se trasladaban todos los datos y se cambiaba el destino de conexión de la aplicación
Tamaño de la base de datos de origen y restricciones del servicio
- La base de datos de origen era PostgreSQL 11 y tenía un tamaño aproximado de 400 GB
- Alrededor de 1,300 millones de filas
- 85 tablas
- 185 índices
- 120 claves foráneas
- En un día hábil normal se producían unas 1,000 inserciones o modificaciones por segundo, además de una cantidad similar de lecturas
- GOV.UK Notify envía millones de notificaciones importantes y sensibles al tiempo todos los días, como alertas de inundaciones o avances en solicitudes de pasaporte
- Como todo envío de notificaciones requiere acceso a la base de datos, era necesario mantener muy breve la interrupción del servicio
Carga inicial y replicación continua con DMS
- El equipo de PaaS ofrecía un método de migración de bases de datos usando AWS Database Migration Service
- DMS se encarga de trasladar datos desde la base de datos de origen hacia la de destino, y puede ejecutarse en la cuenta de AWS de origen o de destino
- Las tareas de DMS se dividen en dos etapas
- full load: copia, tabla por tabla, todos los datos existentes hasta un momento determinado
- replicación continua: reproduce en la base de datos de destino las nuevas transacciones de la base de datos de origen para mantener ambas sincronizadas
- El equipo de Notify se encargó directamente de cambiar la aplicación para que usara la base de datos de destino en lugar de la de origen
Preparación de la base de datos de destino y full load
- La instancia de DMS se creó en la cuenta de AWS de origen
- Como el equipo de PaaS ya tenía configurada una instancia de DMS dentro de la cuenta, pudieron prepararla rápidamente
- La instancia de DMS necesitaba credenciales para conectarse a las bases de datos PostgreSQL de origen y de destino
- Como la instancia de DMS y la base de datos de destino estaban en VPC distintas, configuraron VPC peering para que el tráfico de DMS desde la VPC de PaaS se enrutara hacia su propia VPC sin pasar por Internet pública
- Crearon la instancia RDS de destino en su propia cuenta de AWS y, dado que se acercaba el fin del soporte para PostgreSQL 11, configuraron la nueva base de datos con PostgreSQL 15
- Usaron
pg_dumppara volcar el esquema de la base de datos de origen y crear un archivo SQL para recrearlo; al principio aplicaron solo las declaraciones de tablas en la base de datos de destino - No aplicaron claves foráneas durante el full load
- Esto se debe a que DMS full load no copia los datos respetando el orden requerido por las restricciones de claves foráneas
- Tampoco crearon claves primarias ni índices antes del full load
- Cada inserción habría requerido actualizar índices, lo que podía aumentar mucho el tiempo total al cargar miles de millones de filas
- Era más rápido copiar primero todos los datos y agregar los índices después
- La tarea de full load copió los datos existentes en el momento en que se presionó el botón de inicio
- Los datos nuevos o actualizaciones posteriores no se incluyeron en el full load
- Completar el full load tomó unas 6 horas
- Después del full load, aplicaron el resto del archivo de esquema para agregar índices y restricciones de claves; esta tarea tomó unas 3 horas
Mantener la replicación durante 10 días y luego conmutar el tráfico
- Tras finalizar el full load, la base de datos de destino coincidía con los datos de origen al momento de inicio del full load, pero en la de origen siguieron ocurriendo inserciones, modificaciones y eliminaciones
- Iniciaron la replicación continua de DMS, es decir, change data capture, para enviar a la base de datos de destino las transacciones del transaction log de la base de datos de origen posteriores al inicio del full load
- Al proceso de replicación le tomó algunas horas ponerse al día; luego monitorearon el retraso de replicación de DMS para confirmar el estado de sincronización
- La replicación de DMS se ejecutó en segundo plano durante unos 10 días, manteniendo sincronizadas ambas bases de datos hasta el momento de migración del tráfico anunciado previamente a los usuarios
- El procedimiento de conmutación de tráfico se escribió de antemano como scripts de Python
- Detener el tráfico de la aplicación hacia la base de datos de origen
- Confirmar que la replicación se hubiera puesto completamente al día
- Permitir que la aplicación se conectara a la base de datos de destino
- Había que evitar un estado en el que algunas aplicaciones usaran la base de datos de origen y otras la de destino
- Como los cambios hechos en la base de datos de destino no se reflejarían en la de origen, los usuarios podrían ver datos inconsistentes
- Los scripts se diseñaron para ser más explícitos, repetibles y rápidos que las tareas manuales, y se usaron al menos 40 veces en pruebas y ensayos previos
- El objetivo de downtime era menos de 5 minutos, y programaron la migración para un sábado por la noche relativamente tranquilo, evitando la madrugada
El cambio de DNS que logró 11 segundos de downtime
- La detención del tráfico hacia la base de datos de origen se hizo llamando a
pg_terminate_backendsobre las conexiones de la aplicación, lo que tomó menos de 1 segundo - También cambiaron la contraseña del usuario de PostgreSQL para impedir que la aplicación se reconectara a la base de datos de origen y provocar errores de autenticación en los reintentos
- DMS creó una tabla de estado de replicación en la base de datos de destino y la actualizaba cada minuto; el script de migración usaba esa tabla para comprobar el retraso entre origen y destino
- Como medida adicional de seguridad, después de que la aplicación dejara de acceder a la base de datos de origen, el script escribía un único registro en la base de datos de origen y esperaba hasta que llegara a la de destino
- La información de conexión a la base de datos de la aplicación se proporcionaba mediante la variable de entorno
SQLALCHEMY_DATABASE_URI- El formato existente era
postgresql://...@random-identifier.eu-west-1.rds.amazonaws.com:5432, con nombre de usuario, contraseña y ubicación de RDS - Cambiar la ubicación de la base de datos o las credenciales requería redesplegar la aplicación, y el redespliegue tardaba unos 5 minutos
- El formato existente era
- Para evitar downtime adicional por redespliegue, prepararon dos cosas antes de la migración
- Crear usuarios con el mismo nombre de usuario y contraseña en las bases de datos de origen y de destino
- Crear en AWS Route53 un registro DNS
database.notifications.service.gov.uky configurar el TTL en 1 segundo
- Al principio, el registro DNS tenía ponderación de 100% para el origen y 0% para el destino
- Modificaron de antemano el URI de la aplicación para que usara el nombre de usuario y la contraseña comunes, junto con el nuevo nombre de dominio
- En la conmutación real, el script cambió la ponderación de DNS de AWS a 100% para el destino y esperó a que expirara el TTL de 1 segundo
- En el momento de la conmutación, el sábado 4 de noviembre de 2023 por la noche, el retraso entre la base de datos de destino y la de origen era de apenas unos segundos
- Como resultado de ejecutar el script de migración, la aplicación dejó de acceder a la base de datos de origen y empezó a usar la nueva base de datos de destino, con un downtime de unos 11 segundos
Evaluación de la elección de DMS y próximos pasos
- Eligieron DMS porque estaba bien soportado en GOV.UK PaaS y también podían recibir soporte de AWS
- Si en el futuro vuelven a hacer una migración entre bases de datos PostgreSQL, planean evaluar más a fondo herramientas alternativas como pglogical
- Es posible que DMS haya agregado más complejidad y un proceso de replicación menos familiar que otras herramientas
- Después de la migración de la base de datos, el siguiente paso es migrar la aplicación
- La aplicación de GOV.UK Notify se trasladará a AWS Elastic Container Service, y el avance del proceso se compartirá más adelante
1 comentarios
Opiniones de Hacker News
Nosotros hicimos una migración similar con AWS RDS Blue-Green Deployments y, aunque la base de datos era un poco más grande, el downtime fue de unos 20 segundos y el esfuerzo fue mucho menor. Me sorprende que todavía no se haya mencionado en este hilo.
Básicamente, levantas un nuevo despliegue Blue/Green con los cambios que quieres, y AWS sincroniza el despliegue green mediante replicación lógica mientras la configuración blue existente sigue atendiendo el tráfico.
En green puedes hacer modificaciones, pruebas e incluso pruebas de carga siempre que no escribas; las escrituras siguen entrando al blue en vivo y luego se replican a green.
Cuando estás listo, ejecutas el comando switch y AWS se encarga de verificar la sincronización, cortar escrituras y conexiones, esperar a que la replicación se ponga al día, cambiar los nombres de las bases de datos y reanudar conexiones y escrituras.
En nuestro caso, el downtime fue de menos de 20 segundos, y toda la configuración, incluyendo el primary y varias réplicas de lectura, hizo el cambio sin problemas. AWS también cambia la URL de la base de datos, así que no tuvimos que modificar la configuración; green se convierte en blue y el blue anterior pasa a ser old blue, que puedes eliminar después.
Lo recomiendo mucho, aunque tiene limitaciones, por ejemplo puede que no sirva para movimientos entre cuentas: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/blue-...
Hay que leer y releer la documentación, especialmente las limitaciones. Conviene hacer pruebas en el entorno de desarrollo con carga, y repetirlas también en staging.
O simplemente hacerlo YOLO en producción y probablemente también salga bien.
Eso sí, aprendimos por las malas que RDS Blue/Green no sirve para cambios arbitrarios. En nuestro caso descubrimos que solo se podía usar para subir la versión del motor, no para bajarla.
En MySQL 8.0 un stored procedure fallaba muy ocasionalmente, así que evaluamos la opción de volver a 5.7, pero no era posible.
El trade-off fue que algunas solicitudes se volvieron lentas durante unos segundos.
La combinación de DNS Groups y reintentos es un mecanismo bastante útil para este tipo de trabajos.
Herramienta usada: https://github.com/shayonj/pg_easy_replicate
Hay varias formas de “pausar” las consultas entrantes a Postgres; por ejemplo, usando pgbouncer puedes demorarlas sin hacerlas fallar hasta que la replicación se ponga al día, y luego continuar en la base de datos nueva.
Si algo sale mal y la replicación no logra ponerse al día, quitas la pausa y haces que esas consultas se ejecuten en la base de datos existente.
Así conviertes 11 segundos de downtime en 0 a 11 segundos adicionales de tiempo de carga de página. Más importante aún, entre miles de usuarios de la base de datos que nunca han visto fallar una consulta, también reduces mucho el daño colateral en casos donde exista una ruta de manejo de errores con bugs o donde una sola consulta fallida pueda arruinar todo un job batch.
Es interesante compararlo con https://knock.app/blog/zero-downtime-postgres-upgrades. La discusión relacionada estuvo en https://news.ycombinator.com/item?id=38616181
En ese momento, buena parte de la discusión terminó en “¿no es demasiado complejo solo para evitar unos minutos de downtime?”. Este caso parece una prueba, y da la impresión de que basta con usar AWS Data Migration Service, cambiar el registro DNS, pasarlo a producción y aceptar 11 segundos de downtime
Algunos tipos de datos especiales quizá ni siquiera los pueda procesar. Después de la migración también hay que actualizar las secuencias; si no, pueden aparecer errores por claves primarias duplicadas
Si no hay una clave primaria adecuada, también pueden surgir problemas porque no siempre copia la fila completa de una sola vez
Si las bases de datos están dentro de la misma cuenta de AWS y puedes aceptar 4 o 5 minutos de downtime, probablemente sea más fácil usar replicación a nivel de hardware mediante una base de datos global o snapshots
Hace poco migramos una base de datos PostgreSQL de 3 TB que teníamos autoalojada de la versión 12 a la 16, y pasamos de Ubuntu 18 a Ubuntu 22. Al mismo tiempo también tuvimos que actualizar varias extensiones, y en particular Timescale no tenía una versión compatible que satisficiera todas las combinaciones
Lo hicimos actualizando una réplica de solo lectura: empezamos con PG12, Ubuntu 18 y TS2.9, y primero creamos una réplica de solo lectura en Ubuntu 22 manteniendo PG12 y TS2.9
Luego entramos en modo mantenimiento, detuvimos todos los servicios, separamos la réplica de solo lectura y, en Ubuntu 22, subimos PG12 a PG15 manteniendo TS2.9
Después pasamos de TS2.9 a TS2.13 sobre PG15, y finalmente subimos PG15 a PG16 en Ubuntu 22 manteniendo TS2.13
Por último, reconectamos los servicios al nuevo servidor de base de datos, reanudamos todos los servicios y salimos del modo mantenimiento
Todas las etapas de actualización de la base de datos se probaron y automatizaron suficientemente con Ansible, pero apareció un problema que no había surgido en las pruebas y el downtime se extendió a unos 30 minutos. Para nuestro uso fue un nivel perfectamente aceptable
Si hubiéramos usado replicación lógica, podríamos haber reducido los problemas inesperados de último minuto, y tenemos previsto evaluar ese enfoque en el próximo ciclo de actualización
También evaluamos la replicación lógica para reducir el downtime de la actualización, pero como el esquema de la base de datos y los comandos DDL no se replican, parecía no ser recomendable cuando Timescale está de por medio
Los cambios de esquema base que Timescale debe hacer internamente probablemente sean en gran medida función del tamaño de los chunks de las hypertables y del patrón de escrituras entrantes, así que quizá se puedan planificar o sincronizar, pero consideramos que la complejidad y el riesgo potencial eran demasiado altos frente a reservar una ventana corta de mantenimiento mientras termina pg_upgrade
El enemigo de estas migraciones con bajo downtime o sin interrupción son las consultas de larga duración
Por ejemplo, si hay una sola consulta
updateque tarda 30 minutos, hay que matar esa consulta y hacer rollback, o aceptar 30 minutos de pérdida de disponibilidadHasta donde sé, no hay forma de migrar consultas que ya están en curso
statement_timeoutes tu amigaSi tienes transacciones extremadamente largas, lo más probable es que puedas hacer la conmutación evitando el momento en que se ejecutan. Ojalá sean el resultado de algo como una tarea programada, no ocurrencias aleatorias
Combinando límites de tiempo de transacción con una configuración de failover, por ejemplo haciendo fallar el primary existente, y algo como pgbouncer, puedes controlar con bastante precisión el tiempo de lentitud en lugar del downtime
Sinceramente, lo que más me preocuparía es si todo el stack y los servidores DNS de caché externos de los que dependes respetan correctamente el TTL de DNS
Pero normalmente evitar unos 10 segundos de downtime en una app no es crítico, así que lo correcto es elegir la solución que te resulte más simple
updatede 30 minutos no esté escrita de forma extremadamente ineficiente o no sea una migración masiva de datos puntualClaro que lo primero existe mucho en la práctica. Me he divertido bastante convirtiendo trabajos de minutos u horas en trabajos de milisegundos
Me pregunto qué datos y qué personas manejan escrituras de base de datos así, como para depender de que el motor de DB trabaje durante tanto tiempo en vez de dividirlas en partes más pequeñas mediante una cola en una capa superior
Es raro lo de crear en AWS Route53 un registro DNS para
database.notifications.service.gov.ukcon un TTL de 1 segundo, hacer que el script de migración cambiara la ponderación DNS de AWS para enviar el 100% a la ubicación de la base de datos de destino y luego esperar 1 segundo a que venciera el TTL.Dicen que entonces, la próxima vez que la app intentara consultar la base de datos, consultaría la base de datos de destino. ¿Eso significa que su ORM o el comportamiento predeterminado de Python hace una consulta DNS en cada query y se queda bloqueado?
¿O sea que no cachean la dirección resuelta durante cierto tiempo, ni usan pooling o reutilización de conexiones?
getaddrinfoogethostnamedel sistema operativo tengan ese comportamiento. Python casi no reimplementa llamadas a nivel de sistema, así que depende de la configuración del sistema.Si se respetó el TTL de 1 segundo, se habría cacheado durante 1 segundo, pero tampoco es raro que las bibliotecas de resolución DNS y, en particular, los servidores DNS con caché no respeten del todo el TTL. Honestamente, eso podría explicar parte del downtime observado.
Bien. Nosotros acabamos de migrar en RDS 3 clústeres de Postgres con unos 2 TB de datos y 8 bases de datos, de Postgres 14 a 16. Estuvimos caídos de 00:00 a 04:00.
Primero activamos el “modo mantenimiento”, un sitio sustituto muy liviano que corre en Cloudflare Workers, y con Terraform escalamos a 0 todas las apps que escriben en la BD.
En la UI web de AWS presionamos el botón de upgrade para hacer 14→15 con
pg_upgrade, esperamos a que terminara y luego lo volvimos a presionar para pasar de 15→16.Esperamos hasta que la base de datos empezara a aceptar conexiones; parecía aceptar conexiones incluso antes de aparecer como ready, y daba la impresión de que AWS hacía algo más además de
pg_upgrade.Después iniciamos
VACUUM ANALYZE; REINDEX DATABASE CONCURRENTLY. La intención era evitar problemas de rendimiento entre versiones y aprovechar las mejoras de rendimiento de la nueva versión.Empezamos a levantar las apps de nuevo, esperamos a que todas tuvieran algunos contenedores corriendo, luego empezamos a recibir tráfico, apagamos el sitio de mantenimiento y nos fuimos a dormir.
REINDEX CONCURRENTLYsiguió corriendo 18 horas más en la BD más grande, pero no bloqueó nada.La próxima vez pensamos usar AWS Blue/Green Deployments para evitar downtime. Esta vez no pudimos porque no estábamos en la versión menor mínima de 14 que soporta Blue/Green, que es 14.9.
Si lo hiciera por mi cuenta, no pagaría el costo de AWS y armaría mi propio Blue/Green con replicación lógica y un balanceador de carga.
pg_upgrade --hardlinkspara un upgrade in-place.En instancias Postgres propias on-premise he logrado procesar incluso una BD de 2 TB en menos de 1 minuto.
GOV.UK Notify forma parte de un conjunto de servicios que GDS ofrece a organismos públicos del Reino Unido. También están GOV.UK Pay y GOV.UK PaaS, y originalmente se conocía como Government As A Platform.
DMS es una pésima herramienta de migración. Después de casi un mes peleando con varios problemas de migración, nos rendimos.
No pudo migrar tipos text y json, y el soporte de AWS tampoco pudo proponer una solución.
Probamos AWS Blue/Green en una etapa inicial de pruebas y, gracias a eso, los upgrades con casi cero downtime se volvieron viables.
Está completamente rota.
Es interesante, pero no entiendo por qué el gobierno usa AWS en primer lugar. No es una startup que esté hackeando para encontrar encaje producto-mercado, ni una situación en la que tenga que responder a un pico de tráfico impulsado por marketing que no pudo prever.
Sabe que este tipo de servicio será necesario a largo plazo, y los patrones de uso también se pueden predecir bastante bien.
Podría crear una nube para el sector público o adoptar un enfoque on-premise razonable. Requeriría fondos, coordinación y liderazgo técnico, pero a largo plazo les ahorraría enormes costos a los contribuyentes.
La TI del sector público en general es un desastre, pero sé que también hay buenos ingenieros trabajando ahí.
Cada hora que uno pasa corriendo con discos duros y drivers, o su versión en la nube —storage y cosas como Ansible—, es una hora que no dedica a construir lo que los clientes necesitan.
¿Por qué tendría que ser distinto para el gobierno?
No esperamos que el gobierno fabrique sus propios autos; esperamos que los compre a Volkswagen o Renault. Incluso si el gobierno tiene una necesidad clara de transporte. Entonces no entiendo por qué se insiste en que construya su propia infraestructura de TI.
Y también hay cosas que aparecen de la nada, como una pandemia. El hecho de haber podido escalar para cubrir la demanda durante la pandemia fue una de las demostraciones clave de por qué el sector público debe usar nubes públicas comerciales.
Recomiendo ver la presentación de septiembre sobre las varias iteraciones de Gov.UK y las migraciones entre nubes: https://youtube.com/watch?v=mpY1lxkikqM&pp=ygUOUmljaGFyZCB0b....
Al menos en el gobierno del Reino Unido, por los requisitos de procurement, cada ciertos años tienen que volver a sacar al mercado una cotización basada en uso.
Por ejemplo, si Oracle Cloud cuesta una décima parte, es muy probable que gane el contrato; entonces durante el período del contrato habría que migrar a Oracle, y si después aparece otra nube más barata, quizá haya que volver a mudarse.
Fue lo peor que he visto en mi vida. Y eso que he trabajado bastante con legados en VB.NET, Web Forms, SharePoint viejo, Basic e incluso apps enteras que eran un enorme montón de stored procedures.
AWS, Azure y Google Cloud al menos fueron diseñados pensando en el usuario final, es decir, el desarrollador. En cambio, la nube gubernamental la diseñó y construyó el oferente más barato, cuyo primer objetivo era recortar costos en todos los lugares posibles.
Por el contrario, también he conocido a responsables de infraestructura y operaciones realmente excelentes que operaban un datacenter interno de una institución médica del gobierno alemán. El problema ahí no era la tecnología ni la gente, sino 100% la gerencia y los procesos, que se convertían en un cuello de botella en cada interacción entre el equipo de infraestructura y el equipo de ingeniería.