Alcuni anni fa ho realizzato un sistema installato su una macchina industriale (un banco di lavoro) che aveva lo scopo gestire i file di lavorazioni generati dalla pressa presente a bordo macchina.

L’applicazione al termine di ogni ciclo leggeva il file generato, estraeva le informazioni e le inseriva in un database SQL Server locale, dove era installata l’applicazione stessa.

Un sistema esterno poi, ad intervalli regolari, si connetteva al database locale, importava i dati e cancellava il contenuto delle tabelle (oltretutto la versione era SQL Server express con un limite a 10GB del singolo database, che si esaurivano velocemente).

I dati prelevati venivano elaborati in un sistema centrale per analizzare la qualità delle lavorazioni e fare altre scelte strategiche.

Come puoi immaginare questo può essere fonte di diversi problemi tra cui

  • problemi di rete: a causa “dell’accumulo” di dati sul database locale, quando il servizio esterno li prelevava questo poteva generare dei carichi di trasporto non indifferente. Mentre negli altri momenti era tutto scarico
  • problemi di processing dei dati: un processo notturno lavorava tutti i dati importati dalle macchine (ovviamente non era solo una, ma si parla di diverse linee produttive) con ovvi problemi di performance. In alcuni momenti della giornata sarebbe stato tutto fermo, mentre in altri ci sarebbe stato un picco notevole di processore (con evidenti possibili rallentamenti)

Apache Spark, Azure e sopratutto structured streaming

Da qui l’idea di realizzare un proof of concept e ridisegnare il processo usando Apache Spark ed il cloud (in questo caso Azure). Perché non usare questi componenti per analizzare uno stream di dati continuo piuttosto che enormi batch di dati? Perché non mettere a disposizione le possibilità di un motore di elaborazione dei dati distribuito su così performante?

Il sistema prevede che un componente che produce dati (generati dal banco di lavoro) e un componente che consuma i dati prodotti, per elaborarli, possano comunicare tramite un servizio di trasporto di eventi e di stream di dati. Il tutto in un’ottica near-realtime.

Lo scopo è quello di evitare lo spostamento e l’elaborazione di grandi moli di dati in momenti specifici della giornata, con gli svantaggi descritti prima. Il “goal” è ottenere un “flusso” continuo di dati (parleremo di “microbatch”) che quindi possa stabilizzare la curva di carico dei sistemi, provveda performance migliori, e che renda disponibili subito i risultati di elaborazione delle lavorazioni poco dopo che queste hanno avuto luogo.

Un approccio di questo genere garantisce migliori prestazioni, dati in tempo quasi reale e sopratutto una migliore gestione delle risorse. Per darti una idea visuale della differenza tra batch e stream ho cercato di realizzare una illustrazione che rendesse bene l’idea, come puoi vedere sotto.

Come puoi vedere anziché eseguire l’elaborazione in una volta sola, andremo a suddividere gli stessi dati in “gruppi” più piccoli e lavorati non appena vengono prodotti.

Il sistema sarà realizzato da questi componenti:

  • Una applicazione installata a bordo macchina (scritta in python, la chiameremo producer) che ad ogni fine ciclo di lavorazione preleva il file di log generato dal banco di lavoro, ne trasforma le parti utili in un documento json, e lo spedisce al sistema di trasporto dei dati.
  • Il sistema di trasporto dati (lo chiameremo sistema di streaming) che sarà responsabile di ricevere tutti i messaggi spediti senza creare rallentamenti e distribuirli altrettanto velocemente a chi dovrà elaborarli (o “consumarli”), predisponendo così un vero e proprio “flusso” di dati. Questo componente sarà realizzato con il servizio Azure Event Hubs di Microsoft (altrimenti potrebbe essere approntato un server con un servizio kafka on-premise).
  • Il sistema di elaborazione dei dati (detto anche “consumer”) prodotti dalla applicazione python e ricevuti tramite Event Hubs, che sarà basato sul motore Spark (motore di data processing ad altissime prestazioni). Anche in questo caso si potrebbe utilizzare un servizio cloud (Azure Databcricks) oppure creare il proprio cluster on-premise.
  • Un database Postgres su cui verranno storicizzati i dati finali, dopo tutte le lavorazioni.

Perché usare Spark ed Event Hubs?

Perché la mole di dati prodotti potrebbe crescere molto rapidamente! Nella fattispecie infatti, ogni banco di lavoro produce dati ad intervalli regolari. Quindi l’applicazione python a bordo macchina sarà sempre sufficientemente veloce per leggere il log, modificarne il formato, e spedirlo al sistema di streaming.

Ma che dire del traffico dati al crescere dei banchi di lavoro e delle diverse tipologie di macchina? In una azienda di produzione i banchi di lavoro e in generale le macchine possono essere decine se non centinaia.

Quindi il sistema di streaming deve essere scalabile: deve cioè poter aumentare le proprie risorse per adattarsi ai volumi di dati crescenti senza inficiare sulle le prestazioni. Azure Event Hubs è un servizio di questo tipo che è compatibile con diverse piattaforme di streaming tra cui Kafka. Quindi utilizzando Azure Event Hubs sarebbe possibile passare da un servizio Kafka on-premise al cloud senza particolari modifiche ai software già esistenti e viceversa.

Stesso discorso vale per Spark. Spark è un motore di elaborazione e analisi dati distribuito che permette di scalare al mutare delle necessità. Esattamente come per Event Hubs è possibile passare con poco sforzo da un sistema on-premise locale al cloud utilizzando il servizio Azure Databricks che mette a disposizione un cluster Spark senza però doversi preoccupare di tutta la parte di configurazione.

Il caso reale

L’obiettivo è questo: nel caso specifico il file di log generato dalla macchina contiene tantissime informazioni tra cui una serie temporale rappresentante per ogni istante della campionatura i dati si forza esercitata in quel momento, spostamento della pressa ed altre grandezze utili. Vogliamo ottenere informazioni utili da quei dati per migliorare la qualità del prodotto.

Due parole sul processo produttivo, per farti capire il valore dell’informazione

L’azienda in questione ha la necessità di accoppiare due parti meccaniche. Spesso quando vogliamo fissare due parti insieme vengono in mente bulloni o viti che passato da una parte all’altra.

Ma in molti casi questi accoppiamenti vengono fatti a pressione: su uno dei due pezzi che devono essere accoppiati viene eseguita una foratura (non se la prendano con me gli ingegneri meccanici per il mio dizionario tecnico…) mentre dal secondo pezzo sporge generalmente un “cilindro” che andrà ad inserirsi nella foratura stessa.

Gli ingegneri meccanici progettano i due dettagli in modo che per inserire il cilindro nella foratura sia necessaria una determinata forza così che i due pezzi non si separino e che siano rispettati altri vincoli funzionali all’assemblato che si realizzerà.

La pressa esegue questa operazione: su un lato del banco viene fissato il primo pezzo, sul braccio della pressa viene fissato il secondo. Così la pressa nel suo movimento andrà a comprimere le due parti realizzando l’accoppiamento.

Ma per richiedere la forza necessaria all’accoppiamento, e di conseguenza l’assemblato abbia la massima qualità, sia la foratura che il cilindro devono essere realizzati con una precisione altissima, ad esempio al centesimo di millimetro e con altre caratteristiche secondo progetto.

In termini davvero grossolani, se il foro è leggermente più grande (cosa che non si vede ad occhio nudo) l’accoppiamento avviene, ma alla prima occasione non ci sarà tenuta e i due pezzi si separeranno.

Se invece il foro è leggermente più piccolo questo potrebbe richiedere invece una forza di accoppiamento così alta che potrebbe deformare le parti, rendendole ancora una volta inutilizzabili.

Il nostro file e il fattore qualità

Si, è un problema di qualità.

Il file di cui ti parlo permette di verificare proprio questi dati relativi alle condizioni di lavorazione, ed in particolare vogliamo calcolare per ogni ciclo (per i motivi detti prima) la forza massima e lo spostamento.

Così potremo certificare la qualità: se la forza impiegata per l’accoppiamento e se lo spostamento dell’asse sono congruenti ai calcoli progettuali vorrà dire che le parti erano realizzate come voluto e che erano state accoppiate completamente.

Per l’azienda di cui ti parlo questo significa essere leader mondiale nella realizzazione dei loro prodotti.

I dati che tratteremo

Ecco uno screenshot del file originale generato dalla pressa. Come puoi vedere i dati che ci interessano sono quelli a partire dalla riga 279. Ma tali dati sono annegati all’interno del file assieme a tanti altri che al momento non sono necessari ed anzi… disturbano…

Fig. 1 – Il file originale (solo la porzione che ci interessa)

Vogliamo invece arrivare ad avere dei dati “puliti” su cui eseguire i calcoli di cui parlavo prima in tempo “reale”. Così per ogni ciclo di lavorazione vorremo elaborare ad esempio la forza massima e lo spostamento massimo. Infine i dati elaborati ed aggregati li inseriremo in un database con il riferimento al file (e alla lavorazione) come puoi vedere sotto.

Fig. 2 – Il risultato finale delle trasformazioni eseguite sui dati dei diversi file

Nota che ogni file di origine contiene tantissime righe relative a diversi istanti dello stesso ciclo di lavorazione, ma il dato elaborato sarà una sola riga per lavorazione.

Le fasi principali del nostro processo

Vogliamo quindi farci un esempio delle varie fasi?

  • Il producer ad ogni fine ciclo della pressa legge il file generato dalla macchina (fig. 1). Qui avviene una piccola lavorazione dei dati “on edge” (ovvero a bordo della macchina stessa): il software legge il file, ed estrae solo le informazioni che ci interessano (ovvero la serie temporale con i dati di forza, spostamento, ecc.). Viene creato così in memoria un documento json formattato come in figura sottostante.
Fig. 3 – Il documento JSON generato in memoria dal producer
  • Il producer serializza ed inserisce nello stream di dati di Event Hubs (su un “canale” in particolare) il documento appena creato. Opzionalmente può anche caricare (ma non sarà presente nel codice) anche il file originale su un data-lake (ovvero uno storage in cloud che può essere usato per una ulteriore elaborazione batch dei dati)
  • Il sistema di streaming inizia a recapitare i messaggi contenenti il documento json al consumer (che è in ascolto in un loop continuo). Opzionalmente Event Hubs può salvare su uno storage in cloud il messaggio in transito sotto forma di file “.avro”.
  • Nota che per ragioni di finestra temporale al consumer arriveranno più messaggi alla volta e ricorda: ogni messaggio contiene tutti i dati temporali del file della singola lavorazione, non la singola riga del file di lavorazione.
  • Il consumer eseguirà le seguenti trasformazioni:
    • Deserializzazione dei messaggi in formato testuale (avremo come risultato una “tabella” dove ogni riga contiene la stringa del documento json passato (quindi, lo ripeto, il file di lavorazione con le campionature di tutti i momenti di quella lavorazione)
    • Conversione del formato testuale (che rappresenta il documento json) in oggetto json vero e proprio con una struttura fissa.
    • Conversione del documento json in una forma tabulare contenente adesso una riga per ogni istante di campionatura, il riferimento della lavorazione e i dati per quell’istante.
    • Aggregazione dei dati raggruppati per lavorazione con calcolo del massimo della forza esercitata e massimo dello spostamento dell’asse.
    • Scrittura del dato aggregato sul database (postgres nel nostro caso)

I prossimi passi

Fino a qui abbiamo parlato di teoria, ma nel prossimo articolo vedremo insieme la pratica di questo sistema!

Alla prossima informazione!