Domanda

Ho una semplice tabella delle serie temporali

movement_history (
    data_id serial,
    item_id character varying (8),
    event_time timestamp without timezone,
    location_id character varying (7),
    area_id character varying (2)
);

Il mio sviluppatore frontend mi dice che il costo è troppo alto se vuole sapere dove si trova un elemento in un determinato timestamp perché deve ordinare la tabella.Vuole che aggiunga un altro campo timestamp per il prossimo evento in modo da non dover ordinare.Tuttavia, ciò raddoppierà il costo del mio codice per inserire un nuovo movimento poiché dovrò interrogare la voce precedente per l'articolo, aggiornarla e quindi inserire i nuovi dati.

I miei inserti ovviamente superano di gran lunga le sue domande in frequenza.E non ho mai visto una tabella della serie temporale che includesse una voce per l'ora del prossimo evento.Mi sta dicendo che il mio tavolo è rotto perché la sua rara query richiede un ordinamento.Eventuali suggerimenti?

Non so quale query stia usando, ma farei questo:

select * from movement_history 
where event_time <= '1-15-2015'::timestamp  
and item_id = 'H665AYG3' 
order by event_time desc limit 1;

Al momento disponiamo di circa 15.000 articoli che vengono inseriti nel database al massimo una volta al giorno.Tuttavia presto avremo 50.000 articoli con dati dei sensori aggiornati ogni 1-5 minuti.

Non vedo che la sua query venga eseguita molto spesso, ma lo sarà un'altra query per ottenere lo stato corrente dei pallet.

select distinct on (item_id) * 
from movement_history 
order by item_id, event_time desc;

Su questo server è attualmente in esecuzione la versione 9.3 ma, se necessario, potrebbe essere in esecuzione la versione 9.4.

È stato utile?

Soluzione

Creare un indice su (item_id, event_time).

Volterà all'elemento specificato, salterà all'event_time specificato per questo elemento_ID, quindi spostarlo.Nessun ordinamento coinvolto.

Altri suggerimenti

Soluzioni contrastanti

Avresti bisogno di un indice a più colonne come fornito da @jjanes.Mentre ci sei, tu Potevo Fare (item_id, event_time) la chiave primaria per fornire automaticamente l'indice.

Ma questo è in conflitto con le prestazioni di scrittura come @Michael ha spiegato:Raddoppi il costo per 50K of items ... updated every 1 to 5 minutes produrre occasionale SELECT query più economiche.Sono circa 1 milione.righe all'ora.

Partizionamento

Se non hai requisiti più contrastanti, il compromesso potrebbe esserlo partizionamento dove il attuale la partizione non ha ancora un indice.In questo modo ottieni prestazioni di scrittura al top e prestazioni di lettura (quasi) al top.

La tabella genitore potrebbe essere movement_history, la partizione corrente movement_history_current.Nessun indice, solo un vincolo da consentire esclusione dei vincoli.Potrebbero essere partizioni giornaliere per impostazione predefinita.Ma gli intervalli di tempo possono essere nulla, non deve nemmeno essere regolare.Possiamo lavorare con questo e avviare una nuova partizione ogni volta che ne abbiamo bisogno.

Quando è necessario includere i dati correnti in detta query, procedere come segue:

  1. Per avviare una nuova partizione, in una transazione:

    • Rinominare la partizione corrente aggiungendo sth.al nome, come movement_history_20150110_20150115 (o più specifico) e modificare il vincolo event_time.
    • Crea una nuova partizione con lo stesso nome movement_history_current e un vincolo su event_time che non si sovrappone all'ultimo e con finale aperto.
    • A seconda dei tuoi modelli di accesso potresti dover gestire l'accesso in scrittura simultaneo ...
  2. Aggiungi un PK attivo (item_id, event_time) alla nuova partizione storica.Non nella stessa transazione.Creare l'indice in un unico pezzo lo è tanto più economico che aggiungerlo in modo incrementale.

    2a.Per integrare i consigli per la tua seconda query di seguito:

    REFRESH MATERIALIZED VIEW mv_last_movement 
    
  3. Esegui interrogazione.In realtà, puoi eseguire la query Qualunque tempo.Se include la partizione corrente o qualsiasi partizione che non dispone ancora dell'indice, è più lento per quella partizione.

Archivia di tanto in tanto le partizioni più vecchie.Basta eseguire il backup ed eliminare la tabella.Non interferisce molto con le operazioni in corso, questo è il bello del partizionamento.

Leggere prima il manuale.Ci sono avvertenze per eredità E partizionamento.

La tua seconda domanda

La seconda query che hai aggiunto in una modifica è the lontano questione più grande per le prestazioni.Sto parlando di ordini di grandezza:

select distinct on (item_id) * from movement_history
order by item_id, event_time desc;

Una volta che inizi a inserire 1 mio.righe all'ora, le prestazioni di questa query peggioreranno rapidamente.Hai a che fare con molti molti righe per articolo, DISTINCT ON va bene solo per pochi righe per articolo.Spiegazione dettagliata per DISTINCT ON e alternative più veloci:

Suggerisco ancora partizionamento come nella mia prima risposta.Ma applica una nuova partizione a intervalli ragionevoli, in modo che la partizione corrente non diventi troppo grande.

Inoltre, crea un file "vista materializzata" che tiene traccia dello stato più recente di ciascun articolo.Non è uno standard MATERIALIZED VIEW perché la query di definizione ha un autoreferenzialità.Lo nomino mv_last_movement e ha lo stesso tipo di riga di movement_history.

Aggiorna ogni volta che viene avviata una nuova partizione (vedi sopra).
Supponendo l'esistenza di un item tavolo:

CREATE TABLE item (
  item_id varchar(8) PRIMARY KEY  -- should really be a serial 
  -- more columns?
);

Se non ne hai uno, crealo.Oppure utilizzare la tecnica CTE ricorsiva alternativa descritta nella risposta collegata sopra.

Dentro mv_last_movement una volta:

CREATE TABLE mv_last_movement AS
SELECT m.*
FROM   item i
,      LATERAL (
   SELECT *
   FROM   movement_history_current  -- current partition
   WHERE  item_id = i.item_id  -- lateral reference
   ORDER  BY event_time DESC
   LIMIT  1
   ) m;

ALTER TABLE mv_last_movement ADD PRIMARY KEY (item_id);

Quindi, per aggiornare (in un'unica transazione!):

BEGIN;

CREATE TABLE mv_last_movement2 AS
SELEZIONA m.*
A partire dall'articolo i
, LATERALE (
   ( -- parentesi obbligatorie
   SELEZIONARE*
   FROM movement_history_current -- partizione corrente
   WHERE item_id = i.item_id -- riferimento laterale
   ORDINA PER event_time DESC
   LIMITE 1 -- applicato a questo SELECT, non strettamente necessario ma più economico
   )
   UNION ALL -- se non trovato, torna all'ultimo stato precedente
   SELEZIONARE*
   DA mv_last_movement -- la tua visione materializzata
   WHERE item_id = i.item_id -- riferimento laterale
   LIMIT 1 -- applicato all'intera query UNION
   ) m;

DROP TABLE mv_last_movement;
ALTER TABLE mv_last_movement2 RENAME mv_last_movement;
ALTER TABLE mv_last_movement ADD PRIMARY KEY (item_id);

COMMIT;

O simili.Maggiori dettagli qui:

La stessa query dall'alto (grassetto) sostituisce anche la query originale citata in alto.

In questo modo non è necessario controllare l'intera cronologia per gli elementi senza righe correnti, il che sarebbe estremamente costoso.

Perché UNION ALL ... LIMIT 1?

Ulteriori consigli

  • varchar per le colonne PK/FK è inefficiente, soprattutto per tabelle di grandi dimensioni con 1 milione di righe all'ora.Utilizzo integer invece le chiavi.

  • Utilizza sempre il formato ISO per i valori letterali di data e ora oppure le tue query dipendono dalle impostazioni locali: '2015-15-01' invece di '1-15-2015'.

  • Aggiungere NOT NULL vincoli in cui la colonna non può essere NULL.

  • Ottimizza il layout del tuo tavolo per evitare spazio perso a causa del riempimento

Spesso il design del software è un compromesso tra requisiti concorrenti.È importante capire i meriti relativi, sia per il sistema nel suo insieme che per ciascun caso localmente.Ad esempio, tu dici scrive che superano i numerosi recita.Ciò suggerirebbe che il sistema nel suo complesso dovrebbe essere ottimizzato per scritture.Tuttavia, quali sono quelle letture per - impediscono una collisione del veicolo o un arresto cardiaco?Forse quei sistemi dovrebbero essere ottimizzati per leggere.

Hai un indice sulla colonna del tempo?Quindi una query come select top (1) .. where time < parameter .. sorted desc dovrebbe usare quell'indice.Essenzialmente, pre-ordina i dati per tutte le query.

L'ironia è che ogni scrittura dovrà mantenere questo indice, raddoppiando il costo ogni volta.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a dba.stackexchange
scroll top