Questo articolo è il primo di una serie di quattro parti sui meccanismi interni della macchina virtuale Envision: il software che esegue gli script di Envision. Vedi parte 2, parte 3 e parte 4. Questa serie non tratta il compilatore di Envision (forse un’altra volta), quindi supponiamo semplicemente che lo script sia stato in qualche modo convertito nel bytecode che la macchina virtuale Envision utilizza come input.

Una Supply Chain Optimization pipeline copre una vasta gamma di esigenze di elaborazione dati: acquisizione e arricchimento dei dati, estrazione delle caratteristiche, previsione probabilistica, produzione di decisioni ottimali sotto vincoli, esportazione dei dati, analisi e creazione di dashboard. Ogni pipeline aziendale è diversa, con i propri input, regole, vincoli e output. Il volume dei dati è elevato: dall’esperienza di Lokad, anche i nostri account più piccoli devono processare gigabyte di dati ogni giorno, e i nostri account più grandi superano tranquillamente il terabyte al giorno. Poiché la pipeline di ottimizzazione attende solitamente gli input giornalieri dal resto dell’elaborazione interna dei dati dell’azienda, ha solo poche ore di tempo computazionale per ottimizzare le decisioni di domani basandosi sui dati di oggi.

Oltre le serie temporali

Non è difficile. Davvero no. Processare diversi terabyte in poche ore è un obiettivo prestazionale ben alla portata di un team di ingegneri del software esperti.

La sfida di Lokad è costruire pipeline di questo tipo senza ingegneri del software. Certo, ci sono ingegneri del software che lavorano in Lokad, ma sviluppano strumenti e infrastrutture per l’intera azienda, non per singoli clienti. Invece, abbiamo i nostri Supply Chain Scientists - un team di esperti di supply chain che comprendono a fondo i problemi specifici di ciascun cliente e progettano le soluzioni. In una struttura aziendale più tradizionale, questi sarebbero i product manager, quelli che ascoltano i clienti e poi dicono agli ingegneri del software, in grande dettaglio, esattamente cosa deve essere implementato. La nostra filosofia di sviluppo, e la ragione per cui abbiamo creato Envision, il nostro linguaggio di programmazione, è che dovrebbe risultare più veloce per un Supply Chain Scientist implementare la soluzione da solo, piuttosto che scrivere le specifiche affinché qualcun altro le implementi.

Per riuscire in questo senza sacrificare la scalabilità o le prestazioni, ci sono tre caratteristiche che Envision deve offrire gratuitamente1, senza alcuno sforzo da parte del Supply Chain Scientist.

  • La gestione della memoria deve essere automatica. Ciò include ovviamente la presenza di un garbage collector, ma significa anche che Envision dovrebbe supportare dataset su larga scala in modo trasparente. Creare un array di dieci gigabyte è scontato: non sono nemmeno tre miliardi di numeri! È prevista la capacità di lavorare con un dataset più grande della memoria di una singola macchina. In effetti, Envision supporta dataset che superano la memoria dell’intero cluster, grazie ad un intelligente utilizzo dei drive NVMe.
  • Il parallelismo multi-core e multi-macchina deve essere automatico. Le operazioni facilmente parallelizzabili dovrebbero essere distribuite su quanti più core possibili nel cluster, senza intervento umano. Uno script dovrebbe sopravvivere al crash di una singola macchina nel cluster senza dover ricominciare da capo. Uno script dovrebbe completarsi anche se il cluster viene ridotto a una singola macchina. E, naturalmente, due esecuzioni di Envision dovrebbero potersi eseguire contemporaneamente sullo stesso cluster.
  • Ci aspettiamo che Envision richieda poche risorse hardware per funzionare. Molti problemi di prestazioni possono essere risolti spendendo milioni di dollari in hardware di alta gamma e/o licenze server, ma preferiremmo evitare una situazione in cui cliccare il pulsante “Run” di uno script costi centinaia di dollari.

I linguaggi di programmazione general purpose non forniscono queste funzionalità, e anche se di solito possono essere combinati con framework che lo fanno (Scala + Spark, Python + Dask, e altri), questo lascia troppi spigoli vivi esposti all’utente. In questo senso, Envision è più simile a SQL in esecuzione su BigQuery.

Ambiente

Envision non può essere installato o eseguito in locale. Invece, tutti gli utenti si connettono alla piattaforma online di Lokad, che offre un IDE basato su browser per modificare ed eseguire gli script. I dati e i dashboard sono inoltre memorizzati online e accessibili tramite un’interfaccia web, nonché tramite SFTP e FTPS.

Quando un utente esegue uno script tramite l’interfaccia web, viene creata una mission che viene inviata a un cluster Envision per l’esecuzione.

Envision funziona in modalità batch: ogni esecuzione legge l’intero insieme dei dati in input e produce un output completo. Questo può richiedere dai 5 secondi per uno script molto semplice che opera su pochi dati, a 30-40 minuti per uno script di grande arricchimento dati, e persino diverse ore per alcuni compiti di machine learning.

Altre modalità di esecuzione, come lo stream processing (ascoltare nuovi input e produrre il corrispondente output in tempo reale) o l’accesso transazionale (leggere solo poche righe di dati, scrivere alcune righe indietro) non sono supportate: i primitivi del linguaggio e i dettagli di implementazione a basso livello coinvolti nell’esecuzione di queste modalità con prestazioni decenti contrastano con quelli coinvolti nell’elaborazione in modalità batch.

Struttura del Cluster

A partire dal 2021, tutto Envision gira su .NET 5, ospitato su macchine virtuali Ubuntu 20.04 nel cloud di Microsoft Azure. Un cluster è composto da 2 a 6 istanze Standard_L32s_v2: 32× core AMD EPYC 7551, 256GiB di memoria, e 4× drive NVMe per un totale di 7.68TB di spazio di archiviazione. Chiamiamo queste macchine workers.

Il cluster mantiene un’associazione M-a-N tra workers e mission: un singolo worker può eseguire contemporaneamente diversi script Envision, e se uno script è assegnato a più workers, questi collaboreranno per terminarlo più rapidamente.

Ogni cluster dispone anche di uno scheduler, un’istanza Standard_B2ms più piccola con 2× core e 8GiB di memoria. Lo scheduler fornisce gli endpoint API per le applicazioni esterne per inviare nuove mission e raccogliere i risultati delle mission completate. È inoltre responsabile dell’invio delle mission a una o più macchine del cluster. A seconda del carico e del grado di parallelismo disponibile per ciascuno script in un dato momento, lo scheduler può aggiungere o rimuovere workers da una mission.

L’intero sistema è progettato per essere resiliente: una volta che una mission è stata assegnata a un worker, anche se tutti gli altri workers nel cluster, così come lo scheduler, vanno offline, il worker sopravvissuto sarà comunque in grado di completare la mission. In tal senso, la collaborazione tra più workers e le riassegnazioni effettuate dallo scheduler sono ottimizzazioni di performance, non sono necessarie per il completamento della mission.

Blob Storage

Lokad non utilizza database SQL per i dati dei clienti. Le soluzioni ospitate non riescono a contenere facilmente i dataset dei nostri clienti più grandi (tendono a raggiungere tra 4TB e 16TB), e gestire i nostri server richiederebbe uno sforzo che preferiremmo impiegare altrove.

D’altra parte, Envision funziona in modalità batch, il che elimina la necessità di query più complesse di “Leggere la colonna X tra le righe L e M”: una volta caricati i dati in input, il worker sarà in grado di indicizzarli e rielaborarli secondo necessità.

Per questo motivo, utilizziamo Azure Blob Storage come nostro archivio principale. Ci permette di memorizzare oltre un petabyte a meno dell'1% del costo dei database SQL ospitati, e le prestazioni delle query in lettura restano costantemente tra 30MB/s e 60MB/s.

Abbiamo reso anche i nostri blob immutabili: è possibile creare nuovi blob, ma non modificarne quelli esistenti. Questo assicura che gli input di uno script non possano essere modificati durante l’esecuzione, e che gli output di uno script non possano essere visualizzati fino a quando l’esecuzione non si completa e non restituisce gli identificatori dei nuovi blob.

Per essere precisi, Lokad ha costruito un Content-Addressable Store sopra Azure Blob Storage. È open source, ed è disponibile come una coppia di pacchetti NuGet Lokad.ContentAddr e Lokad.ContentAddr.Azure. Conoscere l’hash dei singoli file permette a Envision di determinare che alcuni dei suoi input non sono cambiati, in modo da poter riutilizzare i valori calcolati memorizzati nella cache da un’esecuzione precedente.

Distribuzione

Envision non utilizza alcuna containerizzazione (come Docker), perché i benefici dei container non giustificano la complessità aggiuntiva coinvolta.

Prima di tutto, il calcolo ad alte prestazioni richiede ogni goccia di CPU, RAM e spazio di archiviazione dai nostri workers, e quindi non è possibile eseguire diverse applicazioni sulla stessa macchina.

In secondo luogo, per impacchettare un’applicazione insieme a tutte le sue dipendenze, in modo indipendente dalla piattaforma, abbiamo ritenuto che dotnet publish sia sufficiente (e, anzi, più veloce di docker build). Con .NET 5, Microsoft offre un supporto cross-platform eccezionale, ed è sufficiente copiare letteralmente i risultati di una build da una macchina Windows a un host Linux.

Infine, creare rapidamente nuove istanze da zero è qualcosa che evitiamo attivamente. Sebbene possiamo spegnere i workers per ridurre i costi, creare nuovi cluster o aggiungere più workers a cluster esistenti è una decisione finanziaria: non addebitiamo ai nostri clienti in base all’utilizzo delle risorse, quindi non abbiamo modo di trasferire gli oneri aggiuntivi.

La prossima settimana, approfondiremo il modello di esecuzione di Envision e come il lavoro da svolgere è rappresentato all’interno del cluster.

Promozione senza pudore: stiamo assumendo software engineers. Il lavoro da remoto è possibile.


  1. nulla è veramente gratis, e lo compensiamo sacrificando la capacità di Envision di agire come un linguaggio di programmazione general purpose. Per esempio, la parallelizzazione automatica significa che non supporteremo mai il controllo esplicito dei thread in Envision; il processamento automatico su più macchine significa che non esisterà mai il concetto di “macchina locale” in Envision. ↩︎