Risoluzione scalare dei dati delle serie temporali

2

Ho creato un'app che mostra i dati delle serie temporali. Il display è molto simile a un grafico finanziario yahoo (cioè link ). Sto usando Postgres per archiviare i dati perché quello era il database disponibile per me quando lo stavo costruendo. Il problema che sto avendo è quando provo a scalare i diversi intervalli di tempo (1 giorno, vs mese, vs max, ect). I dati grezzi vengono salvati in risoluzione minuscola nello schema seguente. L'app_id è un ID che l'app gestisce. Il prezzo è sperabilmente scontato e il tempo è millisecondo dall'epoca.

column | Type   |
-------+--------+---
app_id | bigint |
price  | double |
time   | bigint |

Il problema è quando qualcuno fa clic su 5 giorni (5D nella trama di yahoo), ad esempio non riesco a caricare 7200 (numero di minuti in 5 giorni) di punti dati nel front end. Ho bisogno di ridimensionare i dati per dire 60 dati punti. Ciò significherebbe che ho bisogno di ogni 120 ° punto dati. Per fare ciò uso MOD (app_id, 120). Quindi la mia domanda sarebbe qualcosa come

 select price from my_table 
 where app_id > (select count(*) from my_table) - 7200 
 and MOD(app_id,120) = 0

Tuttavia mi chiedo se esiste un modo più efficiente o scalabile per gestirlo? Perché per garantire di avere il primo e l'ultimo punto di dati devo afferrarli separatamente. Perché se ci sono 7201 punti dati nel db, quella query non prenderà il primo. Inoltre se ci sono lacune nei dati salvati che devo riempire manualmente, per qualsiasi motivo, tutti gli ID che vengono dopo che devono essere aggiornati. Quindi la mia domanda si riduce a:

C'è un modo per farlo con gli stessi timestamp, o un modo più semplice di quello che sto facendo attualmente con MOD?

    
posta achyrd 11.07.2017 - 05:12
fonte

1 risposta

3

La query che ci hai fornito non è molto scalabile perché dovrà ancora eseguire la scansione dell'intero o di un ampio intervallo di righe. Hai contribuito a ridurre la quantità di dati restituiti, ma credo che aumenterà ancora il carico sul tuo database del necessario. Anche l'utilizzo di MOD non rifletterà i dati in modo accurato in alcuni casi. Ad esempio, se il punto dati che corrisponde alla clausola MOD è un valore anomalo, sarebbe fuorviante mostrare tale valore sul grafico. Invece dovresti usare un qualche tipo di media dei valori grezzi nel periodo di tempo.

Approccio alle viste materializzate

Vorrei utilizzare un set di visualizzazioni materializzate per aggregare i dati grezzi in medie . Ecco un esempio:

create materialized view
  my_table_hourly_agg 
as
  select 
    min(app_id) as app_id, 
    avg(price) as price, 
    min(time) as time 
  from 
    my_table 
  group by
    floor(time / (60 * 60 * 1000)) -- group raw data to the nearest hour

Questa soluzione ha il vantaggio che a granularità più elevate non è più necessario interrogare un ampio intervallo di righe. Poiché la vista è materializzata, significa che le righe sono memorizzate e che è possibile accedere come una tabella anziché una normale vista del database in cui la query viene eseguita ogni volta che viene utilizzata la vista. È possibile creare viste materializzate di granularità più ampia dalle viste materializzate di granularità inferiore.

Questa soluzione ha il rovescio della medaglia che la vista materializzata avrà una sorta di latenza di aggiornamento ad essa associata. Devi chiamare refresh materialized view per aggiornare i contenuti della vista materializzata. Puoi farlo su un programma in base alle tue esigenze. L'aggiornamento della visualizzazione potrebbe richiedere del tempo se il numero di righe è elevato.

Puoi provare a utilizzare le viste normali (utilizzando la stessa query di cui sopra), ma non sarà così scalabile.

Approccio tabelle aggregate

Penso che il design ideale - se possibile è creare tabelle per ciascuno dei periodi aggregati e riempirli quando i dati grezzi entrano nel tuo sistema. Ad esempio:

create table my_table_hourly_agg (
  first_app_id integer primary key,
  avg_price real,
  start_time bigint,
  num_samples integer, -- we need number of samples to produce a rolling average
  constraint uniq_hour_agg unique (start_time) -- one row per hour
)

Quindi quando una nuova riga di dati grezzi entra nel tuo sistema, inserisci simultaneamente il punto dati e aggiorna le tabelle di aggregazione.

begin transaction
  insert into 
    my_table 
  values ($app_id, $price, $time)

  insert into 
    my_table_hourly_agg (first_app_id, avg_price, start_time, num_samples)
  values ($app_id, $price, floor($time / (60 * 60 * 1000)), 1)
  on conflict (start_time) -- if there is a conflict that means this is not the first data point for the given hour, and we should update the average price and the number of samples
  do update 
    set avg_price = (avg_price * num_samples + $price) / (num_samples + 1)
    set num_samples = num_samples + 1
commit

Con questo design i tuoi aggregati sono immediatamente aggiornati, e hai il vantaggio di query di query a bassa velocità (come le visualizzazioni materializzate), ma questo potenzialmente ingombra il tuo codice ed è molto importante proteggerti con le transazioni evitare che i tuoi aggregati non siano corretti. Potresti potenzialmente scrivere trigger di database per aggiornare le tue tabelle aggregate, ma non ho abbastanza tempo per mostrare un esempio.

    
risposta data 11.07.2017 - 06:56
fonte

Leggi altre domande sui tag