Módulo 6: Migrar datos sin downtime
Backfill: cargar los datos históricos
Descripción
La lección 3 encendió el dual-write y dejó una tarea pendiente muy visible: usb-c-hub y webcam —y todo el histórico— siguen solo en el viejo, porque el dual-write no mira hacia atrás. Esta lección cierra ese hueco con la segunda pieza de la migración: el backfill. La idea, en una frase: copiar al almacén nuevo los registros que ya existían antes de encender el dual-write, traduciéndolos con el ACL, hasta que el nuevo tenga tanto el frente (lo que puso el dual-write) como la retaguardia (lo que pone el backfill).
Suena a un simple bucle que copia todo del viejo al nuevo, y en esencia lo es —pero tiene dos reglas duras que, si se ignoran, corrompen datos en silencio—:
- No pisar las escrituras frescas. El dual-write ya puso en el nuevo los valores actuales de los registros que se escribieron desde que se encendió. Si el backfill copia sobre ellos una versión vieja (la que tenía el registro cuando el backfill tomó su foto del viejo), sobrescribe un dato fresco con uno rancio. El backfill debe copiar solo lo que falta, nunca pisar lo que el dual-write ya dejó.
- Idempotencia. El backfill de un sistema real puede tardar horas y puede caerse a la mitad; hay que poder volver a correrlo sin miedo. Un backfill idempotente, corrido dos veces (o diez), deja el nuevo exactamente igual que corrido una vez: no duplica, no corrompe, solo completa lo que falte.
Las dos reglas nacen de la misma decisión de diseño: el backfill hace insert-if-absent —inserta una fila solo si no está ya en el nuevo—. Esa única regla logra las dos cosas: no pisa lo fresco (porque lo fresco ya está, así que lo salta) y es idempotente (correrlo otra vez no encuentra nada nuevo que insertar). Toda la lección gira alrededor de por qué esa regla es la correcta.
Y hay una tercera pieza, que es de orden: el backfill va después de encender el dual-write, nunca antes. La razón es sutil y crucial, y la vas a ver ejecutada: si backfilleas primero y enciendes el dual-write después, las escrituras que ocurran en el hueco entre ambos se pierden para el nuevo. Encender el dual-write primero garantiza que ese hueco no exista.
Conexión con el módulo. El dual-write (lección 3) cubrió el frente; el backfill cubre el histórico; juntos dejan el nuevo completo. Pero "completo" es una afirmación que hay que verificar, y eso es la lección 5 (parallel-run): comparar los dos almacenes para comprobar que el dual-write más el backfill de verdad dejaron el nuevo igual al viejo. Fíjate en la frontera: aquí el backfill es un bucle en memoria sobre un diccionario; a escala, backfillear billones de filas sin saturar la base ni bloquear las escrituras vivas (por lotes, con pausas, reanudable con checkpoints) es un problema de ingeniería de datos en producción —del ecosistema Data Engineering—. El patrón (insert-if-absent, idempotente, después del dual-write) es idéntico; la maquinaria para ejecutarlo a esa escala es del otro ecosistema.
Una analogía: copiar el archivero viejo al nuevo sin pisar lo fresco
Sigues con la mudanza de la lección 3. El reenvío de correo (el dual-write) ya está activo: cada carta nueva llega a las dos casas. Ahora te toca lo que el reenvío no hace —llevar el archivo histórico—: tienes un archivero viejo lleno de expedientes acumulados por años, y quieres tener todos esos expedientes también en el archivero nuevo de la casa nueva.
El plan obvio: sacar cada expediente del archivero viejo, fotocopiarlo, y guardarlo en el nuevo. Pero hay una trampa, y es la regla que da nombre a la lección. Mientras copias el archivo (tarda días), el reenvío sigue trayendo actualizaciones frescas. Imagina el expediente de "SSD": ayer, cuando empezaste a copiar, decía "precio 89.99". Hoy llegó por reenvío una actualización —"precio 84.99"— que ya guardaste en el archivero nuevo. Si ahora sacas de tu caja la fotocopia vieja del expediente "SSD" (la de 89.99, que tomaste ayer) y la metes en el archivero nuevo, sobrescribes la versión fresca de 84.99 con la vieja de 89.99. Acabas de rancar el dato con tu propia mudanza.
La regla que evita el desastre es simple: antes de guardar una fotocopia en el archivero nuevo, revisa si ya hay un expediente de ese registro ahí. Si ya hay uno —lo puso el reenvío, es más fresco—, no lo toques. Solo guarda las fotocopias de los expedientes que faltan en el nuevo. Eso es insert-if-absent: copias lo ausente, respetas lo presente.
Y esa misma regla te da un regalo: puedes volver a pasar por todos los expedientes cuantas veces quieras sin hacer daño. Si te interrumpen a media mudanza y al día siguiente vuelves a empezar desde el primer expediente, no importa: los que ya copiaste ya están en el nuevo, así que los saltas; solo copias los que aún faltan. Nunca duplicas un expediente ni pisas uno fresco. Esa tranquilidad de "puedo re-correrlo sin miedo" es la idempotencia, y es lo que hace manejable un backfill que tarda horas y que quizás se cae a la mitad.
Falta el detalle del orden. ¿Por qué activaste el reenvío antes de empezar a copiar el archivo, y no al revés? Porque si copiaras primero todo el archivo y después activaras el reenvío, cualquier carta que llegue en el hueco entre "terminé de copiar" y "activé el reenvío" no la agarra ninguno de los dos: el reenvío aún no estaba, y tu copia del archivo ya pasó. Activando el reenvío primero, ese hueco no existe: todo lo nuevo lo agarra el reenvío, todo lo viejo lo agarra la copia, y no queda tierra de nadie entre ambos.
Ejemplo trabajado: el backfill que no pisa lo fresco, y que es idempotente
Vamos a ejecutar las dos reglas de frente. El montaje reproduce el problema del archivero: hay tres registros históricos (dual-write apagado, solo en el viejo). El backfill toma su foto del viejo en ese momento —con ssd-1tb a 89.99—. Después, con el dual-write encendido, llega una escritura fresca: ssd-1tb baja a 84.99, y el dual-write la pone en los dos almacenes. Ahora el nuevo tiene el ssd-1tb fresco (84.99), pero la foto del backfill todavía dice 89.99. Comparamos un backfill naive (que pisa todo) contra uno seguro (insert-if-absent).
import copy
old_db = {}
new_db = {}
dual_write_on = False
def to_new_model(old_row):
return {
"id": old_row["sku"],
"title": old_row["name"],
"price_usd": old_row["price_cents"] / 100,
"in_stock": old_row["stock"] > 0,
}
def write_product(row):
old_db[row["sku"]] = row
if dual_write_on:
new_db[row["sku"]] = to_new_model(row)
# --- FASE historica: 3 filas escritas con dual_write OFF (solo en old). ---
for row in [
{"sku": "ssd-1tb", "name": "SSD 1TB", "price_cents": 8999, "stock": 12},
{"sku": "usb-c-hub", "name": "USB-C Hub", "price_cents": 3499, "stock": 40},
{"sku": "webcam", "name": "Webcam HD", "price_cents": 5999, "stock": 0},
]:
write_product(row)
# --- El backfill toma su FOTO del viejo AHORA: ssd-1tb a 89.99. ---
snapshot = copy.deepcopy(old_db)
# --- dual_write ON. Llega una escritura FRESCA: ssd-1tb baja a 84.99 (a ambos). ---
dual_write_on = True
write_product({"sku": "ssd-1tb", "name": "SSD 1TB", "price_cents": 8499, "stock": 12})
print("Estado antes del backfill:")
print(f" old['ssd-1tb'].price_cents = {old_db['ssd-1tb']['price_cents']} (fresco: 84.99)")
print(f" new['ssd-1tb'].price_usd = {new_db['ssd-1tb']['price_usd']} (fresco, via dual_write)")
print(f" snapshot['ssd-1tb'].price_cents = {snapshot['ssd-1tb']['price_cents']} (VIEJO: 89.99)")
print(f" new tiene: {sorted(new_db)} (falta lo historico: usb-c-hub, webcam)\n")
# --- Backfill NAIVE: escribe cada fila del snapshot SIN mirar si ya existe. ---
def naive_backfill(target_new, snap):
for sku, old_row in snap.items():
target_new[sku] = to_new_model(old_row) # pisa siempre
# --- Backfill SEGURO: insert-if-absent. Solo escribe lo que FALTA en el new. ---
def safe_backfill(target_new, snap):
copied = 0
for sku, old_row in snap.items():
if sku not in target_new: # no pisa lo fresco
target_new[sku] = to_new_model(old_row)
copied += 1
return copied
# Corremos cada backfill sobre una COPIA del new para comparar sin cruzarlos.
new_naive = copy.deepcopy(new_db)
new_safe = copy.deepcopy(new_db)
naive_backfill(new_naive, snapshot)
copied1 = safe_backfill(new_safe, snapshot)
copied2 = safe_backfill(new_safe, snapshot) # <- IDEMPOTENCIA: 2a corrida
print(f"{'backfill':<22}{'ssd-1tb en new':>16}{'filas en new':>14}")
print("-" * 52)
print(f"{'naive':<22}{new_naive['ssd-1tb']['price_usd']:>16}{len(new_naive):>14}")
print(f"{'seguro (1a corrida)':<22}{new_safe['ssd-1tb']['price_usd']:>16}{len(new_safe):>14}")
print("-" * 52)
print(f"\n naive -> piso el 84.99 fresco con el 89.99 del snapshot: CORRUPCION.")
print(f" seguro -> respeto el 84.99 (ya estaba) y copio lo historico. {copied1} filas.")
print(f" idempotencia -> la 2a corrida del backfill seguro copio {copied2} filas mas")
print(f" (nada que copiar: el new ya esta completo, sin duplicar).")
Qué esperar. Al correr el archivo, la salida es exactamente esta:
Estado antes del backfill:
old['ssd-1tb'].price_cents = 8499 (fresco: 84.99)
new['ssd-1tb'].price_usd = 84.99 (fresco, via dual_write)
snapshot['ssd-1tb'].price_cents = 8999 (VIEJO: 89.99)
new tiene: ['ssd-1tb'] (falta lo historico: usb-c-hub, webcam)
backfill ssd-1tb en new filas en new
----------------------------------------------------
naive 89.99 3
seguro (1a corrida) 84.99 3
----------------------------------------------------
naive -> piso el 84.99 fresco con el 89.99 del snapshot: CORRUPCION.
seguro -> respeto el 84.99 (ya estaba) y copio lo historico. 2 filas.
idempotencia -> la 2a corrida del backfill seguro copio 0 filas mas
(nada que copiar: el new ya esta completo, sin duplicar).
Lee primero el estado antes del backfill, porque ahí está el conflicto montado. El old tiene ssd-1tb a 84.99 (el valor fresco, ya actualizado). El new también tiene ssd-1tb a 84.99 (el dual-write lo puso). Pero la foto que el backfill tomó dice 89.99 —el valor viejo, de antes de la actualización—. El nuevo tiene solo ssd-1tb; le faltan las históricas usb-c-hub y webcam. Este es el escenario clásico donde un backfill puede hacer daño.
Ahora la tabla:
- El backfill naive dejó
ssd-1tben el nuevo a 89.99. Es una corrupción: pisó el valor fresco (84.99, que el dual-write había puesto correctamente) con el valor viejo de su foto (89.99). El usuario había cambiado el precio a 84.99, y el backfill lo revirtió a 89.99 sin que nadie lo pidiera. Esto es lo peor que puede hacer una migración: no perder un dato, sino ensuciar uno que estaba bien. Y es silencioso: el naive no lanzó ningún error; simplemente sobrescribió. - El backfill seguro dejó
ssd-1tba 84.99 —el valor fresco, intacto—. Porquessd-1tbya estaba en el nuevo, el insert-if-absent lo saltó: no tenía nada que insertar ahí. Y sí copió lo que faltaba: las 2 filas históricas (usb-c-hub,webcam), que no estaban en el nuevo. El resultado son 3 filas en el nuevo (1 fresca + 2 históricas), todas con el valor correcto. Copió exactamente 2 filas, las que faltaban.
Y el renglón de la idempotencia cierra la lección: la segunda corrida del backfill seguro copió 0 filas. Nada que hacer: el nuevo ya estaba completo, así que el insert-if-absent no encontró ninguna fila ausente. Correrlo otra vez —o diez veces— no cambia nada ni corrompe nada. Esa es la propiedad que te deja re-correr un backfill de horas sin miedo: si se cayó a la mitad, lo vuelves a lanzar y retoma lo que faltaba, sin pisar ni duplicar lo ya hecho.
Profundización: por qué el backfill va después del dual-write
La regla de orden —dual-write primero, backfill después— parece arbitraria hasta que dibujas la línea de tiempo y ves el hueco que aparece si la inviertes.
El orden correcto: dual-write, luego backfill.
dual_write ON backfill (copia lo historico)
│ │
────────┼─────────────────────────────────────┼──────────────▶ tiempo
│ │
toda escritura desde aqui copia todo lo anterior
cae en AMBOS (frente cubierto) (retaguardia cubierta)
└── sin hueco: el dual_write agarra
lo nuevo, el backfill lo viejo ──┘
Con este orden, cada escritura del sistema cae en al menos uno de los dos mecanismos: si ocurre después de encender el dual-write, la agarra el dual-write; si ocurrió antes, la agarra el backfill. No hay ningún instante que se le escape a los dos. (El solape —una fila que el dual-write ya puso y el backfill vuelve a ver— es seguro justamente por el insert-if-absent: el backfill la salta.)
El orden invertido: backfill, luego dual-write. El hueco.
backfill (copia lo historico) dual_write ON
│ │
────────┼─────────────────────────────────────┼──────────────▶ tiempo
│ ╲ HUECO ╱ │
copia todo lo ▼▼▼▼ desde aqui cae en ambos
anterior a aqui las escrituras en el hueco caen SOLO en old:
el backfill ya paso, el dual_write aun no esta
Si backfilleas primero y enciendes el dual-write después, las escrituras que ocurran en el hueco entre ambos momentos caen solo en el viejo: el backfill ya tomó su foto y ya pasó (no las verá), y el dual-write todavía no está encendido (no las copiará). Esas escrituras se pierden para el nuevo —silenciosamente—. En un sistema con tráfico, ese hueco puede ser de segundos o de minutos, pero en esos segundos entran ventas, cambios de precio, ajustes de stock: datos reales que el nuevo nunca tendrá, hasta que un parallel-run los delate mucho después. Encender el dual-write primero elimina el hueco de raíz.
Por qué insert-if-absent, y no "escribir si es más nuevo". Podrías pensar en una regla más lista: "copia la fila del backfill solo si es más reciente que la del nuevo". Pero eso requiere un campo de versión o timestamp confiable en cada fila, y comparar fechas entre dos modelos distintos —más complejidad y más formas de equivocarse—. El insert-if-absent es más simple y suficiente: durante el dual-write, lo que está en el nuevo siempre es al menos tan fresco como la foto del backfill (porque el dual-write escribe en tiempo real y la foto se tomó en un punto fijo del pasado). Así que "ya está en el nuevo" implica "es fresco o más fresco", y saltarlo es siempre correcto. La simplicidad no es pereza aquí: es lo que hace al backfill fácil de razonar y difícil de romper.
Idempotencia como requisito operativo, no como lujo. Un backfill sobre una base real recorre millones de filas, por lotes, durante horas. Va a fallar a la mitad alguna vez —un deploy, un reinicio, un timeout—. Si el backfill no fuera idempotente, cada caída te obligaría a razonar "¿por dónde iba?, ¿qué copié ya?, ¿qué pasa si copio de nuevo lo que ya estaba?" —un infierno operativo—. Con idempotencia, la respuesta es siempre la misma: vuélvelo a lanzar entero. Retomará lo que falte y saltará lo que ya esté. Por eso insert-if-absent no es un detalle de implementación: es lo que vuelve al backfill operable en el mundo real.
Errores comunes
El backfill que pisa las escrituras frescas. Qué pasa: el backfill copia todo el viejo al nuevo sin verificar si la fila ya está, sobrescribiendo valores que el dual-write había puesto frescos con los valores viejos de su foto —como el naive que revirtió ssd-1tb de 84.99 a 89.99—. Por qué pasa: "copiar todo" es el impulso natural, y el conflicto con las escrituras frescas es invisible en un ejemplo sin concurrencia. Cómo detectarlo: después del backfill, el parallel-run reporta discrepancias en filas que habían sido actualizadas durante la migración —el viejo tiene el valor nuevo, el nuevo tiene el valor revertido—. Peor: si el backfill corre después de que el nuevo ya recibió tráfico, puede revertir cambios que los usuarios ya veían. Cómo corregirlo: insert-if-absent, siempre. El backfill copia lo que falta, nunca pisa lo presente. Lo presente en el nuevo, durante la transición, es igual o más fresco que la foto del backfill; sobrescribirlo solo puede rancar datos.
Backfillear antes de encender el dual-write. Qué pasa: el equipo, con lógica de "primero lleno el nuevo con lo que hay, luego enciendo la sincronización", corre el backfill y después activa el dual-write —abriendo el hueco de escrituras perdidas—. Por qué pasa: intuitivamente parece más ordenado "cargar primero, sincronizar después", como llenar un tanque antes de abrir la llave. Cómo detectarlo: el parallel-run reporta como faltantes o descuadradas justo las filas que fueron escritas en la ventana entre el fin del backfill y el encendido del dual-write. Cómo corregirlo: invierte el orden. Dual-write primero (para que ninguna escritura nueva se escape), backfill después (para lo anterior). El solape es seguro por el insert-if-absent; el hueco del orden inverso no tiene arreglo salvo re-backfillear. La regla es contraintuitiva pero firme: enciende la sincronización del futuro antes de copiar el pasado.
Un backfill que no es idempotente. Qué pasa: el backfill duplica filas, o corrompe, si se corre dos veces —por ejemplo, uno que inserta incondicionalmente en vez de insertar-si-ausente, generando duplicados en la segunda pasada—. Por qué pasa: en la primera corrida "feliz" nunca se nota; el problema aparece cuando el backfill se cae a la mitad y hay que relanzarlo. Cómo detectarlo: tras una segunda corrida (o tras un reinicio a mitad), aparecen filas duplicadas o valores alterados que no estaban antes. Cómo corregirlo: diseña el backfill idempotente desde el principio —insert-if-absent, o un upsert por clave que no dependa del orden ni del número de corridas—. La prueba es concreta: correrlo dos veces seguidas debe dejar el nuevo idéntico a correrlo una vez (como en el ejemplo, donde la segunda corrida copió 0 filas). Si esa prueba no pasa, el backfill no está listo para producción, donde va a correr más de una vez.
Ejercicios
Ejercicio 1 — Reconstruye la corrupción del naive. En el ejemplo, el backfill naive dejó ssd-1tb a 89.99 en el nuevo, cuando el valor correcto era 84.99. (a) ¿De dónde salió el 89.99? (b) ¿Por qué el 84.99 correcto ya estaba en el nuevo antes de que el backfill corriera? (c) ¿Qué única línea del backfill seguro evita esta corrupción, y cómo?
Ver solución
(a) El 89.99 salió de la foto (snapshot) que el backfill tomó del viejo antes de la actualización de precio. En ese momento ssd-1tb valía 8999 centavos (89.99). El backfill naive copió esa foto vieja al nuevo, traduciéndola a 89.99, y como escribe incondicionalmente, la puso encima del valor fresco.
(b) Porque el dual-write ya estaba encendido cuando llegó la actualización a 84.99, así que esa escritura cayó en los dos almacenes —incluido el nuevo—. Cuando el backfill corrió, el nuevo ya tenía ssd-1tb a 84.99, puesto por el dual-write en tiempo real. El nuevo estaba correcto; el backfill naive lo ensució.
(c) La línea if sku not in target_new: del backfill seguro. Antes de escribir, verifica si la fila ya está en el nuevo. Como ssd-1tb ya estaba (fresco, puesto por el dual-write), la condición es falsa y el backfill lo salta, dejando intacto el 84.99. Solo inserta las filas ausentes (las históricas que faltaban). Esa sola verificación —insert-if-absent— convierte un backfill peligroso en uno seguro.
Ejercicio 2 — Dibuja el hueco. Un equipo decide backfillear primero y encender el dual-write después. Durante los 30 segundos entre "terminó el backfill" y "se encendió el dual-write", llegan 3 escrituras: un producto nuevo mouse-pro, un cambio de precio de webcam, y un ajuste de stock de ssd-1tb. (a) ¿En qué almacén(es) cae cada una? (b) ¿Cuáles de esos 3 cambios tendrá el nuevo después de encender el dual-write? (c) ¿Cómo evita el orden correcto (dual-write primero) este problema?
Ver solución
(a) Las 3 caen solo en el viejo. Durante esos 30 segundos, el backfill ya terminó (tomó su foto antes, no verá nada nuevo) y el dual-write aún no está encendido (no copia al nuevo). Así que mouse-pro, el cambio de precio de webcam y el ajuste de stock de ssd-1tb se escriben únicamente en el viejo.
(b) El nuevo tendrá ninguno de los 3, al menos no por estos mecanismos. El backfill ya pasó y no los copió (ocurrieron después de su foto). El dual-write, al encenderse, solo agarra las escrituras futuras, no las de esos 30 segundos que ya pasaron. Los 3 cambios quedan en el viejo y ausentes del nuevo —hasta que un parallel-run los detecte y una reconciliación (o un re-backfill) los recupere—. mouse-pro faltará por completo; webcam y ssd-1tb estarán descuadrados (viejo con el cambio, nuevo sin él).
(c) El orden correcto —dual-write primero, backfill después— elimina el hueco. Al encender el dual-write antes de backfillear, esas 3 escrituras (que ahora ocurrirían después del encendido) las agarra el dual-write y caen en los dos almacenes. El backfill posterior cubre solo lo anterior al encendido, y el solape con lo que el dual-write ya puso es seguro por el insert-if-absent. No queda ninguna ventana donde una escritura se escape de ambos mecanismos.
Ejercicio 3 — La prueba de idempotencia. Te dan un backfill escrito por otro equipo y quieres saber si es seguro para producción. (a) ¿Qué prueba concreta corres para verificar que es idempotente? (b) En el ejemplo, la segunda corrida del backfill seguro copió 0 filas: ¿por qué es esa la señal de idempotencia? (c) ¿Por qué importa tanto la idempotencia en un backfill real que en el ejemplo de juguete casi no se nota?
Ver solución
(a) Lo corres dos veces seguidas sobre el mismo estado y verificas que el resultado sea idéntico al de correrlo una sola vez: mismas filas, mismos valores, sin duplicados, sin cambios en la segunda pasada. Si la segunda corrida altera algo —duplica filas, cambia valores—, no es idempotente y no está listo para producción. (Una versión más exigente: córrelo, interrúmpelo a la mitad, y vuélvelo a lanzar entero; debe converger al mismo resultado.)
(b) Porque copiar 0 filas en la segunda corrida significa que el backfill no encontró nada que hacer la segunda vez: todo lo que debía estar ya estaba, y su insert-if-absent no insertó nada. Ese "no hace nada la segunda vez" es la idempotencia: correrlo de nuevo no cambia el estado. Si la segunda corrida hubiera copiado filas (o duplicado), habría cambiado el estado, y no sería idempotente.
(c) Porque en el ejemplo de juguete el backfill copia 3 filas en un instante y nunca se cae; correrlo una o dos veces da igual y el riesgo es teórico. En un backfill real recorres millones de filas, por lotes, durante horas, y va a caerse alguna vez (un deploy, un reinicio, un timeout de la base). Sin idempotencia, cada caída te obliga a averiguar exactamente por dónde ibas y a temer que relanzarlo duplique o corrompa. Con idempotencia, la recuperación es trivial: relanzas el backfill entero y retoma lo que falte, sin pisar ni duplicar lo hecho. A escala, la idempotencia es la diferencia entre un backfill operable y uno que da pánico tocar.
Resumen y siguiente paso
En esta lección cerraste el hueco histórico que el dual-write dejó abierto: el backfill. Viste, con el archivero viejo que se copia al nuevo sin pisar los expedientes frescos que ya llegaron por reenvío, que el backfill hace insert-if-absent —copia lo que falta, respeta lo presente—, y que esa única regla logra las dos cosas que importan: no sobrescribir las escrituras frescas del dual-write y ser idempotente (re-correrlo no duplica ni corrompe). Lo ejecutaste: el backfill naive revirtió ssd-1tb de 84.99 a 89.99 (corrupción silenciosa), mientras el seguro respetó el 84.99 y copió solo las 2 filas históricas faltantes; y su segunda corrida copió 0 filas, la prueba de idempotencia. Y viste, con la línea de tiempo, por qué el backfill va después de encender el dual-write: invertir el orden abre un hueco donde las escrituras se pierden para el nuevo.
Antes de avanzar deberías poder: explicar la regla insert-if-absent y cómo logra las dos reglas duras a la vez; dibujar el hueco que aparece si backfilleas antes del dual-write; distinguir un backfill idempotente de uno que no lo es y la prueba para verificarlo; y argumentar por qué pisar una escritura fresca es peor que perder una fila.
La lección 5 responde la pregunta que el dual-write y el backfill dejan pendiente: ¿de verdad quedó bien? El dual-write cubrió el frente, el backfill cubrió el histórico, y ahora afirmas que el nuevo está completo —pero afirmar no es verificar—. El parallel-run lee de ambos almacenes, los compara fila por fila, y reporta cada discrepancia: una fila que el backfill saltó, un campo que el ACL tradujo mal, una escritura que el dual-write perdió por un fallo parcial. Es la red de seguridad de toda la migración, y el equivalente exacto —sobre datos— del characterization test del módulo 2. Vas a ver un parallel_run reportar discrepancias = 2 y entender por qué, mientras ese número no sea cero, no se toca el read-switch.
Recursos
- Sam Newman, Monolith to Microservices (O'Reilly, 2019), cap. 4, "Decomposing the Database" — la sección sobre cargar los datos existentes en el almacén nuevo durante la migración y coordinar ese backfill con la sincronización en vivo. La referencia directa de esta lección. En inglés.
- Pramod Sadalage y Martin Fowler, Refactoring Databases: Evolutionary Database Design (Addison-Wesley, 2006) — los refactorings que implican mover y transformar datos existentes (como "Migrate Data") tratados como pasos idempotentes y reversibles. En inglés.
- Stripe Engineering, "Online migrations at scale" — stripe.com/blog/online-migrations. Un relato de producción del patrón completo (dual-write, backfill, comparación, corte) sobre una tabla enorme y en uso, con el énfasis en backfillear sin bloquear el tráfico vivo. El puente entre la técnica de esta lección y su ejecución a escala. En inglés.
- GitHub Engineering, "gh-ost: GitHub's online schema migration tool for MySQL" — github.blog/2016-08-01-gh-ost-github-s-online-migration-tool-for-mysql. Cómo GitHub migra esquemas de tablas grandes sin downtime, copiando datos en segundo plano mientras la tabla recibe escrituras —un backfill de producción con todas sus reglas—. En inglés.