From Nets developer day 2011: A talk on often-made mistakes that cause data loss, duplication and inconsistency when something goes wrong. And how to avoid it.
2. Advarsel
Dette foredraget inneholder en lang rekke feil jeg har sett i
produksjon eller på god vei til å bli produksjonssatt de siste
6 månedene.
Der hvor eksemplene er tatt fra andres kode har jeg som
regel gjort samme feil selv tidligere.
Det er all grunn til å tro at disse feilene er ganske vanlige.
2
3. Units of Work for å unngå grums
• Å dele input / oppgaver inn i behandlingsenheter
• Som enten blir behandlet og lagret i sin helhet
• Eller feiler uten å etterlate noen spor
• Så de enkelt kan prøves på nytt etter en feil
• Og som aldri ”mistes” eller dupliseres
3
4. 1. Databasetransaksjoner
• Databaseverdenens hyppigst misforståtte funksjon?
• NB: Alle eksemplene her er like relevante for filsystem-transaksjoner
(vanlig i batch-systemer).
• f.eks. med apache-commons-transactions.
4
5. Ikke gjør som i læreboka:
• Fra: slides til ch 6, ”Software Architecture”,
Ian Sommerville ”Software Engineering” 9th
ed
6. Eksempel på hva som skjer da
Korreksjonssystemet
Controller Service
DAO
(tabl md)
”godkjenn
bestilling”
”reverser”
”korriger”
”store(new Trans)”
”store(new Trans)”
”begin tx”
”insert”
”commit”
”begin tx”
”insert”
”commit”
7. Feil nr 1: hva er transaksjoner?
• IKKE: et verktøy til å få noe lagret i databasen.
• MEN: et verktøy for å hindre at noe blir lagret i
databasen...
8. Feil nr 2: bruksområde for transaksjoner
• ... Slik at du kan prøve hele operasjonen på nytt
• dersom et midlertidig/korrigerbart problem forhindrer den i
å lykkes ved første forsøk.
• En transaksjon må dermed startes og avsluttes der input
kommer inn i systemet
• Web-controller
• Message Endpoint
• En Fil
• Dette er ikke default for nesten noen web-rammeverk
9. Sånn skal det være
Transaksjonsgrense-admin
Controllere
Servicer
Domene
Database / TransasjonsMgr /
infrastruktur-ressurser
”GET index.do”
melding
fil
10. Spring: ingen @RequestMapping
uten @Transactional
• Ikke sånn:
• Men sånn:
1 @RequestMapping(value = "/verifisering.do", method = RequestMethod.POST)
2 public String postVerifisering(@ModelAttribute("command")
3 @Valid VerifiseringCommand command,
4 BindingResult errors, Map<String, Object> model,
5 HttpServletRequest request)
6 {
7 if (errors.hasErrors()) {...
1 @RequestMapping(value = "/verifisering.do", method = RequestMethod.PO
2 @Transactional(readOnly = false, rollbackFor = {Throwable.class})
3 public String postVerifisering(@ModelAttribute("command")
4 @Valid VerifiseringCommand command
5 BindingResult errors, Map<String, Obje
6 HttpServletRequest request)
7 {
8 if (errors.hasErrors()) {...
11. Unit of In/Output
• Ikke la data være synlig før de er klar!
• Sørg for at ”unit of output” = ”unit of input” til neste system.
• Alt skal være med
(selv i feilsituasjoner)
• Vær helt sikker på at det må være kommet fram
(selv i feilsituasjoner)
• Men unngå å duplisere
(selv i feilsituasjoner)
12. Ikke gjør data synlig før de er komplette
Program A Fil .../delt/katalog Program B
scan
no files
create file
create
write
write
write
One file: Fil
scan
Process (Fil)
MoveTo(processing dir)write
I/O error
Failed:
Set op-status
”retry later”
13. Bruk heller atomisk gjør-synlig-operasjon: move
Program A Fil .../ delt / katalog/ Program B
scan
no files
create file
create
write
close
moveTo(../Fil)
One file: Fil
scan
Process (F
../ katalog / tmp/
addLink( Fil )unlink( Fil )
write
14. Ikke send noe ut før du vet det er uunngåelig
14
Anvisningsystem
Print-
systemerDB
Delt
katalog
Tx: begin
Select:
”klar til print” Lag ny fil
Append, append,…
Close, move ..
Update, update:
”printet” + detaljer
exception
Tx: rollback
Task: retry later
Poll
No files
Poll
New File: process
15. Oppdatere og lagre alt selv først, deretter sende ut
15
Anvisningsystem
Print-
systemerDB
Delt
katalog
Lokale
filer
Plukk til print
Oversend printfil
Tx: begin
select
skriv lokal fil
Update, update
New task
Insert task
Task: complete
Tx: commit
Tx: begin
Tx: commit / rollback
Task: complete / retry
Oversend fil Flytt/kopier
poll
nothing
poll
nothing
poll
New File!
16. Å skille ut til egen Unit-of-Work
• Utføres i bakgrunnen / seinere
• Enklest hvis du ”sender meldinger” til deg selv med oppgaver som
skal utføres senere i egen transaksjon.
• En melding sendt er en del av din unit-of-work
• rollback => undo send
• Meldinger sendt må sync’es med databasetransaksjonen:
• Lettest hvis meldingssystem er basert på meldingstabell i
databasen: send = insert, styres av rollback
• Meldingssystem må ha re-try. En oppgave vil feile før eller senere.
17. Nettverk 1: Du vet aldri om et svar
vil komme fram, eller bli mottatt
Korreksjons-
system
Bestillings-
system
Hent nye bestillte korreksjoner
2. GET /bestilling/nyekorreksjoner
3.Select
(type:korreksjon
status:new)
4.update
(status:hentet)
5.commit
6.return bestilling
bestilling
bestilling
1.Begintx
7.Insert,
insert,....
8.commit
18. 2: Gjør bare endringer ett sted av gangen
Korreksjonsys Bestillingsys
Hent nye bestillte korreksjoner
Tx: begin
GET /bestilling/nyekorreksjoner
select ”korreksjon, ny”
4.return bestilling
bestilling
bestilling
Insert bestilt korreksjon
POST status = mottatt
Update, commit
Insert bestilt korreksjon
POST status = mottatt
HVA NÅ?
Commit eller rollback?
trøbbel
19. 3: Du er ikke ferdig før du har lagret (committet)
Korreksjonsys Bestillingsys
Hent nye bestillte korreksjoner
Tx: begin
GET /bestilling/nyekorreksjoner
select ”korreksjon, ny”
bestilling
Insert bestilt korreksjon
POST status = mottatt
Update, commit
Insert new msg
commit
Tx: begin
Update msg: done!
commit
20. Kandidater til egen UoW
• All sending av data ut av transaksjonskonteksten
• Til andre systemer
• Til filsystem
• Til mailsystem, sms,…
• Beskytt hovedflyten, skill ut til i egen UoW
• Underordnede, tidkrevende ting som kan utsettes
• Risikable ting som lett feiler
• Alt med navn, adresser og kontoeierskap fra BKAD .....
• Data-enheter som skal kunne kræsje/feile uten å forsinke andre
kunders data.
• Unit-of-Work = punkt for ”rett-feilen-og-prøv-igjen”.
• Du trenger noen steder hvor du kan si ”prøv igjen herfra nå”
21. 4: Operasjonen kan være utført allerede!
Avsender Mottaker
Tx: begin
Send update / melding med data
commit
Process(msg)
Gen. response
Store/update (data)
OK
msg: done!
TrySend
begin
Send fil / melding med data
{ client: abc ; msg: 123 } Process(msg)
Store/update (data)
Retry
23. Retry-safe operasjon
• Ikke foreta endring: Opprett en kjent måltilstand
• x/ fil fra «meg» med navn «abc» og lengde «123» finnes.
• Sjekk først om tilstanden alt er oppnådd
• Eller sjekk på egen feilkode for «duplisert operasjon», om det
finnes
• Men husk: filer flyttes av mottakeren når de er lest,
• legg igjen en logg-fil også, i en egen logg-katalog
• At du har sendt noe er IKKE det samme som at det er mottatt.
• Det er ikke mottatt før det er bekreftet mottatt og lagret av
mottaker!
• Sjekk det selv, for å være sikker!
23
24. Competing consumers
(med dagens feil)
Uavhengig
prosess
Delt lager
Uavhengig
prosess 2
Nye data?
Ny! Min!
Behandle,
behandle,
behandle
Flytt til ”ferdig”
Nye data?Finn og
behandle
Finn og
behandle
Ny! Min!
Behandle,
behandle,
behandle
Flytt til ”ferdig”
25. Competing consumers med reservasjon
Uavhengig
prosess
Delt lager
Uavhengig
prosess 2
Nye data?
Nye data?Reserver ny Reserver ny
Flytt til (unikt navn)
Flytt til ”ferdig”
Sjekk at OKFlytt til (unikt navn)
Sjekk at OK
Behandle
reservertBehandle,
behandle
26. One mistake to rule them all ....
• GLEMTE:
HVA HVIS AKKURAT DETTE GÅR BRA, MEN
DET SKJER FEIL LITT LENGER NED?
• Hvordan kjører vi operasjonen på nytt
etter at feilen er retta?
• Er det trygt?
• Hva må til for at det skal være trygt?
27. Oppsummering
• Tenk ALLTID:
• Hva hvis det feiler litt lenger nedi her?
• Da må vi kunne prøve det på nytt, uten å introdusere feil
• Foreta bare endringer/oppdateringer ETT STED av gangen.
• Ikke send noe videre før egen behandling er lagret (dvs committet)
• Gjør data synlig med atomiske handlinger, først når de er komplette.
• Du vil aldri kunne vite om kommunikasjonen gikk ned før eller etter at det andre
systemet mottok/behandlet din request (response).
• Kommunikasjon må alltid kunne kjøres på nytt, uten at du modifiserer egne data på nytt
• Kommunikasjon bør automatisk kjøres på nytt helt til mottakeren bekrefter at det er
mottatt OG du har notert dette og lagret.
• Kommunikasjon bør opprette en kjent, fast måltilstand
(f.eks. Fil [navn: X, lengde: Y, mottattFra: <meg>] finnes) .
• Lurt å starte med å sjekke om den tilstanden allerede er oppnådd
Editor's Notes
Oppdatere forretningrelaterte data bare i ett system av gangen. Endringer to steder kan ikke gjøres synlig atomisk Dette forhindrer ikke at en nettverksfeil e.l. i den siste committen kan forårsake dobbelt overføring nettopp pga manglende oppdatering av database etter oppdatering av filsystem. Mer om det siden.
[sekvensdiagram med henting av flere bestillinger; oppdatering etter mottak, nettverksfeil for 3. oppdatering og spørsmålet ”skal vi committe eller rulle tilbake nå?]”
[sekvensdiagram: mottak av en bestilling, der insert går bra, oppdatering i bestillingssystem går bra, men commit feiler på nettverksfeil]
[sekvensdiagram: mottak av en bestilling, der insert går bra, oppdatering i bestillingssystem går bra, men commit feiler på nettverksfeil]