2 puntos por GN⁺ 2023-12-14 | 1 comentarios | Compartir por WhatsApp
  • Knock definió un procedimiento para migrar Postgres, el almacén central de su motor de flujos de trabajo de notificaciones, de AWS RDS Aurora 11.9 a 15.3 sin afectar a los clientes
  • La empresa tenía que actuar antes del 29 de febrero de 2024, fecha de retiro de Postgres 11.9 en Amazon RDS, o enfrentarse a una actualización forzada con posible tiempo de inactividad
  • Descataron la actualización in-place y pg_dump/pg_restore porque requerían una interrupción larga, y eligieron configurar replicación lógica basada en PUBLICATION/SUBSCRIPTION hacia una nueva base de datos
  • Dividieron la estrategia de replicación según el tamaño de las tablas y el patrón de escritura: las tablas pequeñas se replicaron directamente y las grandes append-only combinaron copy_data = false con backfill desde snapshots
  • El cambio final se completó en segundos manteniendo conexiones a ambas bases de datos, cambiando un flag, dando 500 ms a las consultas en ejecución y pausando durante 1 segundo las solicitudes a la nueva base para reducir el riesgo de lecturas obsoletas

Objetivos y restricciones de la actualización

  • Knock depende de Postgres para su motor de flujos de trabajo de notificaciones y lo usa para configuraciones de workflows, plantillas de mensajes, recolección de millones de logs y encolado de trabajos en segundo plano
  • Por la naturaleza de una base de datos relacional, Postgres requiere al menos un reinicio al actualizarse, y una actualización de versión mayor puede necesitar una detención completa de varios minutos o más debido a cambios en cómo se almacenan los datos e índices en disco
  • Postgres 11.9, que usaban desde el inicio de la empresa, iba a quedar retirado en Amazon RDS, y si no hacían nada podían terminar con una actualización forzada y downtime forzado
  • Las condiciones de la actualización se definieron para reducir el riesgo operativo
    • Saltar hasta la versión más reciente posible para Aurora: Postgres 15.3
    • No permitir más de 60 segundos de downtime y, de forma ideal, 0 downtime del sistema
    • Completarlo antes de la fecha límite de Amazon en 2024
    • Minimizar el impacto en clientes, por ejemplo, 0 respuestas de error en la API
    • Convertir el procedimiento en un runbook reutilizable para la siguiente actualización
  • Pasar de 11.9 a 15.3 implicaba una actualización de 4 versiones mayores, por lo que descartaron repetir 4 veces una actualización in-place

Preparación previa: reducir riesgos y mejorar la observabilidad

  • El enfoque para actualizar Postgres fue primero hacer una lista de riesgos y luego reducir antes que nada los de mayor impacto y más fáciles de eliminar con anticipación
    • downtime prolongado
    • pérdida de datos
    • cambios en el rendimiento de la base de datos bajo la carga de la aplicación
    • cambios en la frecuencia o el comportamiento de VACUUM
    • necesidad de migrar replication slots
  • Revisaron las notas de lanzamiento de Postgres para identificar cambios entre versiones, como modificaciones en el comportamiento de VACUUM o la necesidad de reindexar en ciertas actualizaciones
  • Durante la actualización había que monitorear continuamente métricas del sistema y de la base de datos
    • Max TXN ID para prevenir transaction wraparound
    • uso de CPU de la base de datos
    • sesiones en espera en la instancia writer
    • latencia de consultas
    • latencia de respuesta de la API de la aplicación
  • Knock también monitoreó métricas propias de la aplicación, como el tiempo que tarda una petición de API en convertirse en una notificación
  • Sin métricas visibles a tiempo, el proceso de actualización se vuelve prácticamente a ciegas

Enfoques descartados: actualización in-place y dump/restore

  • La actualización in-place de AWS RDS se ejecuta desde la consola de AWS: AWS detiene la base de datos, corre scripts de actualización y luego la vuelve a poner en línea
  • Ese proceso puede tardar desde minutos hasta varias horas, según la cantidad de datos y el tamaño de los cambios entre versiones
  • Incluso después de volver a estar en línea, la base de datos puede requerir tareas de mantenimiento como VACUUM o REINDEX, por lo que no necesariamente queda totalmente utilizable de inmediato
  • El enfoque con pg_dump y pg_restore obliga a desconectar todas las aplicaciones de la base original para obtener un backup confiable, y en bases grandes tanto el dump como el restore pueden tardar mucho
  • Como ambos enfoques probablemente excedían por mucho el límite de downtime de Knock, fueron descartados

Enfoque elegido: actualización basada en replicación lógica

  • La opción final fue usar replicación lógica de Postgres con PUBLICATION y SUBSCRIPTION
  • El flujo general fue el siguiente
    • levantar una nueva base de datos con la versión objetivo de Postgres
    • mover configuración, extensiones, estructura de tablas, usuarios, etc.
    • crear una publication en la base original y una subscription en la nueva
    • agregar tablas a la publication
    • cuando la replicación estuviera completa, ejecutar pruebas para validar los riesgos restantes
    • cuando la configuración de la nueva base estuviera suficientemente validada, cambiar la aplicación a la nueva base
    • eliminar la base original
  • Esto permitió avanzar en etapas graduales en lugar de ejecutar una actualización grande de una sola vez, y probar la nueva base con datos y carga reales
  • Una vez lista la nueva base, el cambio en sí tomaba solo segundos, lo que daba mucho más control sobre el momento y la forma del cutover

Puntos clave de la configuración de replicación

  • La replicación lógica de Postgres usa parámetros necesarios para configurar replication slots, y en aplicaciones simples el cambio principal puede ser poner wal_level en logical
  • Si ya se usan replication slots para réplicas de lectura, failover de base de datos o sincronización con un data warehouse, hay que ajustar parámetros relacionados como max_replication_slots según la documentación
  • La estructura de tablas en la nueva base debe ser idéntica a la original, pero debe estar vacía
  • Se puede generar un snapshot del esquema con pg_dumpall usando --schema-only y --no-role-passwords, y luego comparar el SQL con el de la nueva base para corregir diferencias
  • Al crear la publication en la base original y la subscription en la nueva, configuraron opciones importantes
    • enabled = false: para no iniciar la sincronización desde el principio
    • create_slot = true: para que Postgres administre el replication slot
    • copy_data = true: para copiar por defecto el contenido de las tablas
    • disable_on_error = true: para detener la subscription ante errores inesperados y poder corregir el problema antes de reanudarla
  • Agregar todas las tablas de una vez con FOR ALL TABLES puede causar problemas de rendimiento en bases grandes, así que Knock usó ALTER PUBLICATION ... ADD TABLE para agregarlas una por una

Clasificación de tablas y estrategia de replicación

  • Knock dividió las tablas según tamaño en disco y cantidad de tuplas
    • tablas pequeñas que podían sincronizarse en unos minutos
    • tablas grandes pero casi append-only
    • tablas grandes donde la mayoría de las filas se actualizan con frecuencia
  • Para Knock, una tabla “pequeña” era una de menos de 50 GB y menos de 10 millones de tuplas
  • En Postgres, una tupla es la unidad en la que se guarda un insert o update, y aunque haya pocas filas, si hay muchas tuplas sin limpiar la replicación puede tardar bastante
  • Ejecutar VACUUM antes de replicar puede ayudar a reducir la cantidad de tuplas que la base origen necesita copiar hacia la base destino
  • El tiempo de sincronización de una tabla está directamente relacionado con su tamaño en disco y su número de tuplas, y una sincronización demasiado larga puede obstaculizar VACUUM en la base primaria, degradar el rendimiento y aumentar el riesgo de transaction wraparound

Replicación de tablas pequeñas

  • Las tablas pequeñas se manejaron agregándolas a la publication en la base original y haciendo refresh de la subscription en la nueva
  • Postgres se encargó de copiar la tabla, sincronizarla y aplicar los cambios posteriores
  • Las tablas muy pequeñas podían sincronizarse en menos de 1 segundo

Replicación de tablas grandes append-only

  • Las tablas grandes sin updates, o donde solo se actualizan filas recientes, pueden replicarse con una publication/subscription separada usando copy_data = false
  • Knock usó el sufijo _nocopy en el nombre para distinguirlas de la replicación normal
  • Primero replicaron solo los cambios nuevos, y luego hicieron backfill de los datos históricos por separado desde backups o snapshots
  • En AWS RDS Aurora, el procedimiento fue este
    • crear un snapshot de la base de producción
    • restaurar el snapshot en una nueva instancia de base de datos
    • agregar un sufijo como _snapshot a los nombres de las tablas del snapshot que se iban a replicar
    • crear también en la base destino tablas para snapshot con el mismo esquema
    • configurar publication/subscription desde la base del snapshot hacia la base destino
    • monitorear el progreso de la replicación
    • cuando la replicación alcanzara el estado esperado, fusionar hacia la tabla destino real con INSERT ... ON CONFLICT DO NOTHING
  • En tablas muy grandes este proceso puede tardar días, pero al ejecutarse en segundo plano no debería afectar producción
  • Después de la fusión, compararon el número de filas para validar consistencia y eliminaron las tablas de snapshot en la base destino, la subscription del snapshot y la instancia de base del snapshot

Tablas grandes con updates frecuentes

  • Las tablas grandes donde la mayoría de las filas se actualizan con frecuencia son las más difíciles, y una replicación prolongada puede bloquear la ejecución de AUTOVACUUM
  • Algunas medidas posibles son estas
    • revisar si tareas de housekeeping pueden reducir el tamaño de la tabla
    • verificar si se ejecutó VACUUM recientemente
    • evaluar si la tabla puede particionarse en fragmentos más pequeños
    • comprobar si después de cierto tiempo las filas dejan de actualizarse, para poder tratarlas como append-only
  • Si la base origen está en una versión anterior a PG 15, las opciones son limitadas y hay que replicarla como una tabla pequeña y monitorear si hay degradación del servicio
  • Si hace falta, se puede hacer rollback quitando la tabla de la publication y refrescando la subscription
  • En tablas demasiado grandes, conviene iniciar la replicación en horas de poco tráfico para reducir el impacto de la carga y de la actividad de escritura

Replicación fragmentada de tablas grandes en PG 15 o superior

  • Si la base origen ya está en PG 15 o superior, se puede dividir la replicación en varias publications para mover tablas grandes en trozos más pequeños
  • Este enfoque funciona de manera parecida al particionado o al sharding, a cambio de usar más replication slots
  • Como Knock estaba migrando de 11.9 a 15.3, no podía usar este método y no lo probó directamente
  • El ejemplo consiste en dividir filas entre varias publications usando un hash de la primary key y una cláusula WHERE
  • El tamaño de fragmento que Knock consideraba manejable era de alrededor de 100 GB de datos sin contar índices

Verificación del estado de replicación e interrupción

  • Cuando se agrega una tabla a una subscription, el estado puede verse en pg_subscription_rel.srsubstate de la base destino
    • i: inicialización
    • d: copia del contenido de la tabla
    • f: copia completa, esperando sincronización final
    • s: finalización de la sincronización inicial
    • r: replicación normal en ejecución
  • La fase d necesita mantener transaction IDs antiguos, por lo que puede bloquear efectivamente VACUUM y derivar en problemas de rendimiento o transaction ID wraparound
  • Si el sistema se acerca al wraparound, conviene abortar la migración y dividirla en fragmentos más pequeños
  • Para detener la replicación de una tabla específica, hay que quitarla de la publication en la base original y refrescar la subscription en la nueva
  • Deshabilitar solo la subscription puede no resolver el problema de rendimiento, porque la base origen puede seguir reteniendo transaction IDs viejos
  • En emergencias, se puede borrar por completo la publication y la subscription y empezar de nuevo; Postgres limpiará los replication slots relacionados

Restricciones al migrar replication slots

  • Los replication slots de Postgres guardan logs de actividad de la base que pueden consumir otras bases o aplicaciones
  • Su progreso se rastrea con el Log Sequence Number, o LSN, y el LSN es único para la base Postgres primaria
  • No es posible copiar directamente el LSN de un replication slot de la base original a la nueva
  • Si una aplicación consume replication slots, como una herramienta de data warehouse, hay que definir la estrategia de migración según la documentación de cada herramienta
  • Si una aplicación propia usa replication slots, contar con un mecanismo de idempotencia para deduplicar transacciones entre la base vieja y la nueva puede ayudar

Validación final

  • Una vez agregadas todas las tablas a la publication y cuando la subscription se puso al día, había que validar que las tablas coincidieran
  • Debido al retraso inherente de la replicación lógica, no siempre es posible que la base original y la nueva coincidan perfectamente en el mismo instante, pero comparar conteos de filas permite verificar si están suficientemente cerca
  • Knock escribió un script para contar filas por tabla en la base original y la nueva
  • En tablas con columna inserted_at, compararon solo filas de más de 10 segundos de antigüedad, asumiendo que los últimos 10 segundos todavía podrían replicarse en breve
  • En algunas tablas también compararon muestras aleatorias de filas para validar con más detalle que el contenido coincidiera

Cómo hicieron el cambio de la aplicación

  • Para el cutover final, la aplicación podía modificarse para conectarse a ambas bases de datos
  • En bases con poco tráfico, la migración puede hacerse simplemente cambiando la configuración a la nueva base y reiniciando la aplicación
  • En aplicaciones con mucha actividad concurrente, había que evitar writes en conflicto entre la base original y la nueva
  • El script de cutover de Knock siguió este orden
    • indicar a todas las instancias de la aplicación que envíen las nuevas consultas a la nueva base
    • dar 500 ms a las consultas en ejecución para que terminen, y después cancelarlas por la fuerza
    • durante el primer segundo tras cambiar el flag, pausar artificialmente las solicitudes a la nueva base para dar tiempo a que las transacciones pendientes se repliquen
    • luego normalizar la actividad de base de datos, ya apuntando a la nueva base
    • reiniciar algunas cargas de trabajo especiales de base de datos para que se reconecten a la nueva base después de detenerse
  • Knock confirmó que 500 ms era mucho más que la duración de la mayoría de sus consultas y que no hubo errores por desconexión forzada

Manejo de sequences

  • La replicación lógica de Postgres no sincroniza las sequence
  • Aunque se usen valores de sequence en la base original, los de la nueva base no avanzan automáticamente
  • Justo antes del cambio por feature flag, Knock ejecutó un script que se conectaba a ambas bases
    • en la base original, para cada sequence, obtenía el siguiente valor con SELECT nextval('sequence_name')
    • en la nueva base, adelantaba la sequence con SELECT setval('sequence_name', value::int4 + 100000)
  • Este método crea huecos en las sequence, pero en Knock eran bigint, así que saltarse 100 mil valores representaba prácticamente 0% del espacio disponible
  • El tamaño del salto debe ajustarse según la cantidad de valores de sequence que podrían usarse durante el cutover real

Qué revisar antes del cutover

  • La lista de verificación final cubría ampliamente la preparación operativa
    • que el conteo de filas de todas las tablas coincida como se espera
    • que todas las subscriptions estén habilitadas y corriendo sin errores
    • que el esquema coincida y que puedan congelarse los releases de migración
    • que la nueva base tenga el sizing adecuado para la carga
    • si hacen falta read replicas para igualar la topología del clúster entre la base original y la nueva
    • que se hayan ejecutado REINDEX y el mantenimiento normal de VACUUM en la nueva base
    • que se hayan revisado otra vez las notas de lanzamiento de Postgres por posibles regresiones en la aplicación
    • que se hayan hecho pruebas automáticas y manuales en una base de staging con la nueva versión
    • que se haya hecho load testing con pg_bench sobre las consultas más pesadas
    • si queda algún riesgo que todavía pueda reducirse
    • que se haya practicado el procedimiento de cutover varias veces en staging o test
    • que se haya creado un backup de la base justo antes del cutover

Resultado real del cambio

  • Knock replicó las tablas una por una durante varias semanas, principalmente fuera del horario laboral y en las franjas de menor tráfico
  • Practicaron el cutover varias veces en staging y fueron afinando el procedimiento para que funcionara con poca intervención operativa
  • Una vez listas la réplica de PG 15 y la lógica de cambio en la aplicación, hicieron la validación final y activaron el flag
  • El cutover real terminó en cuestión de segundos, y salvo una breve subida intencional de latencia mientras esperaban la replicación, la aplicación siguió funcionando
  • Después revirtieron los cambios temporales de la aplicación, movieron de forma permanente todas las conexiones a la nueva base y eliminaron la subscription en la nueva base y la base original
  • Knock completó una migración sin downtime de Postgres de 11.9 a 15.3

Conclusión

  • Saltar 4 versiones mayores de Postgres de una sola vez es difícil, pero posible
  • El enfoque de replicación lógica puede ser más seguro que un downtime programado porque permite practicar, probar y rehacer varias veces antes del cutover real
  • Si algo falla durante el proceso, se puede eliminar la publication de la base original y empezar de nuevo sin degradar el servicio
  • Aunque una disponibilidad perfecta del 100% no siempre es técnicamente posible, una migración sin downtime ayuda a mantener el sistema en operación sin interrupciones importantes

1 comentarios

 
GN⁺ 2023-12-14
Opiniones de Hacker News
  • El enfoque de copiar todo el contenido de las tablas una por una genera demasiada carga de E/S, y no funciona con tablas muy grandes.
    Un mejor método es crear un slot de replicación, tomar un snapshot, restaurarlo en una nueva instancia, avanzar el LSN y luego replicar desde ahí. Así se obtiene una réplica lógica con todos los datos, y basta con actualizar esa réplica.
    El artículo de Instacart explica el método: https://archive.ph/K5ZuJ
    Si no recuerdo mal, el artículo tenía algunos errores pequeños, pero el procedimiento general funcionaba y actualicé varias veces instancias de nivel TB de esta forma.

    • Este método es una buena receta, pero requiere un ajuste pequeño e importante en el orden en que se inserta pg_upgrade.
      Si se inicia primero la replicación lógica y luego se ejecuta pg_upgrade, hay riesgo de corrupción. La discusión relacionada está en pgsql-hackers: https://www.postgresql.org/message-id/flat/20230217075433.u5...
      Para resolverlo, primero hay que crear el slot lógico, avanzar el nuevo clúster hasta la posición LSN del slot sin iniciar todavía la replicación lógica, luego ejecutar pg_upgrade y, una vez que el clúster levante con la nueva versión de PostgreSQL, iniciar la replicación lógica.
      Postgres.ai usó recientemente exactamente este enfoque para actualizar sin downtime varios clústeres multi-TiB de GitLab bajo alta carga, y también utilizó PAUSE/RESUME de PgBouncer. Hay una charla de Alexander Sosna programada para más adelante esta semana: https://www.postgresql.eu/events/pgconfeu2023/schedule/sessi...
    • Como OP, también evalué este método, pero no estaba seguro de avanzar manualmente el LSN como se proponía, ni confiaba en poder detectar inconsistencias si se nos escapaba algo en la replicación.
      El avance tabla por tabla era mucho más engorroso, pero parecía más confiable.
    • El artículo fue actualizado: https://tech.instacart.com/zero-downtime-postgresql-cutovers...
    • Ese artículo cubre la base del enfoque de actualización de Instacart, pero ya es bastante antiguo; el siguiente muestra mejor el procedimiento actual.
      Con este método hemos actualizado con éxito muchas bases de datos muy grandes y activas.
      https://www.instacart.com/company/how-its-made/zero-downtime...
  • El enfoque es interesante y está bien documentado, pero la frase “los clientes modernos esperan 100% de disponibilidad” me hace ruido.
    No es mi preferencia como cliente, ni mi experiencia como proveedor. En muchas cargas de trabajo, la consistencia es mucho más importante que la disponibilidad.
    Cuando un proveedor anuncia una ventana de downtime, muchas veces me tranquiliza, porque lo veo como una señal de que está tratando mis datos con cuidado.

    • Como OP, es un buen feedback.
      Quería generar confianza tanto en la confiabilidad del producto como en la consistencia de la carga de trabajo. Por supuesto, es mucho mejor gestionar las expectativas de los clientes y tomar downtime de forma deliberada para lograr mejor uptime a largo plazo, que fingir consistencia mientras todo es inestable.
      Hacer que las ventanas periódicas de mantenimiento sean previsibles de antemano también podría llevar a una arquitectura más robusta en general. Si los clientes crean mecanismos de seguridad para tolerar downtime, aumenta la resiliencia, y el equipo también gana tiempo para invertir en un mejor producto cuando puede confiar en que los clientes lo entienden así.
      Tal vez después de la próxima actualización de versión mayor escriba un artículo titulado “Establecer expectativas sobre el downtime como camino hacia un uptime muy alto”.
    • Depende de quién sea el cliente.
      Como cliente de AWS, espero 100% de disponibilidad, porque mis propios clientes están en todo el mundo y no hay un horario en el que pueda tener downtime.
  • AWS ahora soporta despliegues blue/green: https://aws.amazon.com/about-aws/whats-new/2023/10/amazon-rd...

    • Lo probé personalmente hace unas semanas, y por ahora conviene no confiar en ello para PostgreSQL.
      Después de varios intercambios con AWS, el experimento quedó detenido durante horas, y recién más tarde la UI de AWS admitió que el cambio no se había aplicado. Por suerte falló de forma segura, pero no tengo confianza en que se pueda coordinar el momento real del cambio con datasets de más de GB.
    • Es cierto. Como OP, en ese momento estábamos en Aurora 11.9 y no estaba dentro de las versiones con soporte para despliegues blue/green.
      Tal vez la próxima vez sea posible.
  • Esto es excelente.
    Creé una herramienta que automatiza la mayor parte de lo que pasaron; si les resulta útil o quieren ampliarla con feedback/ideas, son más que bienvenidos: https://github.com/shayonj/pg_easy_replicate

    • Muy buena herramienta.
      Los hallazgos en tablas grandes podrían ser interesantes para una herramienta así. Si facilitara aplicar la estrategia adecuada por tabla, podría convertirse en una herramienta imprescindible para los equipos que hagan este tipo de migraciones en el futuro.
  • Me parece dudosa la idea de que “para un servicio como Knock no se permite ningún downtime, esté programado o no”
    Si es un sistema complejo, hay fallas y hay downtime. Un downtime de 15 minutos anunciado de antemano está bien para casi cualquier negocio SaaS. No es un hospital ni una central eléctrica
    Se genera mucho trabajo ficticio por pensar que el servicio es más importante de lo que realmente es. Si el tiempo de ingeniería invertido aquí se hubiera usado para mejorar el producto o la productividad del equipo de desarrollo, es muy probable que los usuarios hubieran quedado más contentos. Sobre todo si las notificaciones se pueden poner en una cola y ponerse al día después del downtime
    Si existe un SLA enterprise con condiciones de compensación por 15 minutos de downtime, podría justificarse, pero la mayoría no lo tiene. De hecho, es muy probable que ya hayan tenido algunas interrupciones similares o más largas
    En migraciones de bases de datos, la diferencia de trabajo entre “downtime breve” y “sin downtime” suele ser considerable, así que esto importa más. En especial cuando, como en este caso, es algo de una sola vez y las versiones recientes de PostgreSQL en RDS vienen soportadas por defecto; me parece particularmente difícil de justificar

    • Como OP, es cierto que todos los servicios tienen downtime por algún motivo
      También discutimos definir una ventana de interrupción, pero lo que seguíamos pensando era cómo podíamos hacer un ensayo del upgrade con datos de producción. Una réplica de PG 15 sincronizada con los datos de producción fue muy importante para validar que las cargas de trabajo se comportaran como esperábamos
      Usar una réplica en tiempo real permite hacer ensayos con impacto mínimo en el entorno de producción
      Una gran lección de esta migración fue lo útil que resulta rastrear y mitigar todos los riesgos imaginables en este tipo de proyecto. Al final, el riesgo de un upgrade in-place nos pareció mayor que el riesgo del camino elegido, y esa evaluación era independiente de si había o no una ventana de interrupción
      Como extra, si en el futuro necesitamos este enfoque, esta entrada de blog servirá como punto de partida y nos ahorrará semanas. Espero que también ayude a otros equipos en situaciones similares
    • Como médico, me parece curioso que “ni siquiera es un hospital” aparezca como ejemplo de un sistema que no puede tolerar downtime
      Epic, uno de los mayores proveedores de historias clínicas electrónicas en EE. UU., también tiene downtime programado al menos una vez al mes para upgrades, normalmente de unos 30 a 60 minutos cada vez
    • El problema es que en RDS no hay forma de actualizar una instancia de PostgreSQL con 15 minutos de downtime programado
      No se puede controlar el momento del reinicio. Si inicias el proceso, el cambio puede empezar una hora después, dos horas después, tres horas después, y no puedes saber ni controlar cuándo se reinicia
      Si tienes réplicas, se actualizan en paralelo y se reinician en momentos arbitrarios, lo que complica más las cosas
      Por lo tanto, si no puedes tolerar indisponibilidad arbitraria durante una ventana que, según el tamaño de la base de datos, puede extenderse por varias horas, la replicación lógica es prácticamente la única opción para upgrades en RDS
      Cuanto más grande es la instancia, más difícil se vuelve el problema
    • El verdadero problema del downtime es cuando todos los sistemas caen al mismo tiempo
      Si Jira cae 15 minutos al día, normalmente no tiene gran impacto. Hay otras cosas en la cola de trabajo y, en el peor de los casos, incluso si se combinan varias interrupciones, hay documentación prometida a alguien
      Pero si toda la suite de Atlassian se cae al mismo tiempo, se vuelve mucho más difícil mantener trabajo de reserva para seguir avanzando. Si haces que todas las apps de una empresa usen el mismo arreglo de almacenamiento, la pérdida de productividad puede saltar de 5% a 95%
    • A diferencia de la frase “un downtime de 15 minutos anunciado de antemano está bien para casi cualquier negocio SaaS”, puede haber un competidor que no tenga downtime todos los meses
      Ese competidor está poniendo mis necesidades por encima de su propia conveniencia
      Tu interrupción también es mi interrupción
  • En hava.io estamos pasando por este proceso ahora mismo
    Estamos subiendo de AWS RDS PostgreSQL 11.13 a 15.5
    Al final elegimos un enfoque relativamente simple: replicación unidireccional usando pglogical. Ya habíamos hecho una migración sin downtime de Google Cloud SQL a AWS RDS con el mismo método, así que teníamos confianza en que funcionaría sin impacto visible para los clientes
    pglogical hace que este tipo de migraciones sean bastante simples. No siempre es rápido, pero si puedes esperar unos días mientras toda la base de datos se replica gradualmente a la nueva instancia, está bien
    Este método también nos dio más libertad para cambiar el tipo y el tamaño del almacenamiento. Como teníamos almacenamiento sobredimensionado para obtener IOPS, queríamos cambiar el tipo de almacenamiento y también reducir el tamaño. Por eso una simple restauración desde snapshot no servía

  • Me pregunto si se refieren a esa funcionalidad que AWS prometió durante la etapa de “ingeniería de ventas”
    En la práctica, cuando tuvimos que hacer forzosamente un upgrade de versión mayor, no pudieron ofrecerla

  • Sorprende que no se pueda inicializar una réplica desde un backup
    Si se pudiera, habría evitado el trabajo de hacer streaming del contenido estable de la base de datos existente hacia el servidor nuevo
    Y esto no es “sin interrupciones”: hay unos segundos de downtime al cambiar el servicio al servidor nuevo
    El artículo omitió cómo preservaron la consistencia. Por ejemplo, no se puede simplemente conectar la aplicación a ambos servidores durante un tiempo. Las lecturas quizá podrían servirse desde ambos, aunque eso tampoco es perfecto, y las escrituras necesariamente deben ir a un solo servidor
    Por último, tampoco hay opción de rollback. En operaciones donde se mueve de una vez un volumen tan grande de datos, a veces las cosas salen mal tarde en la noche. Por eso siempre hace falta un plan que permita volver al paso anterior y dormir con la certeza de que el servicio seguirá vivo por la mañana
    En particular, si ya enviaste transacciones de escritura al servidor nuevo y por alguna razón tienes que volver al servidor anterior, es difícil, y los datos ya quedaron inconsistentes

    • Como OP, sí se puede inicializar una réplica desde un backup, pero no se obtienen las escrituras que siguen ocurriendo durante el backup
      Si no hay algún medio de replicación, o si no se sube esa lógica a la capa de la aplicación, el sistema restaurado tendrá escrituras faltantes
      Por ejemplo, se podría modificar la app para aplicar escritura doble. Entiendo que eso hicieron equipos que replatformearon toda su aplicación desde un RDBMS a una base de datos completamente distinta, como Apache Cassandra
      En nuestro caso, la escritura doble parecía más riesgosa que configurar replicación por streaming con una funcionalidad nativa de PostgreSQL. Pero para algunos equipos puede ser una mejor opción
      Sobre “no es sin interrupciones” y “faltan detalles de preservación de consistencia”, el artículo explicó en detalle cómo mantuvimos la consistencia y evitamos downtime de la API. La idea central es que la app estaba conectada a ambas bases de datos, pero todavía no usaba la base nueva como principal
      Luego enviamos una señal de cambio a todas las instancias de la app con LaunchDarkly, que mantiene conexiones de baja latencia con todas las instancias
      Durante el primer segundo después de la señal, los servidores pusieron en cola las solicitudes a la base de datos para que la replicación pudiera ponerse al día. Esto causó un breve pico de latencia, pero estaba dentro de un margen aceptable calculado intencionalmente. Después de esa pausa temporal, las solicitudes fluyeron como de costumbre, pero apuntando a la base de datos nueva, y el cambio quedó completado
      Para el tráfico que seguía quedando hacia la base de datos anterior, también agregamos una desconexión forzada con timeout de 500 ms. Ese valor era mucho mayor que nuestro tiempo de consulta p99, así que no terminaba consultas en ejecución a la fuerza. Con esto se detuvo el tráfico hacia la base de datos anterior y la replicación tuvo tiempo suficiente para ponerse al día
      La opción de rollback no quedó en el blog, pero también evaluamos crear una base de datos alternativa en PG 11.9 y replicar la base 15.3 hacia esa tercera base de datos. Si teníamos que interrumpir, podríamos haber hecho roll forward a esa base de datos de la misma versión
      Después de practicar varias veces el procedimiento de upgrade en staging y confirmar las probabilidades de éxito, decidimos no usar esa opción. Como hicimos varios ensayos, teníamos confianza para el cambio real. En producción también validamos algunas cargas de trabajo solo de lectura contra la instancia 15.3 mediante despliegue canario, tratándola como una réplica de lectura
      Para evitar problemas tarde en la noche, lo hicimos deliberadamente temprano en la tarde-noche de un fin de semana. El cambio fue cuidadosamente scriptado y ensayado para reducir el riesgo de error humano
      Si ocurría una falla catastrófica, el sistema también estaba preparado para volver a la base de datos anterior. En ese caso habría habido pérdida de algunos datos que entraron en la base nueva, y teníamos preparadas conciliaciones para las partes críticas. Para reducir el riesgo de pérdida de datos, pausamos temporalmente algunas tareas en segundo plano durante el cambio y así redujimos la cantidad de escrituras
      No incluimos estos detalles en el blog porque queríamos enfocarnos en los detalles relacionados con PostgreSQL más que en consideraciones específicas de Knock. Cualquier equipo que quiera aplicar este playbook siempre debe armar su propia lista de riesgos en su contexto y mitigarlos
  • La parte sobre secuencias definitivamente es interesante
    Hace tiempo que casi no uso secuencias; principalmente uso UUID secuenciales, UUID v7 o enfoques como HiLo
    https://en.wikipedia.org/wiki/Hi/Lo_algorithm

    • Para quienes quieran mantener dentro de la base de datos la responsabilidad de generar UUID v7 hasta que PostgreSQL lo soporte de forma nativa, una función PL/pgSQL puede ayudar
      Basándose en el borrador de la especificación de IETF, crea una secuencia de 12 bits y construye el UUID combinando los milisegundos actuales desde el UNIX epoch con 62 bits aleatorios
      La clave es tener uuidv7_seq y hacer que la función generate_uuidv7() use clock_timestamp(), NEXTVAL y RANDOM() para devolver un valor con formato UUID v7
    • Como OP, evitamos las secuencias salvo en una parte de la aplicación por una dependencia
      Usamos KSUID y UUID v4 en varios lugares. Esta “trampa” aplica a todas las secuencias, así que vale la pena señalarla como consejo general al hacer este tipo de migraciones
      [1]: https://segment.com/blog/a-brief-history-of-the-uuid/
  • No quiero restarle mérito al enorme trabajo que hicieron con éxito, pero me pregunto por qué no hicieron upgrades pequeños cada vez que salía una versión nueva
    Como lectura es excelente, pero se siente como una historia de marineros que, en vez de rodear una gran tormenta, decidieron atravesarla de frente aun sabiendo que podía terminar en tragedia
    En este caso, ¿los upgrades pequeños no eran una opción? Me pregunto si era algo como “cada upgrade pequeño implicaba el mismo costo de downtime que un upgrade grande, así que lo postergamos lo máximo posible”. En la introducción parece haber indicios de eso, aunque quizá estoy leyendo demasiado

    • Como OP, habríamos usado el mismo enfoque también para upgrades menores
      Más que “lo postergamos hasta quedar contra la pared”, fue algo más parecido a “si no está roto, no lo arregles”, aunque sabíamos que en algún momento tendríamos que dar el salto
    • Subir N versiones es casi lo mismo en términos de amenaza a la disponibilidad, sea N igual a 1 o a 3
    • Cada upgrade implica downtime
      Aunque la respuesta real sea menos de 60 segundos, en el camino hasta 15 habríamos pasado por ese downtime varias veces