Come progettare un database per la memorizzazione di un elenco ordinato?


42

Sto cercando di memorizzare un elenco ordinato all'interno di un database. Voglio eseguire le seguenti operazioni in modo efficiente.

  1. Inserisci (x) - Inserisci il record x nella tabella
  2. Elimina (x) - Elimina il record x dalla tabella
  3. Prima (x, n) - Restituisce i record 'n' che precedono il record x nell'elenco ordinato.
  4. Dopo (x, n) - Restituisce i record 'n' successivi al record x nell'elenco ordinato.
  5. Primo (n) - Restituisce i primi 'n' record dall'elenco ordinato.
  6. Last (n) - Restituisce gli ultimi 'n' record dall'elenco ordinato.
  7. Confronta (x, y) - Dati due record xey dalla tabella, scopri se x> y.

Il metodo semplice che mi viene in mente è quello di memorizzare una sorta di attributo 'rank' nella tabella e interrogare ordinando quell'attributo. Ma in questo metodo inserire / modificare un record con un rango diventa un'operazione costosa. C'è un metodo migliore?

In particolare, sto cercando di implementare la tabella utilizzando Amazon SimpleDB. Ma dovrebbe essere utile anche una risposta generale per un database relazionale.

Aggiornamento sul profilo di caricamento:

Dal momento che sto pianificando questo per un'applicazione Web, dipende dal numero di utenti che utilizzano l'app.

Se ci sono 100k utenti attivi (super ottimismo: P), allora la mia stima molto approssimativa al giorno sarebbe

500k seleziona, 100k inserisce ed elimina, aggiornamenti 500k

Mi aspetto che il tavolo cresca fino a 500k in totale.

Sto cercando di ottimizzare gli aggiornamenti, inserire e confrontare le operazioni. Il rango degli articoli cambierà costantemente e devo tenere aggiornato il tavolo.


Elaborare un po 'il profilo di carico previsto. Quante selezioni / inserti / aggiornamenti al giorno? Per quali operazioni desideri ottimizzare di più? Quanto ti aspetti che il tavolo cresca ogni giorno o arrivi in ​​totale?
Nick Chammas,

È per una classifica dei giocatori? Comunque, ho aggiornato la mia risposta di seguito con un feedback basato sul profilo di carico previsto.
Nick Chammas,

no non è una classifica dei giocatori.
Chitti,

Quale approccio hai usato?
Nick Chammas,

Non sono nemmeno sicuro di cosa ti venga chiesto qui o di cosa non devi fare dalla lista delle cose che devi fare.
Evan Carroll,

Risposte:


22

Se il grado non è completamente arbitrario ma è invece derivabile da altre proprietà (ad esempio nome, punteggio del giocatore, ecc.), Dai un'occhiata alla risposta di Joel .

Se si tratta di una proprietà arbitraria dei tuoi dati, dovrebbe essere archiviata come colonna nella tabella dei record. Supponendo che il SimpleDB di Amazon sia simile al tipico RDBMS, è quindi possibile indicizzare questa colonna e soddisfare rapidamente tutte le query precedenti con la strategia di indicizzazione appropriata. Questo è normale per un RDBMS.

Dato che ti aspetti un'attività di inserimento e aggiornamento elevata, ma anche un'attività di lettura relativamente alta, ti consiglio di fare quanto segue:

  • Raggruppa la classifica in classifica, specialmente se la stragrande maggioranza delle tue domande sono in contrasto con la classifica. In caso contrario, o se la scelta di una chiave di clustering non è disponibile in SimpleDB, creare semplicemente un indice con rango come colonna principale. Ciò soddisferebbe le domande 3-6.
  • Un indice sul record prima e poi in classifica (o, nel mondo di SQL Server, semplicemente registra e INCLUDE-ing rank, o semplicemente registra se sei stato raggruppato in classifica) soddisfarrebbe la query 7.
  • Le operazioni 1 e 2 possono essere ottimizzate spaziando i dati in modo appropriato (ovvero impostando FILLFACTORin SQL Server). Ciò è particolarmente importante se si raggruppa in classifica.
  • Quando si inseriscono o si aggiornano le classifiche, mantenere il più possibile uno spazio tra i numeri delle classifiche per ridurre al minimo la possibilità che sia necessario riordinare un record esistente per adattarsi a un inserimento o aggiornamento delle classifiche. Ad esempio, se classifichi i tuoi record con incrementi di 1000, lasci abbastanza spazio per circa la metà di molte modifiche e inserimenti con la minima possibilità che dovrai riordinare un record non direttamente coinvolto in tali modifiche.
  • Ogni notte ri-classifica tutti i record per ripristinare i vuoti di rango tra di loro.
  • È possibile ottimizzare la frequenza dei ri-classifiche di massa e la dimensione del gap in base al numero previsto di inserimenti o aggiornamenti relativi al numero di record esistenti. Quindi, se hai record da 100K e ti aspetti che inserimenti e aggiornamenti siano il 10% di quello, lascia abbastanza spazio per 10K nuovi ranghi e ri-classifica di notte.
  • La riclassificazione dei record da 500.000 è un'operazione costosa, ma eseguita una volta al giorno o una settimana fuori orario dovrebbe andare bene per un database del genere. Questa riclassificazione di massa fuori orario per mantenere le lacune di rango è ciò che ti fa risparmiare dover riordinare molti record per ogni aggiornamento o inserimento di rango durante le ore normali e di punta.

Se prevedete letture di oltre 100 KB su una tabella con dimensioni di oltre 100 KB, non consiglio di utilizzare l'approccio con elenco collegato. Non si adatta bene a quelle dimensioni.


I ranghi sono modificabili. Mi aspetto che i ranghi cambino costantemente e che vengano costantemente inseriti nuovi record. Sono preoccupato per il caso in cui inserisco un nuovo elemento con un rango, quindi i ranghi di tutti i record al di sotto del nuovo record in ordine devono essere modificati. Non è un'operazione costosa quando ho migliaia di record nel mio database?
chitti,

@chitti - Ah, questa è una preoccupazione. Potresti distanziare le tue classifiche (ad es. 0, 1000, 2000, 3000, ...) e riordinare periodicamente tutti i record man mano che le lacune nella classifica si riempiono. Tuttavia, ciò non si ridimensionerà se ti aspetti molto più di qualche decina di migliaia di record.
Nick Chammas,

1
@chitti - Questo è un po 'divertente, in realtà. Questo è esattamente il problema che i motori di database affrontano quando si indicizzano i dati, perché li stanno ordinando e riordinando quando i dati vengono aggiunti o modificati. Se guardi in alto FILLFACTOR, vedrai che è essenzialmente pensato per creare quello spazio extra per i record in un indice, proprio come le lacune di rango che ho descritto creano spazio per le modifiche e gli inserimenti di rango.
Nick Chammas,

2
Grazie per la risposta aggiornata. Il "rango" è una proprietà arbitraria dei miei dati. Sono quasi convinto che una colonna di indice personalizzata sia ciò di cui ho bisogno. Dai un'occhiata a questo link SO con una domanda simile. La risposta in alto fornisce consigli su come gestire una tale colonna di rango.
Chitti,

@chitti - La risposta accettata a questa domanda SO è fantastica. Suggerisce lo stesso approccio che ho dettagliato qui, con l'ulteriore suggerimento di usare decimali invece di numeri interi per espandere notevolmente la tua flessibilità nell'assegnare e cambiare i ranghi. Grande scoperta.
Nick Chammas,

13

In genere utilizzo il metodo "rango" che descrivi. Invece di scherzare con l'aggiornamento delle righe quando è necessario riordinare gli articoli, sono stato spesso in grado di cavarmela eliminando tutti i record nell'elenco e reinserendo nuovi elementi nell'ordine corretto. Questo metodo è chiaramente ottimizzato per il recupero.

Un approccio alternativo sarebbe quello di modellare i record come un elenco collegato utilizzando una colonna di chiave esterna riflessa "precedente" sulla tabella:

ID   setID   item       predecessor
---  ------  ------     ------------
1    1       Apple      null
2    1       Orange     1
3    2       Cucumber   null
4    1       Pear       2
5    1       Grape      4
6    2       Carrot     3

Puoi facilmente recuperare un elenco e aggiungere e rimuovere elementi con un piccolo sovraccarico, ma ottenere i record nell'ordine corretto sarà complicato. Forse c'è un modo intelligente per farlo in una singola query, probabilmente con molti join di tabelle con alias.

Uso quest'ultimo approccio spesso quando modello una relazione stile albero (categorie, cartelle, set e sottoinsiemi). In genere ho avuto una funzione ricorsiva di qualche tipo per ricostruire l'intero albero nella mia applicazione.


2
Il modello dell'elenco collegato è pulito. Per recuperare una tale gerarchia in ordine in SQL Server è necessario utilizzare un CTE ricorsivo .
Nick Chammas,

Costruire quella gerarchia sarebbe piuttosto costoso per un tavolo alto, però. Il vantaggio è che le modifiche / inserimenti / ecc. Possono essere facilmente apportate. A seconda del profilo di carico previsto di Chitti, questo potrebbe effettivamente essere l'approccio migliore.
Nick Chammas,

L'opzione dell'elenco collegato sembra l'idea migliore per tutte le operazioni tranne Confronta. Qualche idea su come implementare Compare senza dover tracciare il percorso tra i due elementi da confrontare?
chitti,

Se hai gli ID degli articoli, penso che Compare () sarebbe semplice, a meno che non abbia frainteso ciò che intendevi per Compare (). Quando hai detto: "trova se x> y" intendevi "trova se x precede y"? Non riesco a vedere che è facile senza un indice personalizzato o una procedura memorizzata che potrebbe essere utilizzata per l'elenco (o quell'interessante funzionalità CTE menzionata da @Nick).
bpanulla,

5
Questo tipo di soluzione approssima anche un modello di dati grafici ( en.wikipedia.org/wiki/Graph_theory ). Un sistema di archiviazione ottimizzato per l'archiviazione di nodi e bordi del grafico potrebbe essere una soluzione migliore di un RDBMS. I negozi triple e quad e i database grafici come Neo4J sono abbastanza bravi in ​​questo.
bpanulla,

6

Penserei che la cosa da fare sia memorizzare la proprietà o le proprietà che vengono utilizzate per calcolare il grado e quindi costruire un indice su di essi. Invece di provare a forzare il database a archiviare fisicamente i dati in ordine classificato o utilizzando un elenco collegato gestito manualmente, perché non lasciare che il motore di database faccia ciò per cui è stato progettato?


2
Cosa succede se le "proprietà utilizzate per calcolare il rango" sono arbitrarie? Ad esempio: una serie di voci del carrello che vengono riordinate in base alle azioni arbitrarie dell'utente.
Chitti,

Quando dici che il grado è arbitrario, cosa intendi? Deve esserci un algoritmo che usi per calcolare quale dovrebbe essere il grado. Ad esempio: "basato sulle voci del carrello" - Basato su come? Deve esserci qualcosa memorizzato nel database che è il driver per il calcolo del rango. Può essere una combinazione di più cose, ma queste cose devono in qualche modo essere archiviate nella tabella del cliente o nelle tabelle relative al cliente. Se si trova nei dati, è possibile creare una funzione che li calcola. Se riesci a calcolarlo, puoi memorizzarlo e indicizzarlo.
Joel Brown,

Diciamo che dobbiamo mantenere l'ordine degli articoli in un carrello e l'ordine può essere 'arbitrariamente' modificato dall'utente usando un interfaccia utente web. Come memorizzeresti un tale elenco di elementi in un database e come manterresti l'ordinamento?
chitti,

Se ti capisco correttamente, "cambiando arbitrariamente" l'ordine degli articoli in un carrello, intendi che l'utente può trascinare gli articoli su e giù in un elenco e rilasciarli dove vogliono. Immagino che mi sembri un po 'forzato. Perché gli utenti dovrebbero farlo? Se potessero farlo, lo farebbero molto? L'utilizzo di una semplice sequenza di articoli all'interno di un carrello è davvero tanto preoccupante per le prestazioni? Mi sembra che un numero progressivo da uno al numero di articoli nel carrello + l'FK all'ordine ti darebbe l'indice di cui hai bisogno. Basta aggiornare gli elementi quando uno viene trascinato in giro.
Joel Brown,

3
Il carrello della spesa è solo un esempio che ho fatto per dimostrare che ci sono casi in cui il "rango" può essere arbitrario. Potrebbe non essere stato un grande esempio. La coda del dvd di netflix può essere un esempio migliore. Solo per ragioni di argomento, immagina una coda netflix con 100k articoli che possono essere arbitrariamente riordinati dall'utente e lo fa ogni minuto. Come progettereste un database per memorizzare l'elenco ordinato di film in questa ipotetica applicazione?
chitti,

1

Queste sono le limitazioni di un non RDBMS come simpleDB. Le funzioni richieste non possono essere implementate sul lato DB in simpleDB, devono essere implementate dal lato / applicazione di programmazione.

Per un RDBMS come SQL server, le funzionalità richieste sono rudimentali all'indice cluster.

  • Inserisci (x) - Inserisci il record x nella tabella> Inserisci semplice.
  • Elimina (x) - Elimina il record x dalla tabella> Elimina semplice.
  • Prima (x, n) - Restituisce i record 'n' che precedono il record x nell'elenco ordinato. > Seleziona i primi n risultati in cui x è inferiore al valore e ordina per clausola.

  • Dopo (x, n) - Restituisce i record 'n' successivi al record x nell'elenco ordinato. > Seleziona i primi n risultati dove x maggiore del valore e ordina per clausola.

  • Primo (n) - Restituisce i primi 'n' record dall'elenco ordinato. > Seleziona i primi n risultati.

  • Last (n) - Restituisce gli ultimi 'n' record dall'elenco ordinato. > Seleziona i primi n risultati dopo l'ordine per descrizione.

  • Confronta (x, y) - Dati due record xey dalla tabella, scopri se x> y. > Dichiarazione TSQL IF.

SimpleDB fornisce indici automatici, ordinamento e un linguaggio di query di base . Il mio problema rimarrà anche se scelgo un RDBMS. Il problema è perché la classificazione dei dati nel mio database cambia in modo arbitrario e non possono essere acquisiti come una singola proprietà (a meno che non utilizzi una colonna di classificazione personalizzata) che può essere indicizzata.
Chitti,

0

Ecco cosa ho usato per riordinare la mia tabella Postgres dopo ogni inserimento:

CREATE OR REPLACE FUNCTION re_rank_list() RETURNS trigger AS $re_rank_list$
DECLARE
    temprow record;
    row_idx integer := 1;    
BEGIN
    FOR temprow IN
    SELECT * FROM your_schema.your_list WHERE list_id = NEW.list_id ORDER BY rank ASC
    LOOP
        UPDATE your_schema.your_list SET rank = row_idx * 100 WHERE id = temprow.id;
        row_idx := row_idx + 1;
    END LOOP;
    RETURN NEW;
END;
$re_rank_list$ LANGUAGE plpgsql;


CREATE TRIGGER re_rank_list AFTER UPDATE ON your_schema.your_list_value
    FOR EACH ROW 
    WHEN (pg_trigger_depth() = 0)
    EXECUTE PROCEDURE re_rank_list();

Per il mio caso d'uso, le prestazioni non sono un problema, ma la fiducia che non si romperà o agirà in modo strano è importante.

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.