Be Software Architecture Wise
Be Wise Apps

Read in English · Lire en français · Auf Deutsch lesen

Sección 7.7 de 7.8 · Intermedio · 25 min de lectura

Evolución de esquemas, migraciones y compatibilidad

Videolección

5 min · narrado en inglés, con subtítulos
Solo escuchar (audio)
Leer la transcripción

Todo esquema cambiará mientras el sistema está en funcionamiento: tablas de bases de datos, cargas útiles de API y eventos por igual. En esta lección aprenderás a hacer esos cambios de forma segura, sin tiempo de inactividad.

Imagina un hospital que cambia el formulario en papel que se usa para cada ingreso, mientras los pacientes siguen llegando. Si simplemente cambia los formularios de un día para otro, el turno de noche tendrá en la mano el formulario antiguo mientras el turno de día espera el nuevo. Y los expedientes archivados siguen usando el diseño antiguo. Un hospital cuidadoso imprime un formulario con las casillas antiguas y las nuevas, y lo usa hasta que todos se han pasado al nuevo y los expedientes antiguos se han copiado. Solo entonces elimina las casillas antiguas. Los equipos de software cambian la estructura de sus datos exactamente de esta forma cuidadosa, paso a paso.

Tres hechos lo hacen difícil. Primero, en cualquier sistema que se despliega sin tiempo de inactividad, las versiones antigua y nueva del código se ejecutan en paralelo, durante un despliegue gradual y después de cualquier rollback. Así que cada estado del esquema debe funcionar con al menos dos versiones del código. Segundo, los datos sobreviven al código. Las filas escritas hace cinco años, los eventos guardados en un log y las copias de seguridad conservan todos la forma antigua. Tercero, los esquemas se comparten. Una columna puede ser leída por informes, trabajos de extracción y otros servicios. Por eso un esquema privado, con un propietario y detrás de una interfaz, es mucho más fácil de cambiar.

La compatibilidad se define por lectores y escritores. A la izquierda están los datos escritos con el esquema antiguo y con el nuevo; a la derecha, los lectores que usan cada uno. Las versiones que coinciden siempre funcionan. Un cambio es compatible hacia atrás cuando los lectores nuevos pueden leer datos antiguos. Añadir un campo opcional con un valor por defecto es compatible hacia atrás: el lector nuevo rellena el valor por defecto. Un cambio es compatible hacia adelante cuando los lectores antiguos pueden leer datos nuevos. Eso exige que los lectores antiguos ignoren los campos que no conocen. La compatibilidad completa significa que funcionan ambas direcciones, así que lectores y escritores pueden actualizarse en cualquier orden. En una base de datos, el código nuevo suele empezar a escribir antes de que se vuelvan a desplegar todos los lectores, así que necesitas al menos compatibilidad hacia adelante.

Algunos cambios son seguros. Añadir un campo opcional, o una columna que admite nulos con un valor por defecto, es el cambio seguro por excelencia. Un campo obligatorio sin valor por defecto rompe los datos antiguos, así que primero añádelo como opcional, rellénalo y solo entonces hazlo obligatorio. Un cambio de nombre se rompe en ambas direcciones a la vez, así que nunca se hace en un solo paso. Y el cambio más peligroso mantiene la forma pero cambia el significado, como pasar un importe de peniques a libras. Todos los lectores se equivocan sin darse cuenta. Crea siempre un campo nuevo con un nombre nuevo.

La técnica para los cambios incompatibles se llama expandir y contraer. Aquí, la plataforma de reparto de comida renombra una columna de postcode a postal code, en una tabla de cuatro millones de filas. Un único cambio de nombre rompería el código en ejecución. Así que primero, expandir: añadir la columna nueva, vacía. A continuación, la aplicación escribe el mismo valor en ambas columnas, pero sigue leyendo la antigua. Un trabajo en segundo plano copia las filas existentes en lotes pequeños y después una comprobación confirma que no hay filas que difieran. Ahora la aplicación lee la columna nueva, pero sigue escribiendo en ambas, así que revertir al lanzamiento anterior sigue siendo seguro. Después deja de escribir en la columna antigua, y el equipo espera hasta que no vaya a hacer falta ningún rollback. Por último, contraer: hacer obligatoria la columna nueva y eliminar la antigua.

Aquí están esos seis pasos desplegables en fila. Cada uno funciona con el código del paso anterior, y cada uno puede revertirse. Fíjate en que durante los pasos uno a cinco existen ambas columnas. Seis despliegues para un cambio de nombre parecen muchos, pero ningún lanzamiento necesita tiempo de inactividad y ningún equipo necesita coordinarse. El paso de contracción es el que los equipos olvidan. Haz un seguimiento explícito, o el esquema se llenará de columnas migradas a medias.

Los cambios en la base de datos deben estar en el control de versiones junto al código, como una serie ordenada de pequeños scripts de migración. Herramientas como Flyway, Liquibase y Alembic los aplican en orden, en pruebas, en staging y en producción. Nunca edites una migración que se haya aplicado en algún sitio; escribe una nueva. Y ensaya con datos de tamaño de producción, porque una sentencia que tarda cincuenta milisegundos en pruebas puede bloquear una tabla grande durante una hora. Los backfills grandes se ejecutan como trabajos separados, en lotes pequeños, que se ralentizan cuando las réplicas se quedan atrás y que pueden reiniciarse con seguridad. Para los eventos, un registro de esquemas comprueba cada versión nueva contra la regla de compatibilidad y rechaza un cambio incompatible antes de que llegue a producción. Da un valor por defecto a todo campo que puedas añadir o eliminar.

La sección completa tiene el SQL de cada paso, más sobre los formatos de eventos y una lista de comprobación. A continuación, en la Sección siete punto ocho, veremos la analítica y el streaming. Nos vemos allí.

Lo que necesitas saber

Por qué los esquemas deben evolucionar sin detener el mundo

Todo dato almacenado o transmitido tiene un esquema: las tablas y los tipos de columna de una base de datos, los campos de una respuesta JSON de una API, la definición del registro de un evento en un flujo. Los esquemas cambian constantemente porque el negocio cambia: el banco añade un segundo titular de cuenta, la plataforma de reparto de comida empieza a registrar las propinas, un regulador exige un campo nuevo. La pregunta arquitectónica es cómo cambiar un esquema mientras el sistema sigue funcionando, cuando la forma antigua la siguen usando el código, los datos almacenados y otros equipos.

Tres hechos lo hacen difícil. Primero, en cualquier sistema que se despliega sin tiempo de inactividad, las versiones antigua y nueva del código se ejecutan en paralelo: un despliegue gradual (rolling deployment) sustituye las instancias una a una, un canary release (lanzamiento canario) envía una pequeña parte del tráfico a la nueva versión y un rollback trae de vuelta la versión antigua. Por tanto, cada estado del esquema debe funcionar con al menos dos versiones del código. Segundo, los datos sobreviven al código. Las filas escritas hace cinco años, los eventos de un log que se conservan para siempre, los mensajes que esperan en una cola y las copias de seguridad conservan todos la forma antigua. Tercero, los esquemas se comparten: una columna la leen informes, trabajos de extracción y a veces otros servicios, y un esquema de eventos lo leen consumidores que el productor quizá ni siquiera conozca. La Sección 7.1 defendía que los datos tengan un propietario y estén detrás de una interfaz; la evolución de esquemas es donde esa disciplina da sus frutos, porque un esquema privado puede cambiarse con el acuerdo de un solo equipo.

Compatibilidad hacia atrás, hacia adelante y completa

La compatibilidad se define en términos de lectores y escritores. Un cambio es compatible hacia atrás cuando el código que usa el esquema nuevo puede leer datos escritos con el antiguo. Añadir un campo opcional con un valor por defecto es compatible hacia atrás: el lector nuevo aporta el valor por defecto cuando a los datos antiguos les falta el campo. Un cambio es compatible hacia adelante cuando el código que usa el esquema antiguo puede leer datos escritos con el nuevo. Eso exige que los lectores antiguos ignoren los campos que no conocen, que es la disciplina del lector tolerante (tolerant reader). Un cambio es totalmente compatible cuando es ambas cosas, lo que significa que lectores y escritores pueden actualizarse en cualquier orden.

La dirección que necesitas depende de quién se actualiza primero. Si los consumidores se actualizan antes que los productores (algo habitual cuando un flujo tiene pocos consumidores y los controlas tú), necesitas compatibilidad hacia atrás. Si los productores se actualizan primero, que es el caso habitual en una base de datos (el código nuevo empieza a escribir antes de que se hayan vuelto a desplegar todos los lectores) y en las API públicas, necesitas compatibilidad hacia adelante. Cuando no puedes controlar el orden, o necesitas revertir cualquiera de los dos lados, apunta a la compatibilidad completa. Confluent Schema Registry, por ejemplo, usa por defecto la compatibilidad hacia atrás comprobada contra la versión anterior y ofrece variantes transitivas que comprueban contra todas las versiones anteriores, lo que importa cuando los consumidores pueden reproducir datos antiguos desde el principio de un log.

Una advertencia sobre el vocabulario. En el diseño de API, compatible hacia atrás se usa a menudo de forma laxa con el sentido de los clientes existentes siguen funcionando, lo que, en términos de lectores y escritores, es compatibilidad hacia adelante para los clientes que leen tus respuestas y compatibilidad hacia atrás para las solicitudes que tú lees de ellos. Cuando una discusión depende del término, pregunta qué lado cambia y qué lado debe seguir funcionando.

Qué lector puede leer qué datos

Compatibilidad hacia atrás: los lectores nuevos leen datos antiguos; hacia adelante: los lectores antiguos leen datos nuevos; completa: ambas cosasDatos escritoscon esq. v1 (antiguo)Datos escritoscon esq. v2 (nuevo)Lector conesq. v1 (antiguo)Lector conesq. v2 (nuevo)misma versión: siempre bienmisma versión: siempre bienATRÁSnuevo lee viejoADELANTEviejo lee nuevoCOMPLETA = ambas diagonales funcionan: lectores y escritores, en cualquier orden
La compatibilidad hacia atrás protege a los lectores nuevos de los datos antiguos; la compatibilidad hacia adelante protege a los lectores antiguos de los datos nuevos.

Cambios habituales y su compatibilidad

CambioHacia atrás (lo nuevo lee lo antiguo)Hacia adelante (lo antiguo lee lo nuevo)Enfoque seguro
Añadir un campo opcional o una columna que admite nulos con un valor por defectoSíSí, si los lectores ignoran los campos desconocidosEl cambio seguro por excelencia
Añadir un campo obligatorio sin valor por defectoNo: a los datos antiguos les faltaSíAñadirlo como opcional, hacer backfill y después imponerlo
Eliminar un campoSíSolo si los lectores antiguos tienen un valor por defecto para élDejar de leerlo en todas partes primero y después eliminarlo
Renombrar un campo o una columnaNoNoExpandir y contraer: añadir el nuevo, copiar, cambiar, eliminar el antiguo
Cambiar un tipo (de texto a entero, de int a long)Depende del formato: ampliar suele ser seguro, reducir noDependeAñadir un campo nuevo del tipo nuevo y migrar
Cambiar el significado sin cambiar la forma (de peniques a libras)Erróneo sin avisarErróneo sin avisarNunca: crear un campo nuevo con un nombre nuevo

Expandir y contraer: la técnica central

La mayoría de los cambios incompatibles pueden dividirse en una secuencia de cambios compatibles. El patrón se llama expandir y contraer (expand and contract), o cambio en paralelo (parallel change; la descripción de Danilo Sato de 2014 en martinfowler.com llama a las fases expandir, migrar y contraer). Primero, expandir: añadir la nueva estructura junto a la antigua, de modo que funcionen tanto el código antiguo como el nuevo. Después, migrar: cambiar los escritores para que mantengan ambas, copiar los datos existentes y pasar los lectores a la nueva estructura uno a uno. Por último, contraer: cuando ya nada usa la estructura antigua, eliminarla. Cada paso se despliega por separado, cada uno es compatible con la versión anterior y cada uno puede revertirse sin perder datos.

El costo es tiempo y un desorden temporal: el cambio abarca varios lanzamientos y, durante días o semanas, el esquema lleva las dos formas. La ventaja es que ningún lanzamiento necesita tiempo de inactividad, ningún despliegue necesita coordinarse entre equipos y cualquier paso puede pausarse. Refactoring Databases (2006), de Scott Ambler y Pramod Sadalage, cataloga decenas de refactorizaciones de bases de datos basadas en la misma idea, con un periodo de transición durante el cual coexisten los esquemas antiguo y nuevo, sincronizados por la aplicación o mediante triggers.

Ejemplo práctico

Ejemplo práctico: renombrar una columna sin tiempo de inactividad

La tabla de restaurantes de la plataforma de reparto de comida tiene una columna postcode. La plataforma se está expandiendo a países que usan otros formatos postales, y el equipo quiere que se llame postal_code. La tabla tiene 4 millones de filas, el servicio de restaurantes se despliega varias veces al día con actualizaciones graduales y un trabajo de extracción nocturno también lee la columna. Un único ALTER TABLE ... RENAME COLUMN rompería todas las instancias en ejecución del código antiguo en el momento en que se confirmara.

Lanzamiento 1: expandirUna migración añade postal_code text NULL. En PostgreSQL 11 y posteriores, añadir una columna sin valor por defecto o con un valor por defecto constante es un cambio solo de metadatos. El código antiguo ignora la columna nueva; nada se rompe.
Lanzamiento 2: escritura dualLa aplicación escribe el mismo valor en ambas columnas en cada inserción y actualización, y sigue leyendo postcode. (Un trigger de base de datos que copie una columna en la otra es una alternativa cuando varias aplicaciones escriben en la tabla).
Paso 3: backfillUn trabajo en segundo plano copia postcode en postal_code para las filas existentes en lotes de 5000 por rango de clave primaria, con pausas entre lotes y vigilando el retardo de replicación (replication lag). Después, una consulta de verificación cuenta las filas en las que las dos difieren: debe devolver 0.
Lanzamiento 4: cambiar las lecturasLa aplicación lee postal_code pero sigue escribiendo en ambas columnas, así que revertir al lanzamiento 2 o 3 sigue siendo seguro. El trabajo de extracción también se cambia para que lea la columna nueva.
Lanzamiento 5: dejar de escribir en la columna antiguaLa aplicación escribe solo en postal_code. A estas alturas la columna antigua no debe ser NOT NULL, o las inserciones fallarán. Espera lo suficiente para tener la seguridad de que no hará falta revertir al lanzamiento 4.
Lanzamiento 6: contraerUna migración impone NOT NULL en postal_code (mediante una restricción de comprobación NOT VALID, validada por separado, que PostgreSQL 12 y posteriores pueden usar para establecer NOT NULL sin un recorrido largo y bloqueante) y, por último, elimina postcode.

Seis despliegues para un solo cambio de nombre parecen excesivos, pero los pasos pueden combinarse cuando el equipo controla todos los lectores y acepta una ventana de rollback corta; el principio que importa es que cada estado desplegado funcione con el código anterior y el posterior.

El paso de contracción es el que los equipos olvidan. Haz un seguimiento explícito (un ticket, un estado de ADR, una regla de lint que señale la columna antigua) o el esquema se llenará de columnas migradas a medias.

El cambio de nombre en seis pasos desplegables

Renombrar una columna con expandir y contraer: añadir la columna nueva, escritura dual, backfill, cambiar las lecturas, dejar de escribir la antigua y eliminarlaUn renombrado lógico en seis pasos desplegables; cada uno se puede revertir al anterior1 Expandirañade postal_codeambas (nueva null)2 Escr. dualapp escribe ambaslee postcode3 BackfillUPDATE por lotesverifica: 0 dif.4 Cambia lect.lee postal_codeaún escribe ambas5 Parar ant.escribe la nuevamira y espera6 Contraerborra la viejacolumnaColumnas que haypostcodepostcode y postal_code coexisten (pasos 1 a 5)postal_codetras el paso 6Expandir (aditivo, compatible con código viejo)Contraer (solo cuando nada lee la columna antigua)
Durante los pasos 1 a 5 existen ambas columnas, así que cualquier versión del código del paso anterior puede seguir ejecutándose.
-- Migration V41 (expand): metadata-only change, no table rewrite in PostgreSQL 11+
SET lock_timeout = '3s';            -- fail fast rather than queue behind long queries
ALTER TABLE restaurants ADD COLUMN postal_code text;

-- Backfill job (run repeatedly by a script, not inside one big migration)
UPDATE restaurants
SET    postal_code = postcode
WHERE  id >= :from_id AND id < :from_id + 5000   -- one small batch per transaction
AND    postal_code IS DISTINCT FROM postcode;

-- Verify before switching reads
SELECT count(*) FROM restaurants WHERE postal_code IS DISTINCT FROM postcode;  -- expect 0

-- Migration V44: enforce NOT NULL without a long blocking scan
ALTER TABLE restaurants ADD CONSTRAINT postal_code_nn CHECK (postal_code IS NOT NULL) NOT VALID;
ALTER TABLE restaurants VALIDATE CONSTRAINT postal_code_nn;  -- scans, but allows writes
ALTER TABLE restaurants ALTER COLUMN postal_code SET NOT NULL;  -- PostgreSQL 12+: uses the check, skips scan
ALTER TABLE restaurants DROP CONSTRAINT postal_code_nn;

-- Migration V45 (contract), weeks later, once nothing reads postcode
ALTER TABLE restaurants DROP COLUMN postcode;
Sentencias de PostgreSQL para los pasos de expansión, backfill, verificación y contracción, escritas para evitar bloqueos largos.

Las migraciones como código en el pipeline de entrega

Los cambios en la base de datos deben estar en el control de versiones junto al código de la aplicación, como una serie ordenada de migraciones, cada una un pequeño script que lleva el esquema de una versión a la siguiente. Evolutionary Database Design, de Pramod Sadalage y Martin Fowler (publicado por primera vez en 2003 y reescrito en 2016), estableció la práctica, y las herramientas están maduras. Flyway ejecuta en orden archivos SQL versionados como V41__add_postal_code.sql y registra cada uno en una tabla flyway_schema_history. Liquibase describe los cambios como changesets en SQL, XML, YAML o JSON, de los que lleva registro en una tabla DATABASECHANGELOG. Alembic, para Python y SQLAlchemy, genera scripts de revisión con funciones upgrade() y downgrade() y registra la revisión actual en alembic_version. Flyway y Liquibase también guardan una suma de comprobación (checksum) de cada migración aplicada y se detienen con un error si alguna se ha editado después, lo que evita que los entornos diverjan sin que nadie lo note.

En un pipeline de entrega continua, las mismas migraciones se ejecutan contra una base de datos nueva en las pruebas, después contra staging y después contra producción, de modo que producción nunca recibe un cambio que no se haya ensayado. Las buenas prácticas añaden algunas reglas. Nunca edites una migración que se haya aplicado en algún sitio; escribe una nueva. Mantén los cambios de esquema y el código que depende de ellos en despliegues separados, con el cambio de esquema primero en las expansiones y al final en las contracciones. Prueba las migraciones con una copia de datos de tamaño de producción, porque una sentencia que tarda 50 ms en una base de datos de pruebas puede bloquear una tabla grande durante una hora. Prefiere avanzar con una migración nueva antes que usar migraciones descendentes que eliminan columnas y, con ellas, datos. Y deja que la base de datos te avise de los bloqueos: en PostgreSQL, un ALTER TABLE que espera su bloqueo bloquea todas las consultas que llegan después, así que establece un lock_timeout corto y reintenta en lugar de quedarte en cola detrás de un informe de larga duración.

Backfills grandes y cambios de esquema en línea

Un backfill (relleno de datos) rellena una columna o tabla nueva para las filas existentes. Hecho como un único UPDATE sobre 400 millones de transacciones, mantiene bloqueos durante horas, genera un enorme volumen de registro de escritura anticipada (write-ahead log, WAL) que las réplicas deben reproducir (lo que provoca retardo de replicación) y, en PostgreSQL, deja versiones de fila muertas que el vacuum debe limpiar. El enfoque profesional es un trabajo separado y reiniciable que procesa lotes pequeños por rango de clave primaria, confirma cada lote, registra su progreso, reduce su ritmo cuando aumentan el retardo de replicación o la carga de la base de datos, y es idempotente, de modo que volver a ejecutar un lote no causa ningún daño. Ejecútalo fuera de la herramienta de migraciones, para que un despliegue nunca tenga que esperarlo.

Algunos cambios no pueden hacerse en el sitio sin bloquear. PostgreSQL ofrece CREATE INDEX CONCURRENTLY (que no puede ejecutarse dentro de un bloque de transacción, así que hay que indicárselo a las herramientas de migración) y la secuencia NOT VALID seguida de VALIDATE CONSTRAINT mostrada arriba. Para MySQL, herramientas como gh-ost y pt-online-schema-change construyen una copia en la sombra de la tabla con el esquema nuevo, la mantienen sincronizada con las escrituras en curso y la ponen en su lugar con un breve cambio de nombre. Cambiar el tipo de una columna suele hacerse mejor con expandir y contraer hacia una columna nueva que con un ALTER en el sitio, que en la mayoría de las bases de datos reescribe toda la tabla.

Registros de esquemas, Avro y Protocol Buffers

Para los eventos y los mensajes, el equivalente de una herramienta de migraciones es un registro de esquemas. Los productores registran cada versión del esquema; el registro la comprueba contra la regla de compatibilidad configurada y rechaza un cambio incompatible en el momento de la compilación o del despliegue, en lugar de en producción. Cada mensaje lleva un identificador de esquema corto, de modo que los consumidores pueden obtener el esquema exacto con el que se escribió.

Apache Avro hace explícito el esquema del escritor: para decodificar se necesitan tanto el esquema del escritor como el del lector, y las reglas de resolución de Avro emparejan los campos por nombre, ignoran los campos del escritor que el lector no conoce y rellenan los campos del lector que faltan en los datos con sus valores por defecto declarados. De ahí la regla práctica: da un valor por defecto a todo campo que puedas añadir o eliminar. En el ejemplo de abajo, currency y tip_pence se añadieron en versiones posteriores con valores por defecto, así que los eventos antiguos se siguen decodificando con los consumidores nuevos.

Protocol Buffers identifica los campos por número, no por nombre, en la transmisión. Añadir campos es seguro, y los lectores antiguos conservan los campos desconocidos al volver a serializar. Renombrar un campo no afecta a la codificación binaria (aunque sí cambia la forma JSON). Las reglas fundamentales son no cambiar ni reutilizar nunca un número de campo, y marcar los números y nombres de los campos eliminados como reserved para que nadie los reutilice por accidente. Confluent recomienda la compatibilidad hacia atrás transitiva para Protobuf, porque añadir un tipo de mensaje nuevo no es compatible hacia adelante. También se puede registrar y comprobar JSON Schema, pero la apertura de JSON (¿se permite o no una propiedad desconocida?) hace que las reglas de compatibilidad sean más sutiles.

{
  "type": "record",
  "name": "OrderPlaced",
  "namespace": "food.orders.v1",
  "fields": [
    {"name": "order_id",       "type": "string"},
    {"name": "restaurant_id",  "type": "string"},
    {"name": "total_pence",    "type": "long"},
    {"name": "currency",       "type": "string", "default": "GBP"},
    {"name": "tip_pence",      "type": ["null", "long"], "default": null}
  ]
}
Un esquema Avro para un evento OrderPlaced; los campos añadidos después de la versión 1 llevan valores por defecto, así que los consumidores nuevos pueden leer los eventos antiguos.

Evolución de los esquemas de API y de eventos

Las API y los eventos siguen los mismos principios con herramientas distintas. En las API, prefiere los cambios aditivos, exige que los clientes sean lectores tolerantes y reserva los cambios incompatibles para una versión nueva que se ejecute junto a la antigua, con monitorización del uso para ver cuándo puede retirarse la versión antigua (la Sección 6.5 trata los enfoques de versionado). Las pruebas de contratos dirigidos por el consumidor permiten que cada consumidor publique los campos de los que realmente depende, de modo que el proveedor sabe qué cambios son seguros.

Los eventos son más difíciles que las API porque se almacenan: un consumidor puede reproducir un topic desde el principio, y un sistema basado en event sourcing (Sección 7.5) reconstruye el estado a partir de eventos escritos hace años. Trata un esquema de eventos publicado como un contrato público, versiónalo en el registro y, cuando un cambio sea realmente incompatible, publica un tipo de evento o un topic nuevo (OrderPlaced.v2) durante un tiempo junto al antiguo, en lugar de modificar el existente. La Sección 6.4 trata el diseño de eventos de forma más general.

Lista de comprobación: antes de lanzar un cambio de esquema

  • ¿Es aditivo el cambio?Si no lo es, divídelo en pasos de expandir, migrar y contraer, cada uno desplegable por sí solo.
  • ¿Funciona cada estado desplegado con las versiones de código anterior y siguiente?Los despliegues graduales y los rollbacks implican que siempre se ejecutan dos versiones a la vez.
  • ¿Quién más lee estos datos?Busca informes, trabajos de extracción, otros servicios y consumidores de eventos; revisa los suscriptores del registro y el linaje en el catálogo de datos.
  • ¿Se ha ejecutado la migración con datos de tamaño de producción?Mide la duración de los bloqueos, las reescrituras de tablas y el volumen del log; usa la creación concurrente de índices y las restricciones NOT VALID donde estén disponibles.
  • ¿Hay un tiempo de espera de bloqueo establecido?Falla rápido y reintenta en lugar de dejar que un ALTER en espera bloquee todo el tráfico.
  • ¿El backfill es separado, por lotes, con ritmo regulado, idempotente y reanudable?Nunca debe bloquear un despliegue ni saturar las réplicas.
  • ¿Tienen los campos nuevos valores por defecto y no se reutiliza ningún número ni nombre de campo?Imprescindible para Avro y Protocol Buffers; lo impone la comprobación de compatibilidad del registro.
  • ¿Ha cambiado el significado de algún campo?Los cambios de unidades, de moneda o de semántica necesitan un nombre de campo nuevo, nunca uno reutilizado.
  • ¿Se hace seguimiento del paso de contracción?Registra un ticket o un ADR para que la estructura antigua se elimine de verdad cuando deje de usarse.

Comprueba lo que has entendido

Un equipo añade a un esquema Avro un campo obligatorio sin valor por defecto. ¿Qué compatibilidad rompe y por qué?

La compatibilidad hacia atrás: un consumidor que usa el esquema nuevo para leer datos antiguos encuentra que falta el campo y no tiene ningún valor por defecto con el que rellenarlo.

¿Por qué continúa el paso de escritura dual después de que las lecturas pasen a la columna nueva?

Para que, si se revierte al lanzamiento anterior, que todavía lee la columna antigua, esa columna esté al día.

¿Por qué un backfill grande no debe escribirse como una única sentencia UPDATE dentro de una migración?

Una transacción enorme mantiene bloqueos durante mucho tiempo, genera una ráfaga de log que provoca retardo de replicación, deja mucho trabajo de limpieza, no puede reanudarse y bloquea el despliegue; los lotes pequeños confirmados evitan todo esto.

¿Qué evita marcar como reserved un número de campo de Protocol Buffers?

Impide que alguien reutilice el número de un campo eliminado para un campo nuevo, lo que haría que los datos antiguos se decodificaran como el campo equivocado.

Siguiente · 7.8Analítica y streamingCómo se diseña la arquitectura de los datos analíticos: OLTP frente a OLAP, almacenes de datos (data warehouses) y esquemas en estrella, ETL y ELT, data lakes y lakehouses, procesamiento de flujos con tiempo del evento y marcas de agua (watermarks), Lambda y Kappa, y data mesh.Continuar →

Fuentes y lecturas recomendadas

¿Tienes una pregunta sobre esta sección? ArchitectBot, el asistente de estudio con IA del curso, responde primero a partir del curso.

Pregunta: “Necesitamos dividir la columna del nombre del cliente en nombre y apellidos en una tabla de 50 millones de filas sin tiempo de inactividad. ¿Qué pasos y lanzamientos planificarías?”