Konzept und Umsetzung eines Hochleistungs-Voting-Systems für unter $100

19. Juli 2018 Patryk Przekwas

Mit meiner frisch erworbenen AWS-Zertifizierung in der Tasche wollte ich mein neu erlangtes Wissen gleich in der Praxis anwenden. Lange musste ich nicht warten: kurz nach der bestandenen Prüfung bat man mich und und einen Kollegen, ob wir nicht an einem kleinen AWS Cloud-Projekt arbeiten wollten.

Der Auftrag schien einfach zu sein: wir sollten ein leistungsstarkes Voting-System erstellen, das auf Serverless-Technologie in der AWS Cloud basiert.

Die Aufgabe

Wie bei den meisten Systemen dieser Art lag die eigentliche Herausforderung im Stichwort „Hochleistung“. Was genau macht ein „Hochleistungssystem“ überhaupt aus? Die Antwort: es kommt drauf an… hauptsächlich die Nutzerfrequenz; aber auch die Komplexität des Systems oder das vorhandene Budget spielen hier eine Rolle. Vor diesem Hintergrund müssen wir die Anforderungen unserer Lösung definieren. Der Anwendungsfall: eine Umfrage erstellen, die innerhalb einer bestimmten Zeit beantwortet werden muss. Sie muss eine sehr hohe Nutzerfrequenz tragen können. Zum Beispiel für eine Live-Sendung, bei der Zuschauer in einem bestimmten Zeitraum abstimmen (z.B. 10 Minuten) – die Ergebnisse werden nach der Werbung präsentiert (siehe untenstehenden Zeitplan).

Voting window

Wir wissen also, dass unser System eine große Anzahl unregelmäßiger Traffic-Spitzen in einem sehr kurzen Zeitraum handhaben muss. Für einen solchen Fall wäre eine leistungsgerechte On-Premise-Lösung pure Geldverschwendung, da das System bei geringem Traffic nicht beansprucht wird. Schauen wir uns also an, wie wir das Problem mit einem Serverless-Ansatz angehen können.

Architektur

Als Erstes müssen wir festlegen, welche Bausteine im System benötigt werden. Unsere Aufgabe war es, mit nativen AWS Komponenten zu arbeiten – die meisten davon haben auch Open-Source-Alternativen.

Front-End

Aus Sicht der Nutzer wäre eine benutzerfreundliche Schnittstelle top. Dieser Teil war ziemlich einfach dank S3 und seiner Funktion, als Bucket für die statische Website zu dienen. AWS zufolge sollte die Skalierung selbst bei starkem Traffic reibungslos verlaufen. Außerdem zahlt man nur für den eigentlichen Traffic. Die Entwicklungsumgebung ist daher sehr kostengünstig. Gewappnet mit allen Tools der modernen Front-End-Entwicklung erstellte einer meiner Kollegen recht schnell eine einfache Single-Page-Applikation in Vue.js. Ich beschreibe euch kurz, was diese App dem Nutzer zu bieten hat.

Search

Auf der URL der Anwendung werden Nutzer mit einem Input-Formular begrüßt. Die Ansicht besteht aus dem Logo des Umfragestellers, einem Username-Eingabefeld, Sende-Button und Timer.

Voting system

Nachdem das Formular ausgefüllt und abgeschickt wurde, kommt der Nutzer zu den Fragen – mit demselben Logo, einem Fragetext, einer Liste mit Antworten und einem weiteren Sende-Button. Der Sende-Button bringt den Nutzer zu den Umfrageergebnissen, welche alle fünf Sekunden aktualisiert werden, damit er den aktuellsten Stand der Umfrage sehen kann (Live-Updates werden später im Artikel beschrieben). In unserem Beispiel dauert die Umfrage 10 Minuten, mit 120 Extra-Sekunden für die Verarbeitung verspäteter Votes.

Back-End

Eben haben wir das Hosting einer Front-End-Applikation beschrieben. Doch eine statische App reicht für eine Voting-Plattform nicht aus.

Das Herzstück des Systems steckt hinter einem API-Gateway-Service. Der API-Gateway ist ein Proxy für unsere Lambda-Funktionen, die für die Validierung der Votes und den Transfer an den SQS-Service zuständig sind. Die Daten-Pipeline-Warteschlange ist wegen DynamoDB’s Beschränkungen ausschlaggebend. Der DynamoDB-Service hat einen begrenzten Durchsatz für Read & Write, welcher glücklicherweise jederzeit geändert werden kann (z.B. mit SDK).

Vor diesem Hintergrund haben wir eine Lambda-Funktion namens Orchestrator eingeführt, die für die Prüfung des aktuellen Warteschlangenstatus und für die Skalierung des Datenbankdurchsatzes (verhältnismäßig zur Anzahl Nachrichten in der Warteschlange) zuständig ist. Nach der erfolgreichen Skalierung von DynamoDB ist der Orchestrator auch für die Erstellung von Kind-Prozessen in Form von Lambda-Worker-Funktionen zuständig. Die Anzahl dieser Prozesse sollte auch im Verhältnis zur aktuellen Anzahl der Nachrichten in der Warteschlange stehen, sodass alle Abstimmungen so schnell wie möglich verarbeitet werden können. Die Logik der Lambda-Worker-Funktionen ist ganz einfach. Der erstellte Prozess zieht Nachrichten aus SQS, versucht den Transfer zu DynamoDB und entfernt die Nachrichten aus der Warteschlange, sobald sie erfolgreich transferiert wurden. Sollte der Nachrichtentransfer an die Datenbank fehl schlagen, greift die Lambda-Funktion nicht und die Nachrichten landen wieder in der Warteschlange – um von einem anderen Arbeiter verarbeitet zu werden. Das untenstehende Diagramm illustriert dieses System.

Serverless Architecture

Im Diagramm sehen wir ein Element des Systems, das wir noch nicht besprochen haben. Es gibt eine weitere Lambda-Funktion zwischen DynamoDB und S3 Bucket. Diese Funktion ist für die vorhin erwähnten Live-Updates der Umfrageergebnisse zuständig. Sie schickt wiederkehrende Anfragen an DynamoDB und transferiert die Ergebnisse an S3 Bucket. Anschließend liest ein Ajax Call die Ergebnisse von Bucket und aktualisiert die Tabelle.

Natürlich brauchen wir einen Trigger für die Orchestrator-Lambda-Funktion und die oben-genannte Lambda-Funktion, damit die Ergebnisse überhaupt eingezogen werden. Der Einfachheit halber haben wir uns für Amazon CloudWatch entschieden: jede Minute werden hiermit Lambda-Funktionen ausgelöst.

Testen

Okay, jetzt haben wir unser Voting-System etabliert und in die AWS Cloud gestellt. Nun müssen wir nur noch prüfen, ob es sich hier auch um ein „Hochleistungssystem“ handelt.

 

An dieser Stelle will ich anmerken, dass uns die Entwicklungsumgebung so weit weniger als $1 gekostet hat!

 

Für die Traffic-Simulation haben wir die Locust-Performance-Testing-Library benutzt, da diese eine kompatible Python API hat. Eine einzige t2.micro EC2 mit einer CPU und 1GB Speicher kann alle 10 Minuten mehr als 250.000 Anfragen abschicken. Wir erstellten einen AMI mit einem vorbereiteten Testskript. Das Skript lief als Daemon und schickte vordefinierte Anfragen an die URL des API-Gateways. Mit AMI konnten wir unsere Lösung einer Belastungsprobe mit mehreren Instanzen von EC2 Maschinen unterziehen. Nun waren wir bereit zu testen!

Wir begannen mit einer einzigen Maschine. In der ersten Iteration wollten wir eine Autoscaling-Funktion in DynamoDB benutzen. Das hat aber nicht wirklich geklappt. Ein sehr hoher Traffic-Peak wurde gedrosselt, und nach kurzer Zeit bekamen wir eine Fehlermeldung: „Provisioned Throughput Exception“. Und dann, nach ungefähr 10 Minuten, wurde die Datenbank skaliert. Siehe Screenshot unten.

CloudWatch

Dieses Problem bedurfte definitiv der genaueren Untersuchung – doch wir entschieden uns gegen Autoscaling und ergänzten den Lambda-Orchestrator mit einem manuellen Scaling. Wir konnten also mit den Tests fortfahren.

Nun war das Problem mit dem Scaling gelöst. Beim DynamoDB-Test mit einer einzelnen Maschine gab es keine Probleme. Zeit also für ein größeres Kaliber: zehn EC2 Instanzen, die 4.000 Anfragen pro Sekunde verschicken! Prompt kam auch schon die nächste Hürde… um genau zu sein, die Maximalgrenze gleichzeitig laufender Lambda-Funktionen in der AWS-Region. Die Standardeinstellung liegt bei 1.000 – also 1.000 gleichzeitig laufende Lambda-Funktionen in einer AWS-Region. Dieser Grenzwert kann aber erhöht werden. Man muss nur den AWS Cloud Provider mit einer kurzen Beschreibung des Vorhabens anschreiben und abwarten. Wir schickten die Anfrage ab, und am Folgetag wurden wir um Zusatzinformation gebeten, wie z.B. die Durchschnittslaufzeit einer Lambda-Funktion oder Näheres zum durchschnittlichen Speicherbedarf. Die zweite Anfrage ging prompt raus; gleichzeitig wollten wir das System mit zwei EC2-Instanzen testen.

Der Voting-Prozess schien anfangs in Ordnung zu sein. Erst nach fünf Minuten ging es richtig los – alle Umfragebalken blieben hängen. Das Problem: die vorhin erwähnte Obergrenze gleichzeitig laufender Lambda-Funktionen. Die Funktion, die für die Aktualisierung der Ergebnisse zuständig ist, war wegen der hohen Zahl der zeitgleich laufenden Lambda-Funktionen funktionsunfähig. Nach einiger Zeit werden die Ergebnisse dann doch aktualisiert – die Benutzererfahrung aber ruiniert. Zusätzlich schafft es das System kaum, alle Stimmen rechtzeitig zu zählen, weil es nur eine begrenzte Anzahl Arbeiter gibt. Die meiste Kapazität wird von Lambda-Funktionen genutzt, die direkt mit dem API Gateway in Verbindung stehen. Eine eingehende Anfrage ist gleich eine Lambda-Anrufung. Es ergab also keinen Sinn, weiter zu testen, ohne die Grenze zu erhöhen. Damit meine ich nicht, dass die Lösung nicht auch anderweitig verbessert werden kann – mehr dazu später.

Kosten

Wie erwähnt, kostet die Entwicklungsumgebung weniger als $1. Die Kosten für die EC2-Instanzen für die Tests ist auch nicht der Rede wert. Die Gesamtkosten betragen $76. In der Analyse dieser Kosten muss erwähnt werden, dass wir mehrere Millionen Anfragen verschickt hatten. Der API Gateway ist mit $32 der teuerste Teil des Systems. DynamoDB kommt auf Platz zwei mit $12 und AWS Lambda auf Platz drei mit $8.

Was haben wir gelernt?

Hinterher weiß man immer mehr. Auch bei diesem Projekt war das so. Es wäre eine gute Idee gewesen, das API Gateway herauszunehmen und die Stimmenvalidierung an die Arbeiter abzugeben. Stimmen hätten von einer statischen Website direkt an SQS geschickt und bestimmte Genehmigungen mit dem Cognito Service eingeholt werden können. So hätten wir die meisten Lambda-Funktionen als Arbeiter nutzen können. Vielleicht werden wir alle oben-genannten Verbesserungsvorschläge umsetzen und einen Folgeartikel darüber veröffentlichen.

Schlussfolgerung

AWS ist eine sehr entwicklerfreundliche Plattform. Sie bietet eine Vielzahl von Services, die sich leicht in verschiedene Lösungen integrieren lassen. Serverless-Architekturen sind auf jeden Fall einen Versuch wert. Man zahlt nur für das, was man auch benutzt: der Entwicklungsprozess ist also sehr günstig. Man kann ganz einfach klein anfangen, und die Rechnungen skalieren im Verhältnis zur Projektentwicklung und Kundenzahl.

Der Serverless-Ansatz hat aber auch einige Nachteile. Die Architekten müssen sich mit den Services auskennen, die erstellt werden sollen. Dieses Wissen ist ausschlaggebend, um unerwartete Blocker in der Umsetzung zu vermeiden. Es ist außerdem schwierig, eine solche Lösung komplett unabhängig von einem Cloud-Plattform-Anbieter zu erstellen – die Entwicklung von Lösungen, die auf mehr als einer Cloud-Plattform einsetzbar sind, ist alles andere als leicht.

Zusammenfassend kann man sagen, dass die Serverless-Architektur für ein Voting-System die beste Lösung zu sein scheint – man braucht nicht in teure Hardware investieren, die Systeme skalieren automatisch und der Entwicklungsprozess ist kostengünstig, da man nur für das zahlt, was man auch wirklich nutzt.

Zur Veröffentlichung eingereicht am 12.04.2018

Unsere Empfehlungen