Come prevenire le condizioni di gara in un'applicazione Web?


31

Prendi in considerazione un sito di e-commerce, dove Alice e Bob stanno entrambi modificando le schede dei prodotti. Alice sta migliorando le descrizioni, mentre Bob sta aggiornando i prezzi. Iniziano a modificare il widget Acme Wonder contemporaneamente. Bob finisce per primo e salva il prodotto con il nuovo prezzo. Alice impiega un po 'più di tempo per aggiornare la descrizione e, quando termina, salva il prodotto con la sua nuova descrizione. Sfortunatamente, sovrascrive anche il prezzo con il vecchio prezzo, che non era previsto.

Nella mia esperienza, questi problemi sono estremamente comuni nelle app Web. Alcuni software (ad es. Software wiki) hanno una protezione contro questo - di solito il secondo salvataggio fallisce con "la pagina è stata aggiornata durante la modifica". Ma la maggior parte dei siti Web non ha questa protezione.

Vale la pena notare che i metodi del controller sono di per sé thread-safe. Di solito usano transazioni di database, che le rendono sicure nel senso che se Alice e Bob provano a salvare nello stesso preciso istante, non causeranno corruzione. Le condizioni di gara derivano da Alice o Bob con dati non aggiornati nel loro browser.

Come possiamo prevenire tali condizioni di gara? In particolare, vorrei sapere:

  • Quali tecniche possono essere utilizzate? ad es. monitoraggio dell'ora dell'ultima modifica. Quali sono i pro ed i contro di ognuno.
  • Che cos'è un'esperienza utente utile?
  • In quali framework è integrata questa protezione?

Hai già dato la risposta: monitorando la data di modifica degli oggetti e confrontandola con l'età dei dati che altre modifiche tentano di aggiornare. Vuoi sapere qualcos'altro, ad esempio come farlo in modo efficiente?
Kilian Foth,

@KilianFoth - Ho aggiunto alcune informazioni su ciò che vorrei particolarmente sapere
paj28

1
La tua domanda non è in alcun modo speciale per le applicazioni web, le applicazioni desktop possono avere esattamente lo stesso problema. Le strategie tipiche della soluzione sono descritte qui: stackoverflow.com/questions/129329/…
Doc Brown,

2
Cordiali saluti, la forma di blocco menzionata nella domanda è nota come " controllo della concorrenza ottimista "
TehShrike

Alcune discussioni relative a Django qui
paj28,

Risposte:


17

Devi "leggere le tue scritture", il che significa che prima di annotare una modifica, devi rileggere il record e verificare se sono state apportate modifiche dall'ultima lettura. È possibile eseguire questa operazione campo per campo (a grana fine) o in base a un timestamp (a grana grossa). Mentre esegui questo controllo, hai bisogno di un blocco esclusivo sul record. Se non sono state apportate modifiche, è possibile annotare le modifiche e rilasciare il blocco. Se nel frattempo il record è cambiato, si interrompe la transazione, si rilascia il blocco e si avvisa l'utente.


Sembra l'approccio più pratico. Conosci qualche framework che lo implementa? Penso che il problema più grande con questo schema sia che un semplice messaggio "modifica conflitto" frustrerà gli utenti, ma è difficile tentare di unire i changeset (manualmente o automaticamente).
paj28,

Sfortunatamente, non conosco alcun framework che supporti questo out of the box. Non credo che un messaggio di errore di modifica confuso verrà percepito come frustrante, purché non si verifichi spesso. In definitiva, dipende dal carico dell'utente del sistema, indipendentemente dal fatto che sia sufficiente controllare il timestamp o implementare una funzione di fusione più complessa.
Phil,

Ho mantenuto un prodotto di database distribuito su PC che utilizzava un approccio a grana fine (rispetto alla sua copia del database locale): se un utente ha cambiato il prezzo e l'altro ha cambiato la descrizione - nessun problema! Proprio come nella vita reale. Se due utenti cambiano il prezzo, il secondo utente riceve le scuse e prova a cambiare nuovamente. Nessun problema! Ciò non richiede blocchi tranne nel momento in cui i dati vengono scritti nel database. Non importa se un utente va a pranzo mentre il suo cambiamento è sullo schermo e lo invia in seguito. Per le modifiche al database remoto, era basato sui timestamp dei record.

1
Dataflex aveva una funzione chiamata "rilettura ()" che fa ciò che descrivi. Nelle versioni successive, era sicuro in un ambiente multiutente. E, in effetti, era l'unico modo per far funzionare tali aggiornamenti interfogliati.

puoi fare un esempio di come farlo con sql server? \
l --''''''---------------- '' '' '' '' '' '' '23

10

Ho visto 2 modi principali:

  1. Aggiungi il timestamp dell'ultimo aggiornamento della pagina che l'utente sta modificando in un input nascosto. Quando si commette il timestamp viene verificato rispetto a quello corrente e se non corrispondono è stato aggiornato da qualcun altro e restituisce un errore.

    • pro: più utenti possono modificare diverse parti della pagina. La pagina di errore può portare a una pagina diff in cui il secondo utente può unire le sue modifiche nella nuova pagina.

    • con: a volte gran parte dello sforzo viene sprecato durante grandi modifiche simultanee.

  2. Quando un utente inizia a modificare il blocco della pagina per un periodo di tempo ragionevole, quando un altro utente tenta di modificare, riceve una pagina di errore e deve attendere fino alla scadenza del blocco o al primo utente.

    • pro: gli sforzi di modifica non vanno persi.

    • con: un utente senza scrupoli può bloccare una pagina indefinitamente. Una pagina con un lucchetto scaduto potrebbe essere ancora in grado di eseguire il commit, a meno che non venga trattato diversamente (utilizzando la tecnica 1)


7

Utilizza il controllo della concorrenza ottimistico .

Aggiungi una colonna versionNumber o versionTimestamp alla tabella in questione (intero è il più sicuro).

L'utente 1 legge il record:

{id:1, status:unimportant, version:5}

L'utente 2 legge il record:

{id:1, status:unimportant, version:5}

L'utente 1 salva il record, questo aumenta la versione:

save {id:1, status:important, version:5}
new value {id:1, status:important, version:6}

L'utente 2 tenta di salvare il record letto:

save {id:1, status:unimportant, version:5}
ERROR

Hibernate / JPA può farlo automaticamente con l' @Versionannotazione

È necessario mantenere lo stato del record di lettura da qualche parte, generalmente in sessione (questo è più sicuro che in una variabile di forma nascosta).


Grazie ... particolarmente utile per conoscere @Version. Una domanda: perché è sicuro memorizzare lo stato nella sessione? In tal caso, sarei preoccupato che l'uso del pulsante Indietro potrebbe confondere le cose.
paj28,

La sessione è più sicura di un elemento di modulo nascosto poiché l'utente non sarebbe in grado di modificare il valore della versione. Se questo non è un problema, ignora la parte relativa alla sessione
Neil McGuigan,

Questa tecnica è chiamata blocco in linea ottimista ed è in SQLAlchemy così
paj28

@ paj28 - quel link per SQLAlchemynon punta a nulla sui blocchi ottimistici offline e non lo trovo nei documenti. Hai avuto un link più utile o hai semplicemente indicato persone su SQLAlchemy in generale?
Dwanderson,

@dwanderson intendevo la parte contatore della versione di quel link.
paj28,

1

Alcuni sistemi ORM (Object Relational Mapping) rileveranno quali campi di un oggetto sono cambiati da quando sono stati caricati dal database e costruiranno l'istruzione di aggiornamento SQL per impostare solo quei valori. ActiveRecord per Ruby on Rails è uno di questi ORM.

L'effetto netto è che i campi che l'utente non ha modificato non sono inclusi nel comando UPDATE inviato al database. Le persone che aggiornano campi diversi contemporaneamente non sovrascrivono le modifiche degli altri.

A seconda del linguaggio di programmazione che stai utilizzando, cerca quali ORM sono disponibili e verifica se uno di essi aggiorna solo le colonne del database contrassegnate come "sporche" nella tua applicazione.


Ciao Greg Sfortunatamente, questo non aiuta con questo tipo di condizioni di gara. Se consideri il mio esempio originale, quando Alice salva, l'ORM vedrà la colonna dei prezzi come sporca e la aggiornerà, anche se l'aggiornamento non è desiderato.
paj28,

1
@ paj28 Il punto chiave nella risposta di Greg è " campi che l'utente non ha modificato ". Alice non ha modificato il prezzo, quindi l'ORM non ha tentato di salvare il valore "prezzo" nel database.
Ross Patterson,

@RossPatterson: in che modo l'ORM conosce la differenza tra i campi modificati dall'utente e i dati obsoleti dal browser? Non lo fa, almeno senza fare alcun tracciamento extra. Se desideri modificare la risposta di Greg per includere tale tracciamento o inviare un'altra risposta, ciò sarebbe utile.
paj28,

@ paj28 alcune parti del sistema devono sapere cosa ha fatto l'utente e memorizzare solo le modifiche apportate dall'utente. Se l'utente ha modificato il prezzo, quindi lo ha modificato, quindi inviato, ciò non dovrebbe essere considerato come "qualcosa che l'utente ha modificato", perché non lo ha fatto. Se disponi di un sistema che richiede questo livello di controllo della concorrenza, devi costruirlo in questo modo. Altrimenti no.

@nocomprende - Alcune parti, certo - ma non l'ORM come dice questa risposta
paj28
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.