Quali sono i modi possibili per evitare i duplicati quando non è possibile aggiungere un indice univoco


10

Sono bloccato in un problema di concorrenza.

È un problema tipico in cui l'utente invia 2 o 3 transazioni per persistere in alcuni dati che NON DOVREBBERO ESSERE duplicati nel DB, in caso di record duplicati è necessario restituire un errore.

Questo problema è semplice quando puoi semplicemente aggiungere un indice (univoco) a una colonna in cui memorizzi un hash.

Ma in questo caso, ho una tabella enorme (probabilmente milioni di record) e non posso semplicemente modificare la tabella.

In effetti, abbiamo una colonna in cui archiviamo un hash dei dati che non deve essere duplicato ma non è stato impostato un indice univoco.

Sto provando il mio codice java per verificare se esiste poco prima del flush, ricevendo ancora duplicati.

Le mie possibili soluzioni per questo sono:

  • Crea un trigger che controlla se l'hash che sto cercando di inserire esiste già nella tabella.
  • Creare un'altra tabella per memorizzare indici univoci per questa tabella e aggiungere una chiave esterna alla tabella principale.
  • Siediti in posizione fetale e piangi

La verifica dell'hash non riesce a causa di collisioni di hash o di un bug nella verifica?
candied_orange,

4
Non ho ricevuto la tua domanda. Quindi, invece di indicizzare una volta per tutte la tua enorme tabella con milioni di record, preferisci leggere per ciascuno dei prossimi milioni di record che aggiungerai, i milioni esistenti per cercare i doppi? o duplicare alcune informazioni e aggiungere join per effettuare il controllo?
Christophe,

Il problema è che, per apportare questa modifica, sono stato avvertito che abbiamo bisogno di molto spazio e di lunghi tempi di inattività per il nostro servizio, al fine di soddisfare alcuni requisiti che il nostro servizio non può essere disattivato per più di 2 ore al mese. So che il modo migliore è eseguire una manutenzione su questo tavolo, ma è qualcosa che non posso fare in questo momento, quindi abbiamo bisogno di una soluzione alternativa.
rafuru,

4
Non capisco: perché l'aggiunta di un trigger o l'aggiunta di un'altra tabella per "emulare" un indice richiedono meno tempi di inattività rispetto al semplice aggiunta di un indice alla tabella esistente?
Doc Brown,

2
@rafuru: chi ha detto che devi creare un indice univoco? Un indice standard, non univoco, probabilmente sarà tutto ciò che serve per trovare rapidamente tutte le righe con lo stesso valore hash.
Doc Brown,

Risposte:


3

Ci sono un paio di possibili scenari che sono facili da risolvere e uno pernicioso che non lo è.

Per un utente che inserisce un valore, inserisce lo stesso valore qualche tempo dopo un semplice SELECT prima che INSERT rilevi il problema. Questo funziona nel caso in cui un utente invii un valore e qualche tempo dopo un altro utente invii lo stesso valore.

Se l'utente invia un elenco di valori con duplicati, ad esempio {ABC, DEF, ABC}, in un'unica chiamata del codice l'applicazione può rilevare e filtrare i duplicati, magari generando un errore. Sarà inoltre necessario verificare che il DB non contenga alcun valore univoco prima dell'inserimento.

Lo scenario difficile è quando la scrittura di un utente si trova nel DBMS contemporaneamente alla scrittura di un altro utente e stanno scrivendo lo stesso valore. Quindi hai una gara tra loro. Poiché il DBMS è (molto probabilmente - non si dice quale si sta utilizzando) un sistema multitasking preventivo, qualsiasi attività può essere messa in pausa in qualsiasi momento della sua esecuzione. Ciò significa che l'attività di user1 può controllare che non ci sia riga esistente, quindi l'attività di user2 può verificare che non ci sia riga esistente, quindi l'attività di user1 può inserire quella riga, quindi l'attività di user2 può inserire quella riga. Ad ogni punto i compiti sono individualmente felici che stanno facendo la cosa giusta. A livello globale, tuttavia, si verifica un errore.

Di solito un DBMS lo gestirà mettendo un blocco sul valore in questione. In questo problema stai creando una nuova riga, quindi non c'è ancora nulla da bloccare. La risposta è un blocco dell'intervallo. Come suggerisce ciò, blocca un intervallo di valori, sia che esistano attualmente o meno. Una volta bloccato, tale intervallo non è accessibile da un'altra attività fino a quando il blocco non viene rilasciato. Per ottenere i blocchi di intervallo è necessario specificare e il livello di isolamento di SERIALIZABLE . Il fenomeno di un'altra attività che si intrufola di seguito dopo aver verificato l'attività è noto come record fantasma .

L'impostazione del livello di isolamento su Serializable nell'intera applicazione avrà implicazioni. Il rendimento sarà ridotto. Altre condizioni di gara che hanno funzionato abbastanza bene in passato potrebbero iniziare a mostrare errori ora. Suggerirei di impostarlo sulla connessione che esegue il codice che induce i duplicati e di lasciare il resto dell'applicazione così com'è.

Un'alternativa basata sul codice è quella di controllare dopo la scrittura piuttosto che prima. Quindi fai INSERT, quindi conta il numero di righe che hanno quel valore di hash. Se ci sono duplicati rollback dell'azione. Ciò può avere esiti perversi. Pronuncia l'attività 1, quindi l'attività 2. Quindi l'attività 1 verifica e trova un duplicato. Torna indietro anche se era il primo. Allo stesso modo entrambe le attività possono rilevare il duplicato ed entrambi i rollback. Ma almeno avrai un messaggio con cui lavorare, un meccanismo di tentativi e nessun nuovo duplicato. I rollback sono disapprovati, proprio come usare le eccezioni per controllare il flusso del programma. Nota bene che tuttoil lavoro nella transazione verrà ripristinato, non solo la scrittura che induce i duplicati. E dovrai avere transazioni esplicite che potrebbero ridurre la concorrenza. Il controllo duplicato sarà orribilmente lento a meno che tu non abbia un indice sull'hash. Se lo fai puoi anche renderlo unico!

Come hai commentato, la vera soluzione è un indice univoco. Mi sembra che questo dovrebbe adattarsi alla finestra di manutenzione (anche se ovviamente conosci meglio il tuo sistema). Supponiamo che l'hash sia di otto byte. Per cento milioni di righe equivalgono a circa 1 GB. L'esperienza suggerisce che un ragionevole pezzo di hardware elaborerebbe queste righe in un minuto o due al massimo. Il controllo e l'eliminazione duplicati si aggiungeranno a questo, ma possono essere programmati in anticipo. Questo è solo un lato, però.


2

In effetti, abbiamo una colonna in cui archiviamo un hash dei dati che non deve essere duplicato ma non è stato impostato un indice univoco.

Controllare le collisioni di hash è un buon primo passo, ma attenzione, non è possibile garantire che lo stesso programma produrrà lo stesso hash sugli stessi dati se viene riavviato . Molte funzioni hash "veloci" usano un prng integrato che viene seminato all'inizio del programma. Usa un hash crittografico se l'hash deve essere sempre lo stesso, non importa cosa, come fai in questa applicazione. Nota che non è necessario un hash crittografico valido o sicuro.

Il secondo passo è quello di verificare effettivamente l'uguaglianza dei dati, poiché anche le migliori funzioni di hash causano talvolta collisioni, poiché si sta (di solito) riducendo l'entropia dei dati.

Così:

Passaggio 1: verificare se si ottiene una collisione su un hash crittografico

Passaggio 2: se gli hash corrispondono, controlla che i dati effettivi siano gli stessi


Non riesco a vedere come questo risponda alla domanda. Supponiamo per un momento che la colonna di hash disponibile sia riempita da una funzione di hash deterministica (altrimenti qualsiasi tentativo di utilizzarla non avrebbe senso). A mio avviso, il problema è che non esiste alcun indice su quella colonna di hash nel database, quindi anche il primo passo nella risposta - verificare se c'è una collisione - richiederebbe comunque una scansione completa della tabella per ogni nuovo record su una tabella con diversi milioni di dischi, che probabilmente diventeranno troppo lenti.
Doc Brown,

È il meglio che puoi fare senza creare un indice, che è quello che la domanda stava ponendo. Una scansione hash significa almeno che devi controllare solo una colonna, il che è molto più veloce del controllo per quante colonne altrimenti dovrebbero controllare.
Turksarama,

Sono abbastanza sicuro, anche quando non è possibile creare un indice (che in questo caso probabilmente lo è), il suggerimento originale di OP di " creare un'altra tabella per memorizzare indici univoci per questa tabella e aggiungere una chiave esterna alla tabella principale " rende molto più senso.
Doc Brown,

L'hash deterministico e l'hash crittografico sono due concetti ortogonali no? un hash crittografico potrebbe non essere deterministico e viceversa un hash deterministico potrebbe benissimo non essere di forza crittografica.
Newtopiano,

Non sono la stessa cosa, ma non sono neppure ortogonali. Gli hash crittografici sono un sottoinsieme di hash deterministici, ma nessuno si preoccupa davvero di creare hash deterministici non crittografici a meno che non si desideri specificamente che sia reversibile per qualche motivo.
Turksarama,

2

Crea una nuova tabella con una chiave primaria univoca

Sul lato client iniziare a generare GUID per ciascun record in modo da poter rilevare rinvii semplici.

Inserisci i nuovi record nella nuova tabella in modo che almeno tu sia utile per i nuovi dati in arrivo.

Avere una colonna nella nuova tabella "CheckedAgainstOldData"

Avere un'attività di back-end che fa tutto ciò che è attualmente il controllo hash lento è vedere se riesce a trovare un duplicato nei vecchi dati e impostare il flag di conseguenza, respingere i duplicati a questo punto, Inviando una notifica al client.

Nel frattempo, hai un'altra attività di backend che sposta i dati dalla vecchia alla nuova tabella, controllando i duplicati con il controllo hash e generando il GUID.

È possibile lasciare questa attività in esecuzione per diversi giorni (se necessario), trasferendo i dati senza tempi di inattività.

Una volta completato il trasferimento è possibile disattivare il lento processo "CheckedAgainstOldData". e trasferire tutti i dati in una singola tabella.

Francamente però se il problema è grave come lo descrivi e il software è vecchio, allora avrai migliaia di duplicati.


1

Supponendo che i dati provenienti dall '"utente" significhino qualcuno seduto a una tastiera e che i duplicati derivino da due utenti che immettono gli stessi dati nello stesso momento. Prova ad aggiungere una funzione che provoca un ritardo casuale all'inizio del trigger. Dagli un minimo di quanto tempo ci vuole per scrivere un nuovo record sul tavolo e probabilmente un massimo di non più di un nanocento o giù di lì. In questo modo, quando ricevi richieste dupe, il primo dovrebbe essere fatto e il trigger di esistenza dovrebbe ripristinare il risultato corretto. (Chiarimento: ogni chiamata dovrebbe avere il proprio tempo di ritardo casuale unico, lungo gli stessi principi del protocollo ALOHA )

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.