Här är ett fel jag har sett hända två gånger, i två olika motorer. En export körs klockan 10:00 och registrerar den högsta tidsstämpeln för senaste ändring som den såg. En transaktion som började klockan 09:58 är fortfarande öppen, committar klockan 10:03 och bär en tidsstämpel från när den startade. Nästa export ber bara om rader nyare än 10:00 och ser den aldrig. Överföringsstorleken var utmärkt och dataen var fel.

Följ exporten som tappade en rad

Följ ordningen av händelser i stället för SQL. Applikationen skriver radens ändringstid när transaktionen börjar, inte när den committar. Exportören läser ett väggklocke-vattenmärke medan transaktionen fortfarande är öppen. Sedan committar transaktionen med en ändringstid som är äldre än det sparade vattenmärket. Konsumentens nästa batch börjar efter vattenmärket, så raden ligger bakom cursorn och kommer aldrig att väljas igen.

Ingenting loggar ett fel, eftersom ingenting misslyckades. Det är det som gör den här felklassen dyr: deltaflödet är snabbt, radantalet ser rimligt ut, och den saknade raden dyker upp veckor senare som en avstämningsbiljett som ingen kan återskapa.

Sekvensnummer har samma form av fel av en annan anledning. Ett monotont ID berättar ordningen i vilken värden tilldelades, inte ordningen i vilken transaktioner committade. En transaktion som tog ett lågt nummer kan committa efter en som tog ett högt nummer, så en cursor baserad på tilldelningsordning kan också lämna ett hål.

Fem löften som flödet måste hålla

Innan du väljer transport, be källsystemet om ett kontrakt med fem klausuler. Saknas någon klausul har du en heuristik och inte ett ändringsflöde.

  • Täckning: infogningar, uppdateringar och borttagningar, var och en med den stabila nyckel som krävs för att tillämpa den.
  • Gräns: vad ett cursorvärde betyder och vad en konsekvent batch innehåller.
  • Konsistens: hur relaterade tabeller läses utan att blanda inkompatibla källtillstånd.
  • Lagring: hur länge en frånkopplad konsument kan återuppta från sin sparade position.
  • Ominitiering: en stödd fullständig ögonblicksbild för när den positionen inte längre är giltig.

En stödd mekanism kan uppfylla alla fem. SQL Servers dokumentation om ändringsspårning är ett konkret publicerat exempel: den definierar en minsta giltig version, registrerar borttagningar såväl som ändringar och beskriver en ögonblicksbildstransaktion som spänner över validering, versionsfångst och de relaterade ändringsfrågorna. Det är ett exempel på vad man bör kräva av en källa, inte syntax som du klistrar in i en annan motor.

Designa kontrollpunkten som en del av pipelinen

Konsumentens sparade position är den egentliga tillståndsmaskinen, så skriv den som en sådan. Cursorn får bara avancera genom det arbetsflöde som redan har tillämpat batchen och committat måltransaktionen.

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

Om leveransen misslyckas halvvägs, stäm av samma batch i stället för att hitta på en ny källposition. Att extrahera igen med en friskare övre gräns är vägen genom vilken en partiell tillämpning blir ett permanent hål. Spegelregeln gäller också på andra sidan: idempotent tillämpning vid målet förhindrar dubbletter och gör ingenting för en ändring som extraktionen aldrig såg.

Borttagningar kräver registreringar, inte slutsatser

Att jämföra rader som fortfarande finns kan inte berätta för en konsument vilka nycklar som försvann. En källa som aldrig registrerar borttagningar lämnar dig med två ofullkomliga alternativ: att hålla en nyckelhistorik tillräckligt länge för att diffa mot, eller att köra en uttrycklig fullständig avstämning enligt ett schema. Båda är designval med en kostnad, och inget av dem finns om ingen äger det.

Lagring är samma typ av beslut. Varje stött ändringsflöde har ett fönster; ange det i kontraktet och varna när en konsuments kontrollpunkt närmar sig det. En konsument som vaknar efter ett avbrott på två veckor bör läsa ett tydligt fel "ominitiering krävs" i stället för att tyst ta emot en partiell batch.

Mät sedan om deltan var värd det

Att falla tillbaka på att jämföra varje nyckel och hash är ibland rätt, och det är värt att veta vad det kostar. Du överför fortfarande information om varje rad, hashvärden ger dig ingen konsekvent ögonblicksbild över tabeller, och ett hashschema för med sig egna frågor om representation och kollisioner. Färre byte med en verifierbar gräns är målet; färre byte som tyst tappar en committad ändring är inte någon delta alls.

En kolumn för senaste ändring är en ledtråd. En cursor som överlever en sen commit, en borttagning och ett långt avbrott är ett kontrakt.

Relaterade artiklar