Velaquí un fallo que vin ocorrer dúas veces, en dous motores distintos. Unha exportación execútase ás 10:00 e rexistra a marca de tempo de última modificación máis alta que viu. Unha transacción que comezou ás 09:58 segue aberta, fai commit ás 10:03 e leva unha marca de tempo do momento en que comezou. A seguinte exportación só pide filas máis novas que as 10:00 e nunca a ve. O tamaño da transferencia era excelente e os datos eran incorrectos.

Percorre a exportación que perdeu unha fila

Segue a orde dos acontecementos, non o SQL. A aplicación escribe o tempo de modificación da fila cando comeza a transacción, non cando fai commit. O exportador le unha marca de auga de reloxo de parede mentres esa transacción segue aberta. Despois a transacción fai commit cun tempo de modificación anterior ao da marca gardada. O seguinte lote do consumidor comeza despois da marca, así que a fila queda detrás do cursor e nunca volverá seleccionarse.

Nada rexistra un erro, porque nada fallou. Iso é o que fai caro este tipo de fallo: o fluxo delta é rápido, os reconto de filas parecen plausibles e a fila que falta aparece semanas despois como un ticket de conciliación que ninguén pode reproducir.

Os números de secuencia teñen a mesma forma de defecto por outra razón. Un ID monotónico diche a orde en que se asignaron os valores, non a orde en que fixeron commit as transaccións. Unha transacción que tomou un número baixo pode facer commit despois dunha que tomou un alto, así que un cursor baseado na orde de asignación tamén pode deixar un oco.

Cinco promesas que o fluxo debe cumprir

Antes de escoller un transporte, pide ao sistema de orixe un contrato con cinco cláusulas. Se falta algunha, tes unha heurística e non un fluxo de cambios.

  • Cobertura: insercións, actualizacións e borrados, cada un coa clave estable necesaria para aplicalo.
  • Límite: que significa un valor de cursor e que contén un lote coherente.
  • Consistencia: como se len táboas relacionadas sen mesturar estados de orixe incompatibles.
  • Retención: canto tempo pode reanudar un consumidor desconectado desde a súa posición gardada.
  • Reinicialización: unha instantánea completa soportada para cando esa posición xa non sexa válida.

Un mecanismo soportado pode cumprir as cinco. A documentación de SQL Server sobre o seguimento de cambios é un exemplo publicado concreto: define unha versión mínima válida, rexistra os borrados ademais dos cambios e describe unha transacción de instantánea que abrangue a validación, a captura de versión e as consultas de cambios relacionadas. É un exemplo do que esixir a unha orixe, non unha sintaxe que poidas pegar noutro motor.

Deseña o punto de control como parte do pipeline

A posición gardada do consumidor é a verdadeira máquina de estados, así que escríbea como tal. O cursor só pode avanzar mediante o fluxo de traballo que xa aplicou o lote e confirmou a transacción de destino.

cursor  = durable_checkpoint()          # last fully applied position
batch   = extract(cursor, upper_bound)  # one consistent batch
apply(batch)                            # idempotent at the target
commit()
checkpoint(batch.upper_bound)           # only now

Se a entrega falla a metade de camiño, concilia ese mesmo lote en lugar de inventar unha nova posición de orixe. Reextraer cun límite superior máis recente é como unha aplicación parcial se converte nun oco permanente. A regra do espello tamén vale no outro lado: a aplicación idempotente no destino evita duplicados e non fai nada por un cambio que a extracción nunca viu.

Os borrados necesitan rexistros, non conclusións

Comparar filas que aínda existen non pode dicir a un consumidor que claves desapareceron. Unha orixe que nunca rexistra borrados déixache dúas opcións imperfectas: manter un historial de claves o tempo suficiente para comparalo, ou executar unha conciliación completa explícita segundo un calendario. Ambas son decisións de deseño cun custo, e ningunha existe se ninguén a asume.

A retención é o mesmo tipo de decisión. Todo fluxo de cambios soportado ten unha xanela; decláraa no contrato e avisa cando o punto de control dun consumidor se achegue a ela. Un consumidor que esperta tras unha interrupción de dúas semanas debería ler un erro claro "hai que reinicializar" en lugar de recibir en silencio un lote parcial.

Despois mide se a delta valía a pena

Volver comparar cada clave e hash ás veces é correcto, e paga a pena saber canto custa. Segues transferindo información de cada fila, os hash non che dan unha instantánea coherente entre táboas e un esquema de hash trae as súas propias preguntas sobre representación e colisións. Menos bytes cun límite verificable é o obxectivo; menos bytes que deixan caer en silencio un cambio confirmado non é ningunha delta.

Unha columna de última modificación é unha pista. Un cursor que sobrevive a un commit tardío, un borrado e unha interrupción longa é un contrato.

Artigos relacionados