Questo articolo è il primo di una serie di quattro parti sulle dinamiche interne della macchina virtuale Envision: il software che esegue gli script Envision. Vedi parte 2, parte 3 e parte 4. Questa serie non copre il compilatore Envision (forse in futuro), quindi assumiamo semplicemente che lo script sia stato convertito in bytecode che la macchina virtuale Envision prende in input.

Un pipeline di ottimizzazione della supply chain copre una vasta gamma di esigenze di elaborazione dei dati: ingestione e arricchimento dei dati, estrazione delle caratteristiche, previsione probabilistica, produzione di decisioni ottimali in presenza di 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 elaborare gigabyte di dati ogni giorno, mentre i nostri account più grandi superano abbondantemente il terabyte al giorno. Poiché il pipeline di ottimizzazione di solito attende gli input giornalieri dal resto dell’elaborazione interna dei dati dell’azienda, ha solo poche ore di tempo di calcolo per ottimizzare le decisioni di domani basate sui dati di oggi.

Oltre le serie temporali

Non è difficile. Davvero. Elaborare diversi terabyte in poche ore è un obiettivo di prestazione ben alla portata di un team di abili ingegneri del software.

La sfida di Lokad è costruire tali pipeline senza ingegneri del software. Naturalmente, ci sono ingegneri del software che lavorano presso Lokad, ma stanno sviluppando strumenti e infrastrutture per l’intera azienda, non per i singoli clienti. Invece, abbiamo i nostri Supply Chain Scientist - un team di esperti di supply chain che comprendono i problemi specifici di ciascun cliente e progettano le soluzioni. In una struttura aziendale più tradizionale, questi sarebbero i product manager, coloro che ascoltano i clienti e poi comunicano agli ingegneri del software, in modo dettagliato, esattamente cosa deve essere implementato. La nostra filosofia di sviluppo e il motivo per cui abbiamo creato Envision, il nostro linguaggio di programmazione personalizzato, è che dovrebbe essere più veloce per un Supply Chain Scientist implementare la soluzione da soli, piuttosto che scrivere le specifiche per far implementare a qualcun altro.

Per raggiungere questo obiettivo senza sacrificare scala o prestazioni, ci sono tre funzionalità che Envision deve offrire gratuitamente1, senza alcuno sforzo da parte del Supply Chain Scientist.

  • La gestione della memoria deve essere automatica. Ciò include ovviamente un garbage collector, ma significa anche che Envision dovrebbe supportare set di dati di grandi dimensioni in modo trasparente. Creare un array di dieci gigabyte è il minimo: non sono nemmeno tre miliardi di numeri! È prevista la possibilità di lavorare con un set di dati più grande della memoria di una singola macchina. Infatti, Envision supporta set di dati più grandi della memoria di tutto il cluster, grazie a un’intelligente scrittura su unità NVMe.
  • Il parallelismo multi-core e multi-macchina deve essere automatico. Le operazioni parallelizzabili in modo imbarazzante dovrebbero essere distribuite su quanti più core possibile 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 essere in grado di completarsi anche se il cluster si riduce a una singola macchina. E, naturalmente, due esecuzioni di Envision dovrebbero poter essere eseguite contemporaneamente sullo stesso cluster.
  • Ci aspettiamo che Envision richieda poca infrastruttura hardware per funzionare. Molti problemi di prestazione possono essere risolti spendendo milioni di dollari in hardware di alta qualità e/o licenze server, ma preferiamo evitare una situazione in cui fare clic sul pulsante “Esegui” di uno script costa centinaia di dollari.

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

Ambiente

Envision non può essere installato o eseguito localmente. Invece, tutti gli utenti si connettono alla piattaforma online di Lokad, che fornisce un IDE basato su browser per modificare ed eseguire script. I dati e i dashboard sono anche archiviati online e accessibili tramite un’interfaccia web, così come tramite SFTP e FTPS.

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

Envision viene eseguito in modalità batch: ogni esecuzione legge l’intero set di dati di input e produce un output completo. Ciò può richiedere da 5 secondi per uno script molto semplice eseguito su pochi dati, a 30-40 minuti per uno script di grande aumento dei dati, e persino diverse ore per alcune attività di apprendimento automatico.

Altri modi di esecuzione, come l’elaborazione in streaming (ascoltare nuovi input e produrre l’output corrispondente in tempo reale) o l’accesso transazionale (leggere solo alcune righe di dati, scrivere alcune righe indietro), non sono supportati: le primitive del linguaggio e i dettagli di implementazione a basso livello coinvolti nell’esecuzione di questi modi con una buona performance sono in contrasto con quelli coinvolti nella modalità di elaborazione batch.

Struttura del cluster

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

Il cluster mantiene un’associazione M-to-N tra i workers e le missioni: un singolo worker può eseguire più script Envision contemporaneamente e se uno script viene assegnato a più workers, collaboreranno per completarlo più velocemente.

Ogni cluster ha anche uno scheduler, un’istanza più piccola di Standard_B2ms con 2× core e 8GiB di memoria. Lo scheduler fornisce i punti di API per le applicazioni esterne per inviare nuove missioni e raccogliere i risultati delle missioni terminate. È anche responsabile della distribuzione delle missioni su una o più macchine nel cluster. A seconda del carico e del grado di parallelismo disponibile per ogni script in un determinato momento, lo scheduler può aggiungere o rimuovere workers da una missione.

L’intero sistema è stato progettato per essere resiliente: una volta che una missione è 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 missione. Pertanto, la cooperazione tra più workers e le riassegnazioni di missione eseguite dallo scheduler sono ottimizzazioni delle prestazioni, non sono necessarie per il completamento della missione.

Archiviazione Blob

Lokad non utilizza database SQL per i dati dei clienti. Le soluzioni ospitate non possono gestire facilmente i dataset dei nostri clienti più grandi (solitamente si esauriscono tra 4TB e 16TB) e gestire i nostri server richiederebbe uno sforzo che preferiamo impiegare altrove.

D’altra parte, Envision viene eseguito in modalità batch, il che elimina la necessità di query più complesse di “Leggi la colonna X tra le righe L e M”: una volta che i dati di input sono stati caricati, il worker sarà in grado di indicizzare e riprocessarli se necessario.

A causa di ciò, utilizziamo Azure Blob Storage come nostro storage primario. Ci consente di archiviare oltre un petabyte a meno dell'1% del costo dei database SQL ospitati, e le prestazioni di lettura delle query rimangono in modo affidabile tra 30MB/s e 60MB/s.

Abbiamo anche reso i nostri blob immutabili: è possibile creare nuovi blob, ma non modificare quelli esistenti. Ciò garantisce che gli input di uno script non possano essere modificati durante l’esecuzione dello script e che gli output di uno script non possano essere visualizzati fino al completamento dell’esecuzione e al ritorno degli identificatori dei nuovi blob.

Per essere precisi, Lokad ha costruito un Content-Addressable Store su Azure Blob Storage. È open source e disponibile come coppia di pacchetti NuGet Lokad.ContentAddr e Lokad.ContentAddr.Azure. Conoscere l’hash dei singoli file consente a Envision di determinare che alcuni dei suoi input non sono cambiati, quindi può riutilizzare i valori calcolati conservati nella cache da una precedente esecuzione.

Distribuzione

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

In primo luogo, l’elaborazione ad alte prestazioni richiede ogni singola risorsa CPU, RAM e storage dai nostri workers, quindi non è possibile eseguire più applicazioni sulla stessa macchina.

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

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

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

Pubblicità sfacciata: stiamo assumendo ingegneri del software. Il lavoro da remoto è possibile.


  1. nulla è davvero gratuito e lo paghiamo sacrificando la capacità di Envision di agire come un linguaggio di programmazione generico. Ad esempio, la parallelizzazione automatica significa che non supporteremo mai il controllo esplicito sui thread in Envision; l’elaborazione automatica su più macchine significa che non ci sarà mai il concetto di “macchina locale” in Envision. ↩︎