Grafici delle Serie Temporali con Dash, Flask, TimescaleDB e Docker – Parte 1

In questo tutorial in tre parti, ti mostrerò come creare un’applicazione reattiva a pagina singola interamente in Python, con grafici di serie temporali dinamici tramite Dash / Plotly, il framework Flask per siti Web e un database specializzato per le serie temporali chiamato TimescaleDB, che a sua volta è basato su PostgreSQL. Un bel boccone, ma questo è uno stack tecnologico piuttosto interessante che è facile da imparare e da programmare, poiché utilizza solo Python e SQL standard (senza JavaScript). Quindi è uno stack ideale per distribuire rapidamente applicazioni di data science.

La prima parte del tutorial si concentrerà sull’utilizzo di Docker per configurare il database TimescaleDB specializzato e PGAdmin per gestirlo. Creeremo alcuni dati IoT simulati e mostreremo alcune delle fantastiche funzionalità di TimescaleDB, che non troverai nel PostgreSQL standard.

La seconda parte si concentrerà sulla configurazione di un sito Web Python Flask che si integra con la straordinaria libreria Dash per la creazione di applicazioni a pagina singola (SPA) basate su JavaScript React. Ti mostrerò come integrare correttamente Dash in Flask, in modo da poter ottenere il meglio da entrambi i framework web.

La terza parte si concentrerà sull’utilizzo di Dash per creare grafici di serie temporali interattivi per il monitoraggio dei dati IoT o per mostrare la tua applicazione di data science.

Tutto il codice per questo tutorial può essere trovato qui su GitHub.

Uso Docker laddove possibile, per ambienti riproducibili e una distribuzione estremamente semplice utilizzando Docker Swarm, quindi se non hai familiarità con Docker, consulta la documentazione qui.

Parte 1 - TimescaleDB, PGAdmin e Docker

Per prima cosa, creiamo una rete Docker in modo che i nostri prossimi contenitori possano parlare tra loro:

            docker network create --attachable --driver bridge timescale_network
        
Successivamente, creiamo un database TimescaleDB locale utilizzando Docker-Compose. Questo avvierà rapidamente un database PostgreSQL locale con l’estensione TimescaleDB configurata automaticamente. Si usa il seguente file docker-compose.timescale.yml:
            # docker-compose.timescale.yml

version: '3.7'
services:
  timescale:
    image: timescale/timescaledb:1.7.4-pg12
    container_name: flaskapp_timescale
    volumes: 
      - type: volume
        # source: timescale-db # the volume name
        source: timescale_volume
        # target: the location in the container where the data are stored
        target: /var/lib/postgresql/data 
        read_only: false
      # Custom postgresql.conf file will be mounted (see command: as well)
      - type: bind
        source: ./postgresql_custom.conf
        target: /postgresql_custom.conf
        read_only: false
    env_file: .env
    environment: 
      POSTGRES_HOST: timescale
    command: ["-c", "config_file=/postgresql_custom.conf"]
    ports: 
      - 0.0.0.0:5432:5432
    networks:
      timescale_network:
    deploy:
      restart_policy:
        condition: on-failure

# Creates a named volume to persist our database data
volumes:
  timescale_volume:

# Joins our external network
networks:
  timescale_network:
    external: true
        

Nel precedente file Docker-Compose possiamo notare i seguenti aspetti:

  • Si utilizza il  timescale_network che abbiamo creato nel passaggio precedente.
  • Si utilizza un volume per rendere persistenti i dati del database, anche se il contenitore Docker viene rimosso o sostituito. Questo è molto comune per i database “Dockerizzati”.
  • Si utilizza la porta 5432 (questo sarà importante quando proveremo ad accedere al database).
  • Si utilizza un file di configurazione personalizzato e un file  .env per memorizzare le informazioni sensibili per la connessione al database, come la password del database. E’ quindi necessario creare questi due file.

Ecco il file di configurazione personalizzato, nel caso in cui desideri / devi modificare una di queste impostazioni in futuro. Il file è troppo lungo per essere inserito in un blocco di codice in questo articolo, quindi fai clic su questo collegamento , quindi copia e incolla il testo in un file chiamato postgresql_custom.conf e mettilo nella radice della cartella del progetto.

Successivamente, ecco un modello per il file .env, che puoi lasciare nella root della cartella del tuo progetto, insieme ai file Docker-Compose e a quelli di configurazione del database:

            # .env

# For the Postgres/TimescaleDB database. 
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_HOST=timescale
POSTGRES_PORT=5432
POSTGRES_DB=postgres
PGDATA=/var/lib/postgresql/data
        

Ora che abbiamo aggiunto la configurazione personalizzata e i file .env, puoi avviare il database TimescaleDB con il seguente comando. Il -d esegue il comando in background (--detached).

            docker-compose -f docker-compose.timescale.yml up -d
        
Controlla i tuoi contenitori in esecuzione con docker container ls o la vecchia scuola docker ps. Se il contenitore si sta riavviando, controlla i log con docker logs <container id> e assicurati di aver configurato il file .env, il file di configurazione e la rete Docker da cui dipende. Infine, creiamo un ambiente PGAdmin amichevole per l’amministrazione del nostro database e l’esecuzione di SQL. Crea un file chiamato docker-compose.pgadmin.yml e aggiungi quanto segue:
            # docker-compose.pgadmin.yml

version: '3.7'
services:
  pgadmin:
    # Name of the container this service creates. Otherwise it's prefixed with the git repo name
    image: "dpage/pgadmin4:latest"
    container_name: flaskapp_pgadmin4
    restart: unless-stopped
    env_file: .env
    environment: 
      PGADMIN_LISTEN_PORT: 9000
    ports: 
      - 0.0.0.0:9000:9000
    volumes: 
      # So the database server settings get saved and stored even if the container is replaced or deleted
      - pgadmin:/var/lib/pgadmin
    networks:
      timescale_network:

volumes:
  pgadmin:

networks:
  timescale_network:
    external: true

        
Aggiungi le seguenti righe al tuo .env file per PGAdmin. Avrai bisogno di queste informazioni di accesso quando tenterai di accedere a PGAdmin nel browser web.
            # .env

# For the PGAdmin web app
[email protected]
PGADMIN_DEFAULT_PASSWORD=password
        

Avvia l’applicazione web PGAdmin (PostgreSQL Admin) con il seguente comando Docker:

            docker-compose -f docker-compose.pgadmin.yml up -d

        

Esegui di docker container lsnuovo per verificare se il contenitore PGAdmin è in esecuzione. Nota che abbiamo specificato una porta di 9000, quindi ora puoi accedere a PGAdmin su http://localhost:9000 o http://127.0.0.1:9000 .Accedi con il nome utente e la password che hai impostato nel tuo file .env.

Ora che hai effettuato l’accesso a PGAdmin, fai clic con il pulsante destro del mouse su “Server” e “Crea / Server…”. Chiamalo “TimescaleDB Locale” nella scheda “Generale” e digita quanto segue nella scheda “Connessione”:

  • Host : timescale (questo è il nome host del “Servizio” Docker definito nel primo file docker-compose.yml per il contenitore del database TimescaleDB)
  • Porta : 5432
  • Database di manutenzione : postgres
  • Nome utente : postgres
  • Password : password

Fai clic su “Salva” e dovresti essere connesso. Ora puoi fare doppio clic su “TimescaleDB Local” e puoi accedere alle tabelle del tuo database su “/ Databases / postgres / Schemas / public / Tables”. Molto intuitivo, eh? Nel menu “Strumenti”, fai clic su “Strumento di query” e sei pronto per iniziare a scrivere SQL.

Ora sei l’orgoglioso comandante di un database TimescaleDB, che è identico a un database PostgreSQL (“Il database open source più avanzato al mondo”, se credi nel loro marketing), tranne per il fatto che ora ha capacità speciali per gestire dati ad alta frequenza di serie temporali.

I dati delle serie temporali sono leggermente diversi dai normali dati relazionali per descrivere utenti e cose. I dati delle serie temporali possono arrivare in qualsiasi secondo o anche più volte al secondo, a seconda di ciò che si sta archiviando, quindi il database deve essere in grado di gestire molti inserimenti. Alcuni esempi sono i dati finanziari, come i prezzi di negoziazione del mercato azionario o dati di Internet of Things (IoT), solitamente per il monitoraggio di metriche ambientali come temperatura, pressione, umidità o qualsiasi altra cosa tu possa pensare. Di solito quando esegui query sui dati di serie temporali, sei interessato ai dati più recenti e di solito stai filtrando sulla colonna timestamp, quindi è assolutamente necessario indicizzarli. TimescaleDB è specializzato in questo genere di cose.

Creiamo uno speciale “Hypertable” di TimescaleDB e inseriamo alcuni dati con cui giocare. Ecco la documentazione . Ed ecco il tutorial da cui ottengo i dati SQL simulati.

In PGAdmin, se non ci sei già, nel menu “Strumenti”, fai clic su “Strumento di query” e digita il seguente SQL per creare due tabelle di dati IoT:

            CREATE TABLE sensors(
  id SERIAL PRIMARY KEY,
  type VARCHAR(50),
  location VARCHAR(50)
);

CREATE TABLE sensor_data (
  time TIMESTAMPTZ NOT NULL,
  sensor_id INTEGER,
  temperature DOUBLE PRECISION,
  cpu DOUBLE PRECISION,
  FOREIGN KEY (sensor_id) REFERENCES sensors (id)
);

CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE


        

Ora vediamo una funzionalità speciale di TimeScaleDB che non puoi fare in un normale database PostgreSQL. Trasformeremo la tabella sensor_data  in una “Hypertable”. Dietro le quinte, TimescaleDB partizionerà i dati sulla dimensione temporale, semplificando il filtraggio, l’indicizzazione e l’eliminazione dei vecchi dati delle serie temporali.

Se sei arrivato a questo tutorial per sfruttare le caratteristiche uniche di TimescaleDB, di seguito vedrai dove avviene la magia.

Eseguiamo la seguente query in PGAdmin per creare l’hypertable, partizionato automaticamente sulla dimensione “time”:

            SELECT create_hypertable('sensor_data', 'time');

        

Ora che è stata creata la nostra tabella di serie temporali specializzata, creiamo un indice speciale sull’ID del sensore, poiché è molto probabile che filtreremo sia l’ID del sensore che l’ora.

            create index on sensor_data (sensor_id, time desc);
        

Aggiungiamo ora alcuni dati di esempio nella tabella “sensori”:

            INSERT INTO sensors (type, location) VALUES
    ('a','floor'),
    ('a', 'ceiling'),
    ('b','floor'),
    ('b', 'ceiling');

        

Ora la parte divertente: creiamo alcuni dati simulati per le serie temporali:

            INSERT INTO sensor_data 
  (time, sensor_id, cpu, temperature)
SELECT
  time,
  sensor_id,
  random() AS cpu,
  random()*100 AS temperature
FROM 
  generate_series(
    now() - interval '31 days', 
    now(), interval '5 minute'
  ) AS g1(time), 
  generate_series(1,4,1) AS g2(sensor_id);

        

Eseguiamo una semplice query di selezione per vedere alcuni dei nostri dati appena simulati:

            SELECT * 
FROM sensor_data
WHERE time > (now() - interval '1 day')
ORDER BY time;
        

Ecco un altro esempio di selezione dei dati aggregati (ovvero la media ad 1 ora, invece di vedere ogni singolo punto dati):

            SELECT 
  sensor_id,
  time_bucket('1 hour', time) AS period, 
  AVG(temperature) AS avg_temp, 
  AVG(cpu) AS avg_cpu 
FROM sensor_data 
GROUP BY 
  sensor_id, 
  time_bucket('1 hour', time)
ORDER BY 
  sensor_id, 
  time_bucket('1 hour', time);

        

Dal tutorial ufficiale di TimescaleDB, mostriamo altre due query. Innanzitutto, invece di una cronologia delle serie temporali, potremmo semplicemente desiderare i dati più recenti . Per questo, possiamo utilizzare la funzione “last()”:

            SELECT 
  time_bucket('30 minutes', time) AS period, 
  AVG(temperature) AS avg_temp, 
  last(temperature, time) AS last_temp --the latest value
FROM sensor_data 
GROUP BY period;

        

E, naturalmente, spesso vogliamo unire i dati delle serie temporali con i metadati (cioè i dati sui dati). In altre parole, otteniamo una posizione per ogni sensore, piuttosto che un ID sensore:

            SELECT 
  t2.location, --from the second metadata table
  time_bucket('30 minutes', time) AS period, 
  AVG(temperature) AS avg_temp, 
  last(temperature, time) AS last_temp, 
  AVG(cpu) AS avg_cpu 
FROM sensor_data t1 
INNER JOIN sensors t2 
  on t1.sensor_id = t2.id
GROUP BY 
  period, 
  t2.location;


        

TimescaleDB ha un’altra funzione molto utile chiamata “aggregazioni continue” per aggiornare continuamente ed efficientemente le visualizzazioni aggregate dei dati delle nostre serie temporali. Se desideriamo creare rapporti / grafici sui dati aggregati, il seguente codice per la creazione di viste fa al nostro caso:

            CREATE VIEW sensor_data_1_hour_view
WITH (timescaledb.continuous) AS --TimescaleDB continuous aggregate
SELECT 
  sensor_id,
  time_bucket('01:00:00'::interval, sensor_data.time) AS time,
  AVG(temperature) AS avg_temp, 
  AVG(cpu) AS avg_cpu
FROM sensor_data
GROUP BY 
  sensor_id,
  time_bucket('01:00:00'::interval, sensor_data.time)


        

Questo è tutto per la parte 1 di questo tutorial in tre parti su TimescaleDB, Dash e Flask. Ecco la parte 2 sull’integrazione di Dash e Flask. La parte 3 si concentrerà sulla creazione di grafici di serie temporali reattivi e interattivi in ​​Dash per la tua applicazione a pagina singola (SPA).

A presto!

Recommended Posts

No comment yet, add your voice below!


Add a Comment

Il tuo indirizzo email non sarà pubblicato.