Ottenere scritture asincrone in tempo reale molto grandi su tabelle MySQL senza bloccarle a letture

2

Sto costruendo una webapp (frontend angolare, Groovy / Spring / Hibernate / MySQL backend) che permetterà agli utenti di competere l'uno contro l'altro su determinate attività. Ogni attività avrà 1 vincitore e 1 perdente. Voglio un sistema di classificazione degli utenti dal vivo che classifica gli utenti (dal primo all'ultimo posto) a seconda del loro record / cronologia di vincita / perdita. Il punteggio e amp; la classifica è in realtà basata sul sistema di valutazione ELO ed è molto simile al sapore dell'ELO utilizzato dalla comunità di scacchi. Lo dico solo perché il calcolo del punteggio di prestazione individuale di ciascun utente / punteggio ELO è un calcolo abbastanza complicato e non è così semplice come semplicemente sommando il numero di vittorie che hanno ottenuto finora o qualcosa di così semplice.

È anche importante ricordare che il ranking di un utente è qualcosa che deve essere memorizzato nel database e non può essere semplicemente ottenuto da: (1) l'ordinamento di tutti gli utenti in base alla valutazione del rendimento punteggio, (2) individuazione di un particolare utente nell'elenco ordinato, (3) posizionamento == posizione nell'elenco ordinato. La classifica deve essere mantenuta nel DB e aggiornata frequentemente.

Quindi questo sistema di classificazione utenti live deve:

  1. Attiva ogni volta che due utenti competono uno contro l'altro e l'attività determina un vincitore / perdente; e poi
  2. Prendi i risultati di quell'attività / competizione e applica un algoritmo matematicamente abbastanza complesso per determinare il nuovo punteggio di rendimento (punteggio complessivo) di entrambi gli utenti; e poi
  3. Aggiornare la loro classifica in alcune tabelle DB (ordinando, il punteggio di rendimento più alto è al primo posto, il punteggio di rendimento più basso è all'ultimo, ecc.). Questo processo è chiamato ri-ranking e riguarda tutti gli utenti (spostandoli su / giù).

I primi due elementi sopra riportati sono piuttosto semplici: li posso gestire facilmente nel livello backend / middleware. Poiché il ri-ranking potrebbe richiedere, diciamo, 30 - 60 secondi se abbiamo un numero elevato di utenti, probabilmente renderò la segnalazione dei risultati della competizione / attività in modo asincrono dal ri-ranking di tutti gli utenti. Significa che il backend riceve i risultati della competizione e li memorizza, quindi pubblica un messaggio a un broker che deve essere eseguita una ri-classifica. Un consumatore che ascolta quel broker reagisce quindi al messaggio attivando un nuovo ranking.

Tuttavia, il terzo elemento, che esegue il ri-ranking, è dove prevedo possibili problemi di prestazioni. Questo perché se la mia app ha centinaia di migliaia di utenti e ancora una volta ri-classifica prende, ad esempio, fino a 60 secondi per l'esecuzione, quindi ogni volta che due di questi utenti competono l'uno contro l'altro i ranking di tutti gli utenti saranno interessati e tutti gli utenti verrà spostato su / giù da un certo numero di classifiche. È inoltre del tutto possibile che 2 set di utenti competano uno contro l'altro nello stesso momento e attivano più re-rankings nello stesso momento).

In questo scenario sono preoccupato per la contesa di scrittura / blocco quando il DB aggiorna tutte le classifiche di tutti gli utenti, ma nel frattempo l'app non può essere in uno stato di attesa (in attesa di aggiornamento delle classifiche) e dovrà leggere le tabelle utente / classifica, anche se sono state scritte.

Quindi chiedo: quali trucchi posso utilizzare (struttura della tabella o ottimizzazioni, o forse trucchi di programmazione in una stored procedure, o forse qualcosa nel livello di dati JPA / Hibernate / JDBC, ecc.) in modo che una ri-classifica può avvenire in qualsiasi momento ( live classifiche utente) senza bloccare le tabelle utente / classifica? In altre parole, è perfettamente OK se la tabella delle classifiche riporta che l'utente 12345 ha un punteggio di 45 (45 ° posto) anche se è in corso una ri-classifica che, una volta completato, farà balzare l'utente 12345 fino al grado 44. Io non faccio altro non voglio bloccare / contendere.

    
posta smeeb 21.11.2017 - 18:58
fonte

2 risposte

5

Prima di indovinare che sarà lento, devi dimostrare che è lento. Questo è il fondamento dell'ottimizzazione delle prestazioni

Centinaia di migliaia di utenti, il numero di volte che il gioco conta per ciascuno avrà ben poco meno di un secondo con gli indici appropriati in atto

Costruisci una struttura fittizia, scrivi la query, pubblica alcuni benchmark e possiamo quindi dare un'occhiata a come possiamo migliorare le statistiche

    
risposta data 21.11.2017 - 19:21
fonte
1

Ci sono alcune domande sul fatto che sia necessario preoccuparsi di questo prima di avere un benchmark delle prestazioni. Sono d'accordo, ma è anche il caso che problemi di contesa come deadlock potrebbero verificarsi in base alla tua spiegazione. Cioè, questo non è solo un problema di prestazioni. Può essere difficile testare ed eseguire il debug di questo tipo di problemi. Non penso che sia una cattiva idea progettare questo per renderlo un non-problema.

Non ho familiarità con MySQL quindi non parlerò dei dettagli di blocco ma dovresti familiarizzare con livelli di isolamento del cursore . La chiave è che meno rigoroso è il livello di isolamento, minore è la contesa che si avrà ma si potrebbero anche avere problemi delicati che ne risultano. Ti consiglierei di rispettare il livello più rigoroso che puoi gestire.

Hai menzionato blocco ottimistico . Questo è qualcosa che consiglio vivamente in situazioni di alta contesa. Una cosa che dovrai determinare da sola è se il tuo algoritmo è deterministico indipendentemente dall'ordine di applicazione o se ti interessa. Il blocco ottimistico consentirà un'elevata concorrenza, quindi è necessario capire in che modo ciò potrebbe modificare i risultati.

L'approccio di base che utilizzo è aggiungere un timestamp o un numero di versione a ciascun record. Quest'ultimo è più a prova di proiettile, ma il primo funzionerà finché la risoluzione temporale sarà sufficiente. Poi quando vai ad aggiornare un record, lo leggi e poi lo aggiorna in questo modo:

UPDATE table_name
SET version = [incremented record version], ...
WHERE key = [surrogate key] and version = [current record version];

E controlla quanti record sono stati aggiornati. Se ottieni zero record aggiornati, qualcos'altro ha aggiornato il record e devi ricominciare da capo. È anche possibile su alcuni database / livelli di isolamento per il successo, ma poi il commit fallisce.

Se si eseguono questi aggiornamenti un record alla volta, si bloccherà solo ciascuno per il tempo necessario per eseguire l'aggiornamento. Probabilmente raramente (se mai) avremo collisioni ma devi assicurarti che il tuo codice ricomincerà da quel record se l'aggiornamento non aggiorna alcuna riga o il commit fallisce.

    
risposta data 21.11.2017 - 22:00
fonte