Envision VM (Teil 1), Umgebung und allgemeine Architektur
Dieser Artikel ist der erste Teil einer vierteiligen Serie über die inneren Abläufe der Envision Virtual Machine: der Software, die Envision-Skripte ausführt. Siehe Teil 2, Teil 3 und Teil 4. In dieser Serie wird der Envision Compiler nicht behandelt (vielleicht ein andermal), also nehmen wir einfach an, dass das Skript irgendwie in den Bytecode umgewandelt wurde, den die Envision Virtual Machine als Eingabe nimmt.
A Supply Chain Optimization Pipeline deckt eine breite Palette von Datenverarbeitungsbedürfnissen ab: Datenerfassung und -anreicherung, Merkmalextraktion, probabilistische Prognose, das Erzeugen optimaler Entscheidungen unter Einschränkungen, Datenexporte, Analytik und Dashboard Erstellung. Die Pipeline jedes Unternehmens ist unterschiedlich, mit eigenen Eingaben, Regeln, Beschränkungen und Ausgaben. Das Datenvolumen ist groß: Nach Lokads Erfahrung müssen selbst unsere kleinsten Kunden täglich Gigabytes an Daten verarbeiten, und bei unseren größeren Kunden werden oft weit über einem Terabyte pro Tag erreicht. Da die Optimierungspipeline normalerweise auf die täglichen Eingaben aus der internen Datenverarbeitung des Unternehmens wartet, hat sie nur wenige Stunden Rechenzeit, um die Entscheidungen für morgen basierend auf den heutigen Daten zu optimieren.

Es ist nicht schwer. Wirklich nicht.
Lokads Herausforderung besteht darin, solche Pipelines ohne Softwareingenieure zu bauen. Natürlich gibt es bei Lokad Softwareingenieure, aber sie entwickeln Werkzeuge und Infrastrukturen für das gesamte Unternehmen, nicht für einzelne Kunden. Stattdessen haben wir unsere Supply Chain Scientists – ein Team von supply chain Experten, die die spezifischen Probleme jedes Kunden verstehen und Lösungen entwerfen. In einer traditionelleren Unternehmensstruktur wären das die Product Manager, die den Kunden zuhören und den Softwareingenieuren dann im Detail mitteilen, was genau implementiert werden muss. Unsere Entwicklungsphilosophie und der Grund für die Schaffung von Envision, unserer eigenen Programmiersprache, ist, dass es für einen Supply Chain Scientist schneller sein sollte, die Lösung selbst zu implementieren, als die Spezifikationen für jemand anderen zu schreiben.
Um dies zu erreichen, ohne Skalierbarkeit oder Leistung zu opfern, muss Envision drei Funktionen kostenlos mitbringen1, ohne dass der Supply Chain Scientist zusätzlichen Aufwand betreiben muss.
- Das Speichermanagement muss automatisch erfolgen. Dies beinhaltet selbstverständlich einen Garbage Collector, bedeutet aber auch, dass Envision große Datensätze transparent unterstützen sollte. Das Erstellen eines zehn-Gigabyte-Arrays ist Standard: Das sind nicht einmal drei Milliarden Zahlen! Die Fähigkeit, mit einem Datensatz zu arbeiten, der größer ist als der Speicher einer einzelnen Maschine, wird vorausgesetzt. Tatsächlich unterstützt Envision Datensätze, die größer sind als der Speicher des gesamten Clusters, indem es clever auf NVMe-Laufwerke auslagert.
- Multi-Core- und Multi-Machine-Parallelisierung muss automatisch erfolgen. Embarrassingly parallele Operationen sollten ohne menschliches Eingreifen auf so viele Kerne wie möglich im Cluster verteilt werden. Ein Skript sollte den Ausfall einer einzelnen Maschine im Cluster überstehen, ohne von vorne beginnen zu müssen. Es sollte in der Lage sein, abgeschlossen zu werden, selbst wenn der Cluster auf eine einzige Maschine reduziert wird. Und natürlich sollten zwei Envision-Ausführungen gleichzeitig im selben Cluster laufen können.
- Wir erwarten, dass Envision wenig Hardware zum Laufen benötigt. Viele Leistungsprobleme können dadurch gelöst werden, dass Millionen von Dollar für hochwertige Hardware und/oder Serverlizenzen ausgegeben werden, aber wir möchten lieber eine Situation vermeiden, in der das Klicken auf den “Run”-Button eines Skripts Hunderte von Dollar kostet.
Allgemein einsetzbare Programmiersprachen bieten diese Funktionen nicht, und obwohl sie normalerweise mit Frameworks kombiniert werden können, die dies tun (Scala + Spark, Python + Dask und andere), bleiben zu viele Tücken für den Anwender sichtbar. In diesem Sinne ist Envision eher vergleichbar mit SQL, das auf BigQuery läuft.
Umgebung
Envision kann nicht lokal installiert oder ausgeführt werden. Stattdessen verbinden sich alle Nutzer mit Lokads Online-Plattform, die eine browserbasierte IDE zum Bearbeiten und Ausführen von Skripten bereitstellt. Die Daten und Dashboards werden ebenfalls online gespeichert und über eine Weboberfläche sowie über SFTP und FTPS abgerufen.
Wenn ein Nutzer über die Weboberfläche ein Skript ausführt, wird eine Mission erstellt, die zur Ausführung an einen Envision-Cluster gesendet wird.
Envision läuft im Batch-Modus: Jede Ausführung liest die Gesamtheit der Eingangsdaten und produziert einen vollständigen Output. Dies kann zwischen 5 Sekunden für ein sehr einfaches Skript mit wenig Daten bis zu 30–40 Minuten für ein umfangreiches Datenanreicherungs-Skript dauern und sogar mehrere Stunden für einige machine learning-Aufgaben.
Weitere Ausführungsmodi, wie z. B. Stream Processing (ständiges Abhören neuer Eingaben und sofortige Erzeugung der entsprechenden Ausgabe) oder transaktionaler Zugriff (nur wenige Datenzeilen lesen und einige Zeilen zurückschreiben), werden nicht unterstützt: Die Sprachprimitive und die detaillierten Implementierungsaspekte, die erforderlich sind, um diese Modi mit anständiger Leistung auszuführen, stehen im Gegensatz zu denen der Batch-Verarbeitung.
Cluster-Struktur
Ab 2021 läuft Envision vollständig auf .NET 5, gehostet auf Ubuntu 20.04-VMs in der Microsoft Azure Cloud. Ein Cluster besteht aus zwischen 2 und 6 Standard_L32s_v2-Instanzen: 32× AMD EPYC 7551-Kerne, 256GiB Arbeitsspeicher und 4× NVMe-Laufwerke mit insgesamt 7.68TB Speicherplatz. Diese Maschinen nennen wir Workers.
Der Cluster hält eine M-zu-N-Zuordnung zwischen Workers und Missionen: Ein einzelner Worker kann mehrere Envision-Skripte gleichzeitig ausführen, und wenn ein einzelnes Skript mehreren Workern zugewiesen wird, arbeiten diese zusammen, um es schneller abzuschließen.
Jeder Cluster verfügt außerdem über einen Scheduler, einen kleineren Standard_B2ms mit 2× Kernen und 8GiB Arbeitsspeicher. Der Scheduler stellt die API-Endpunkte für externe Anwendungen bereit, um neue Missionen einzureichen und die Ergebnisse abgeschlossener Missionen abzuholen. Er ist auch dafür verantwortlich, Missionen an eine oder mehrere Maschinen im Cluster zu verteilen. Je nach Auslastung und dem Grad der Parallelität, der jedem Skript zu einem beliebigen Zeitpunkt zur Verfügung steht, kann der Scheduler Worker zu einer Mission hinzufügen oder entfernen.
Das gesamte System wurde auf Ausfallsicherheit ausgelegt: Sobald eine Mission einem Worker zugewiesen wurde, kann der überlebende Worker die Mission abschließen, selbst wenn alle anderen Worker im Cluster sowie der Scheduler offline gehen. Daher sind die Zusammenarbeit mehrerer Worker und die vom Scheduler durchgeführten Mission-Umverteilungen Leistungsoptimierungen und nicht notwendig für den Abschluss der Mission.
Blob-Speicher
Lokad verwendet keine SQL-Datenbanken für Kundendaten. Die gehosteten Lösungen können die Datensätze unserer größeren Kunden nicht einfach aufnehmen (sie stoßen in der Regel zwischen 4TB und 16TB an ihre Grenzen), und der Betrieb eigener Server würde einen Aufwand erfordern, den wir lieber woanders einsetzen.
Andererseits läuft Envision im Batch-Modus, was die Notwendigkeit komplexerer Abfragen als “Spalte X zwischen Zeile L und M lesen” überflüssig macht: Sobald die Eingangsdaten geladen wurden, kann der Worker sie nach Bedarf indexieren und erneut verarbeiten.
Aus diesem Grund verwenden wir Azure Blob Storage als unseren primären Speicher. Es ermöglicht uns, mehr als ein Petabyte zu speichern, und das zu weniger als 1% der Kosten gehosteter SQL-Datenbanken, während die Leseabfrageleistung zuverlässig zwischen 30MB/s und 60MB/s liegt.
Wir haben unsere Blobs zudem unveränderlich gemacht: Es ist möglich, neue Blobs zu erstellen, aber bestehende nicht zu modifizieren. Dies stellt sicher, dass die Eingaben eines Skripts während seiner Ausführung nicht verändert werden können und dass die Ausgaben eines Skripts erst sichtbar werden, wenn die Ausführung abgeschlossen ist und die neuen Blob-Identifikatoren zurückgegeben werden.
Genauer gesagt hat Lokad einen Content-Addressable Store auf Basis von Azure Blob Storage aufgebaut. Er ist open source und als ein Paar NuGet-Pakete Lokad.ContentAddr und Lokad.ContentAddr.Azure verfügbar. Durch Kenntnis des Hashes einzelner Dateien kann Envision feststellen, dass sich einige seiner Eingaben nicht verändert haben, sodass berechnete Werte aus einem früheren Lauf wiederverwendet werden können.
Bereitstellung
Envision verwendet keine Containerisierung (wie Docker), da die Vorteile von Containern die zusätzliche Komplexität nicht rechtfertigen.
Erstens erfordert Hochleistungsrechnen jede letzte Ressource an CPU, RAM und Speicherplatz unserer Workers, weshalb es nicht möglich ist, mehrere Anwendungen auf derselben Maschine auszuführen.
Zweitens haben wir festgestellt, dass dotnet publish
ausreicht (und in der Tat schneller ist als docker build
), um eine Anwendung zusammen mit all ihren Abhängigkeiten plattformunabhängig zu verpacken. Mit .NET 5 bietet Microsoft hervorragende plattformübergreifende Unterstützung, sodass es genügt, die Ergebnisse eines Builds buchstäblich von einer Windows-Maschine auf einen Linux-Host zu kopieren.
Schließlich vermeiden wir aktiv die schnelle Erstellung neuer Instanzen von Grund auf. Während wir Workers abschalten können, um Kosten zu senken, ist das Erstellen neuer Cluster oder das Hinzufügen weiterer Workers zu bestehenden Clustern eine finanzielle Entscheidung: Wir berechnen unseren Kunden die Ressourcenverbrauchskosten nicht, sodass wir keine Möglichkeit haben, die zusätzlichen Kosten weiterzugeben.
Nächste Woche werden wir in das Ausführungsmodell von Envision eintauchen und untersuchen, wie die zu erledigende Arbeit innerhalb des Clusters repräsentiert wird.
Schamloser Hinweis: Wir stellen Software Engineers ein. Remote-Arbeit ist möglich.
-
Nichts ist wirklich umsonst, und wir zahlen den Preis, indem wir Envisions Fähigkeit opfern, als allgemein einsetzbare Programmiersprache zu fungieren. Zum Beispiel bedeutet automatische Parallelisierung, dass wir in Envision niemals eine explizite Kontrolle über Threads unterstützen werden; automatische Multi-Machine-Verarbeitung bedeutet, dass es in Envision niemals das Konzept einer “local machine” geben wird. ↩︎