2011-10-12 15 views
8

Sto facendo API su HTTP che recupera grandi file da PostgreSQL con paginazione. In casi ordinari, di solito implemento paginazioni come questa attraverso la clausola naive OFFET/LIMIT. Tuttavia, ci sono alcuni requisiti speciali in questo caso:Il modo migliore per recuperare la lista continua con PostgreSQL nel web

  • Un sacco di righe ci sono, ma credo che gli utenti non possono raggiungere la fine (immagina la timeline di Twitter).
  • Le pagine non devono essere accessibili in modo casuale ma in sequenza.
  • API restituirebbe un URL che contiene un token del cursore che indirizza alla pagina di blocchi continui.
  • I token del cursore non devono esistere in modo permanente ma per qualche tempo.
  • Il suo ordinamento ha frequenti fluttuazioni (come le classifiche Reddit), tuttavia i cursori continui dovrebbero mantenere il loro ordinamento coerente.

Come posso raggiungere la missione? Sono pronto a cambiare il mio intero schema di database per questo!

+0

Solo per essere sicuro di ciò che stai chiedendo. Stai dicendo un sacco di righe o file veramente larghe, o entrambi? – Kuberchaun

+0

@ StarShip3000 Grazie. Un sacco di righe. – minhee

risposta

6

Supponendo che sia solo l'ordinamento dei risultati che fluttua e non i dati nelle righe, la risposta di Fredrik ha senso. Tuttavia, suggerirei le seguenti aggiunte:

  • memorizzare l'elenco ID in una tabella di PostgreSQL utilizzando il tipo array piuttosto che nella memoria. Farlo in memoria, a meno che non si utilizzi con cautela qualcosa come redis con scadenza automatica e limiti di memoria, si sta preparando per un attacco di consumo di memoria DOS.Immagino che sarebbe simile a questa:

    create table foo_paging_cursor (
        cursor_token ..., -- probably a uuid is best or timestamp (see below) 
        result_ids integer[], -- or text[] if you have non-integer ids 
        expiry_time TIMESTAMP 
    ); 
    
  • È necessario decidere se il cursor_token e result_ids possono essere condivisi tra gli utenti per ridurre le esigenze di storage e il tempo necessario per eseguire la query iniziale per utente. Se possono essere condivisi, scegliere una finestra della cache, ad esempio 1 o 5 minuti, quindi su una nuova richiesta creare il cache_token per quel periodo di tempo, quindi verificare se gli ID dei risultati sono già stati calcolati per quel token. In caso contrario, aggiungi una nuova riga per quel token. Probabilmente dovresti aggiungere un blocco attorno al codice di controllo/inserimento per gestire le richieste concorrenti di un nuovo token.

  • Avere un processo in background pianificato che cancella vecchi token/risultati e assicurarsi che il codice client in grado di gestire eventuali errori relativi a token scaduti/non validi.

Non prendere nemmeno in considerazione l'utilizzo di cursori db reali per questo.

Mantenere gli ID risultato negli elenchi di Redis è un altro modo per gestirli (consultare il comando LRANGE), ma fare attenzione alla scadenza e all'utilizzo della memoria se si passa a tale percorso. La tua chiave Redis sarebbe il cursor_token e gli id ​​sarebbero i membri della lista.

+0

Ancora meglio, rendi questa una ** tabella provvisoria **. Più veloce, meno carico del disco. Non c'è bisogno di preoccuparsi dell'attacco DOS, le tabelle temporanee possono usare solo RAM limitata (leggi su ['temp_buffers' nel manuale] (http://www.postgresql.org/docs/9.1/interactive/runtime-config-resource.html # RUNTIME-CONFIG-RESOURCE-MEMORY) e vengono scritti sul disco quando la RAM non è sufficiente –

+1

Le tabelle temporanee sono di sessione locale e vengono cancellate al termine della sessione. Pertanto, ciò non funzionerebbe con il pool di connessioni db o il caso in cui l'http l'endpoint api è distribuito su più nodi e utilizza connessioni diverse, oltre a causare problemi quando si riavvia un server delle applicazioni e si deve ricollegare al db. Detto questo, si ottengono gli stessi vantaggi mettendo la tabella in una memoria (tramite tmpfs) Vedi http://magazine.redhat.com/2007/12/12/tip-from-an-rhce-memory-storage-on-postgresql/ –

+0

Grazie per il vostro consiglio Ho deciso di usare memcached e store ID separati da virgola in chiavi (che sono i token del cursore) con tempo di scadenza. Grazie ou! – minhee

1

so assolutamente nulla su PostgreSQL, ma io sono uno sviluppatore abbastanza decente SQL Server, quindi mi piacerebbe prendere un colpo a questo comunque :)

Quante righe/pagine ti aspetti un utente potrebbe navigare al massimo per sessione? Ad esempio, se ti aspetti che un utente sfogli un massimo di 10 pagine per ogni sessione [ogni pagina contenente 50 righe], puoi fare il massimo e configurare il webservice in modo che quando l'utente richiede la prima pagina, la cache 10 * 50 righe (o solo l'Id: s per le righe, dipende da quanta memoria/utenti simultanei hai).

Questo sicuramente contribuirebbe ad accelerare il tuo webservice, in più di un modo. Ed è abbastanza facile da implementare. Quindi:

  • Quando un utente richiede dati dalla pagina n. Esegui una query (completa con ordine per, unisci assegni, ecc.), Memorizza tutti gli id: s in un array (ma un massimo di 500 id). Restituisce i datarows che corrispondono a id: s nell'array nelle posizioni 0-9.
  • Quando l'utente richiede la pagina # 2-10. Restituisce i datarows che corrispondono a id: s nell'array in posizione (pagina-1) * 50 - (pagina) * 50-1.

È inoltre possibile aumentare i numeri, un array di 500 int: s occuperebbe solo 2K di memoria, ma dipende anche dalla velocità con cui si desidera la query/risposta iniziale.

Ho usato una tecnica simile su un sito web in diretta, e quando l'utente ha continuato a pagina 10, ho appena passato alle query. Immagino un'altra soluzione sarebbe quella di continuare ad espandere/riempire l'array. (Esecuzione della query di nuovo, ma escludendo già incluso id: s).

In ogni caso, spero che questo aiuti!