Domanda

Ho bisogno dell'opinione di un vero DBA.Postgres 8.3 impiega 200 ms per eseguire questa query sul mio Macbook Pro mentre Java e Python eseguono lo stesso calcolo in meno di 20 ms (350.000 righe):

SELECT count(id), avg(a), avg(b), avg(c), avg(d) FROM tuples;

Si tratta di un comportamento normale quando si utilizza un database SQL?

Lo schema (la tabella contiene le risposte a un sondaggio):

CREATE TABLE tuples (id integer primary key, a integer, b integer, c integer, d integer);

\copy tuples from '350,000 responses.csv' delimiter as ','

Ho scritto alcuni test in Java e Python per il contesto e schiacciano SQL (ad eccezione di Python puro):

java   1.5 threads ~ 7 ms    
java   1.5         ~ 10 ms    
python 2.5 numpy   ~ 18 ms  
python 2.5         ~ 370 ms

Anche sqlite3 è competitivo con Postgres nonostante presuppone che tutte le colonne siano stringhe (per contrasto:anche usando solo il passaggio alle colonne numeriche anziché ai numeri interi in Postgres si ottiene un rallentamento di 10 volte)

Le accordature che ho provato senza successo includono (seguendo ciecamente alcuni consigli web):

increased the shared memory available to Postgres to 256MB    
increased the working memory to 2MB
disabled connection and statement logging
used a stored procedure via CREATE FUNCTION ... LANGUAGE SQL

Quindi la mia domanda è: la mia esperienza qui è normale e questo è ciò che posso aspettarmi quando utilizzo un database SQL?Posso capire che l'ACID debba comportare dei costi, ma secondo me è un po' folle.Non sto chiedendo velocità di gioco in tempo reale, ma poiché Java può elaborare milioni di doppi in meno di 20 ms, sono un po' geloso.

Esiste un modo migliore per eseguire un OLAP semplice a basso costo (sia in termini di denaro che di complessità del server)?Ho esaminato Mondrian e Pig + Hadoop ma non sono molto entusiasta di mantenere ancora un'altra applicazione server e non sono sicuro che sarebbero d'aiuto.


No, il codice Python e il codice Java fanno tutto il lavoro internamente, per così dire.Genero semplicemente 4 array con 350.000 valori casuali ciascuno, quindi prendo la media.Non includo la generazione nei tempi, solo il passaggio della media.La tempistica dei thread Java utilizza 4 thread (uno per array in media), eccessivo ma è sicuramente il più veloce.

La temporizzazione di sqlite3 è guidata dal programma Python e viene eseguita dal disco (non :memory:)

Mi rendo conto che Postgres sta facendo molto di più dietro le quinte, ma la maggior parte di quel lavoro non mi interessa poiché si tratta di dati di sola lettura.

La query Postgres non modifica i tempi nelle esecuzioni successive.

Ho rieseguito i test Python per includere lo spooling dal disco.Il tempo rallenta considerevolmente fino a quasi 4 secondi.Ma immagino che il codice di gestione dei file di Python sia praticamente in C (anche se forse non la libreria csv?), quindi questo mi indica che nemmeno Postgres è in streaming dal disco (o che hai ragione e dovrei inchinarmi prima di chiunque abbia scritto il proprio livello di archiviazione!)

È stato utile?

Soluzione

Postgres sta facendo molto di più di quanto sembri (mantenendo la coerenza dei dati tanto per cominciare!)

Se i valori non devono essere corretti al 100% o se la tabella viene aggiornata raramente, ma esegui spesso questo calcolo, potresti voler esaminare le visualizzazioni materializzate per accelerarlo.

(Nota, non ho utilizzato visualizzazioni materializzate in Postgres, sembrano poco hacky, ma potrebbero adattarsi alla tua situazione).

Viste materializzate

Considerare anche il sovraccarico della connessione effettiva al server e il viaggio di andata e ritorno necessario per inviare la richiesta al server e viceversa.

Considererei 200 ms perché qualcosa del genere sia abbastanza buono, un test rapido sul mio server Oracle, la stessa struttura della tabella con circa 500.000 righe e senza indici, richiede circa 1 - 1,5 secondi, che è quasi tutto solo Oracle che succhia i dati fuori disco.

La vera domanda è: 200 ms sono abbastanza veloci?

-------------- Di più --------------------

Ero interessato a risolvere questo problema utilizzando visualizzazioni materializzate, poiché non ci ho mai giocato veramente.Questo è nell'oracolo.

Per prima cosa ho creato un MV che si aggiorna ogni minuto.

create materialized view mv_so_x 
build immediate 
refresh complete 
START WITH SYSDATE NEXT SYSDATE + 1/24/60
 as select count(*),avg(a),avg(b),avg(c),avg(d) from so_x;

Durante l'aggiornamento, non vengono restituite righe

SQL> select * from mv_so_x;

no rows selected

Elapsed: 00:00:00.00

Una volta aggiornato, è MOLTO più veloce rispetto alla query non elaborata

SQL> select count(*),avg(a),avg(b),avg(c),avg(d) from so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:05.74
SQL> select * from mv_so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:00.00
SQL> 

Se inseriamo nella tabella base il risultato non è immediatamente visualizzabile vista la MV.

SQL> insert into so_x values (1,2,3,4,5);

1 row created.

Elapsed: 00:00:00.00
SQL> commit;

Commit complete.

Elapsed: 00:00:00.00
SQL> select * from mv_so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:00.00
SQL> 

Ma aspetta un minuto circa e l'MV si aggiornerà dietro le quinte e il risultato verrà restituito velocemente come potresti desiderare.

SQL> /

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899460 7495.35823 22.2905352 5.00276078 2.17647059

Elapsed: 00:00:00.00
SQL> 

Questo non è l'ideale.tanto per cominciare, non è in tempo reale, gli inserti/aggiornamenti non saranno immediatamente visibili.Inoltre, hai una query in esecuzione per aggiornare l'MV se ne hai bisogno o meno (questo può essere sintonizzato su qualsiasi intervallo di tempo o su richiesta).Ma questo dimostra quanto più veloce un MV possa sembrare all'utente finale, se si riesce a convivere con valori che non sono accurati fino al secondo.

Altri suggerimenti

Direi che il tuo schema di test non è davvero utile.Per soddisfare la query db, il server db esegue diversi passaggi:

  1. analizzare l'SQL
  2. elaborare un piano di query, i.e.decidere quali indici utilizzare (se presenti), ottimizzare ecc.
  3. se viene utilizzato un indice, cerca i puntatori ai dati effettivi, quindi vai alla posizione appropriata nei dati o
  4. se non viene utilizzato alcun indice, eseguire la scansione l'intero tavolo per determinare quali righe sono necessarie
  5. caricare i dati dal disco in una posizione temporanea (si spera, ma non necessariamente, memoria)
  6. eseguire i calcoli count() e avg()

Quindi, creando un array in Python e ottenendo la media, sostanzialmente si saltano tutti questi passaggi, tranne l'ultimo.Poiché l'I/O su disco è tra le operazioni più costose che un programma deve eseguire, questo è un grave difetto nel test (vedi anche le risposte a questa domanda ho già chiesto qui).Anche se leggi i dati dal disco nell'altro test, il processo è completamente diverso ed è difficile dire quanto siano rilevanti i risultati.

Per ottenere maggiori informazioni su dove Postgres trascorre il suo tempo, suggerirei i seguenti test:

  • Confronta il tempo di esecuzione della tua query con una SELECT senza le funzioni di aggregazione (i.e.tagliare il passaggio 5)
  • Se trovi che l'aggregazione porta ad un rallentamento significativo, prova se Python lo fa più velocemente, ottenendo i dati grezzi tramite la semplice SELECT dal confronto.

Per velocizzare la query, riduci prima l'accesso al disco.Dubito fortemente che sia l'aggregazione a richiedere tempo.

Esistono diversi modi per farlo:

  • Memorizza i dati nella cache (in memoria!) per l'accesso successivo, tramite le funzionalità proprie del motore db o con strumenti come memcached
  • Riduci la dimensione dei dati archiviati
  • Ottimizzare l'uso degli indici.A volte questo può significare saltare del tutto l'uso dell'indice (dopo tutto, è anche l'accesso al disco).Per MySQL, mi sembra di ricordare che è consigliabile saltare gli indici se si presuppone che la query recuperi più del 10% di tutti i dati nella tabella.
  • Se la tua query fa buon uso degli indici, so che per i database MySQL è utile inserire indici e dati su dischi fisici separati.Tuttavia, non so se ciò sia applicabile a Postgres.
  • Potrebbero anche verificarsi problemi più sofisticati come lo scambio di righe su disco se per qualche motivo il set di risultati non può essere completamente elaborato in memoria.Ma lascerei questo tipo di ricerca finché non mi imbatto in seri problemi di prestazioni che non riesco a trovare un altro modo per risolverli, poiché richiede la conoscenza di molti piccoli dettagli nascosti nel processo.

Aggiornamento:

Mi sono appena reso conto che sembra che tu non abbia alcuna utilità per gli indici per la query di cui sopra e molto probabilmente non ne stai utilizzando nessuno, quindi il mio consiglio sugli indici probabilmente non è stato utile.Scusa.Tuttavia, direi che l'aggregazione non è il problema ma l'accesso al disco lo è.Lascerò comunque il materiale dell'indice, potrebbe essere ancora utile.

Ho riprovato con MySQL specificando ENGINE = MEMORY e non cambia nulla (ancora 200 ms).Anche Sqlite3 che utilizza un db in memoria fornisce tempi simili (250 ms).

La matematica Qui sembra corretto (almeno la dimensione, dato che è grande il db sqlite :-)

Semplicemente non comprendo l'argomento della lentezza del disco perché c'è ogni indicazione che le tabelle sono in memoria (tutti i ragazzi di Postgres mettono in guardia dal tentare troppo di fissare le tabelle in memoria poiché giurano che il sistema operativo lo farà meglio del programmatore )

Per chiarire i tempi, il codice Java non legge dal disco, il che rende un confronto totalmente ingiusto se Postgres sta leggendo dal disco e calcolando una query complicata, ma non è questo il punto, il DB dovrebbe essere abbastanza intelligente da portare un piccolo tabella in memoria e precompilare una procedura memorizzata IMHO.

AGGIORNAMENTO (in risposta al primo commento qui sotto):

Non sono sicuro di come testerei la query senza utilizzare una funzione di aggregazione in un modo che sarebbe giusto, poiché se seleziono tutte le righe passerà un sacco di tempo a serializzare e formattare tutto.Non sto dicendo che la lentezza sia dovuta alla funzione di aggregazione, potrebbe comunque essere solo un sovraccarico dovuto alla concorrenza, all'integrità e agli amici.Semplicemente non so come isolare l'aggregazione come unica variabile indipendente.

Queste sono risposte molto dettagliate, ma per lo più sollevano la domanda: come posso ottenere questi vantaggi senza lasciare Postgres dato che i dati si adattano facilmente alla memoria, richiedono letture simultanee ma non scritture e vengono interrogate con la stessa query più e più volte.

È possibile precompilare il piano di interrogazione e ottimizzazione?Avrei pensato che la procedura memorizzata avrebbe fatto questo, ma non aiuta davvero.

Per evitare l'accesso al disco è necessario memorizzare nella cache l'intera tabella in memoria, posso forzare Postgres a farlo?Penso che lo stia già facendo, dal momento che la query viene eseguita in soli 200 ms dopo ripetute esecuzioni.

Posso dire a Postgres che la tabella è di sola lettura, quindi può ottimizzare qualsiasi codice di blocco?

Penso che sia possibile stimare i costi di costruzione della query con una tabella vuota (i tempi vanno da 20-60 ms)

Non riesco ancora a capire perché i test Java/Python non sono validi.Postgres semplicemente non sta facendo molto più lavoro (anche se non ho ancora affrontato l'aspetto della concorrenza, solo la memorizzazione nella cache e la costruzione delle query)

AGGIORNAMENTO:Non penso che sia giusto confrontare i SELECTS come suggerito inserendo 350.000 attraverso i passaggi del driver e della serializzazione in Python per eseguire l'aggregazione, e nemmeno omettere l'aggregazione poiché il sovraccarico nella formattazione e nella visualizzazione è difficile da separare dai tempi.Se entrambi i motori funzionano con dati in memoria, dovrebbe essere un confronto tra mele, ma non sono sicuro di come garantire che ciò stia già accadendo.

Non riesco a capire come aggiungere commenti, forse non ho abbastanza reputazione?

Anch'io sono un tipo MS-SQL e utilizzeremmo DBCC PINTABLE per mantenere una tabella nella cache e IMPOSTA STATISTICHE IO per vedere che sta leggendo dalla cache e non dal disco.

Non riesco a trovare nulla su Postgres per imitare PINTABLE, ma pg_buffercache sembra fornire dettagli su cosa c'è nella cache: potresti voler controllarlo e vedere se la tua tabella è effettivamente memorizzata nella cache.

Un rapido calcolo della busta mi fa sospettare che stai effettuando il paging dal disco.Supponendo che Postgres utilizzi interi a 4 byte, hai (6 * 4) byte per riga, quindi la tua tabella ha un minimo di (24 * 350.000) byte ~ 8,4 MB.Supponendo un throughput sostenuto di 40 MB/s sul tuo HDD, stai osservando circa 200 ms per leggere i dati (che, come sottolineato, dovrebbe essere il luogo in cui viene trascorso quasi tutto il tempo).

A meno che non abbia sbagliato i miei calcoli da qualche parte, non vedo come sia possibile che tu sia in grado di leggere 8 MB nella tua app Java ed elaborarli nei tempi visualizzati, a meno che quel file non sia già memorizzato nella cache dall'unità o dal tuo sistema operativo.

Non penso che i tuoi risultati siano così sorprendenti: semmai è che Postgres è così veloce.

La query Postgres viene eseguita più velocemente una seconda volta dopo aver avuto la possibilità di memorizzare nella cache i dati?Per essere un po' più equi, il tuo test per Java e Python dovrebbe coprire in primo luogo il costo di acquisizione dei dati (idealmente caricandoli dal disco).

Se questo livello di prestazioni è un problema per la tua applicazione nella pratica ma hai bisogno di un RDBMS per altri motivi, potresti esaminarlo memcached.Avresti quindi un accesso memorizzato nella cache più veloce ai dati grezzi e potresti eseguire i calcoli nel codice.

Stai utilizzando TCP per accedere a Postgres?In tal caso Nagle sta scherzando con i tuoi tempi.

Un'altra cosa che un RDBMS generalmente fa per te è fornire concorrenza proteggendoti dall'accesso simultaneo da parte di un altro processo.Questo viene fatto posizionando i blocchi e c'è un certo sovraccarico da questo.

Se hai a che fare con dati interamente statici che non cambiano mai, e soprattutto se ti trovi in ​​uno scenario fondamentalmente "utente singolo", l'utilizzo di un database relazionale non ti offre necessariamente molti vantaggi.

È necessario aumentare le cache di Postgres al punto in cui l'intero set di lavoro si adatta alla memoria prima di potersi aspettare di vedere prestazioni paragonabili a farlo in memoria con un programma.

Grazie per i tempi Oracle, questo è il tipo di cose che sto cercando (deludente però :-)

Probabilmente vale la pena considerare le visualizzazioni materializzate poiché penso di poter precalcolare le forme più interessanti di questa query per la maggior parte degli utenti.

Non penso che il tempo di andata e ritorno delle query dovrebbe essere molto elevato poiché sto eseguendo le query sulla stessa macchina che esegue Postgres, quindi non può aggiungere molta latenza?

Ho anche controllato le dimensioni della cache e sembra che Postgres faccia affidamento sul sistema operativo per gestire la memorizzazione nella cache, menzionano specificamente BSD come sistema operativo ideale per questo, quindi penso che Mac OS dovrebbe essere piuttosto intelligente nel portare la tabella in memoria.A meno che qualcuno non abbia in mente parametri più specifici, penso che una memorizzazione nella cache più specifica sia fuori dal mio controllo.

Alla fine probabilmente posso sopportare tempi di risposta di 200 ms, ma sapere che 7 ms è un obiettivo possibile mi fa sentire insoddisfatto, poiché anche tempi di 20-50 ms consentirebbero a più utenti di avere query più aggiornate e di liberarsi di un sacco di memorizzazione nella cache e hack precalcolati.

Ho appena controllato i tempi utilizzando MySQL 5 e sono leggermente peggiori di Postgres.Quindi, salvo alcune importanti scoperte nella memorizzazione nella cache, immagino che questo sia ciò che posso aspettarmi dal percorso del database relazionale.

Vorrei poter votare alcune delle tue risposte, ma non ho ancora abbastanza punti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top