Skalierbares Anwendungsdesign ohne Magie
Die Rechenleistung der Clients nutzen, für hohe Leistung bei vielen Nutzern
- Veröffentlicht
- Aktualisiert
- (erstellt )
- Geschrieben von
- Willem L. Middelkoop
- Themen
- Cloud · Daten · Design · Anleitung · iOS · lemmid-store · Programmierung · Server · Smartphone · Web · Webdesign
- Menschliche Version dieses Beitrags
- https://willem.com/de/2020-05-11_skalierbares-anwendungsdesign-ohne-magie/
- English
- Scalable application design without magic
- Nederlands
- Schaalbaar applicatieontwerp zonder magie
- Español
- Diseño de aplicaciones escalables sin magia
- Français
- Concevoir une application scalable sans magie
- Italiano
- Progettare applicazioni scalabili senza magia
- Português
- Design de aplicações escaláveis sem magia
- Papiamentu
- Desèño di aplikashon eskalabel sin magia
- Frysk
- Skaalbere applikaasje-ûntwerp sûnder magy
- Sprachen
- Aus dem englischen Original von einem Sprachmodell in zwei Durchgängen (Übersetzung und Korrektur) mit Glossar und Rechtschreibprüfung übersetzt; noch nicht von einem Muttersprachler gelesen.
Zusammenfassung
Für die Online-Bestell-App, die ich gerade baue, brauchte ich eine skalierbare Backend-Infrastruktur, die viele gleichzeitige Nutzer verkraftet. Skalierbarkeit gilt als schwieriges Problem. Oft wird sie dargestellt, als wäre sie Magie, die nur millionenschwere Firmen mit geheimen Werkzeugen beherrschen. Aber so etwas wie Magie gibt es nicht. Oder etwa doch?
Volltext
Was ist Skalierbarkeit?
Wenn du eine App baust, fängst du wahrscheinlich ganz klein an. Nur ein paar Nutzer (oder nur du, der Entwickler) arbeiten mit der App. Alles ist bestens: Die App läuft gut und schnell, und es gibt keine Probleme. (Genieße dieses Gefühl!)

In diesem frühen, kleinen Szenario kommt der einzelne Server, auf dem die App läuft, mit dem Nutzer gut zurecht. Jedes Mal, wenn der Nutzer etwas antippt oder klickt, erledigt der Server ein bisschen Arbeit. Wenn du gut programmierst, lässt sich diese Arbeit in effiziente Häppchen packen. Das ist ein typisches Beispiel für eine monolithische Software-Architektur.
Auch wenn dein Projekt oder deine App eher klein ist, kann irgendwann ein Schwung neuer Nutzer kommen. Wenn dein System hohe Lasten nicht tragen kann, geht es sehr wahrscheinlich schief.

Bei einem monolithischen App-Design sprechen alle Nutzer mit demselben Server. Alles, was sie tun (Tippen, Klicken, Eingaben), verarbeitet dieser Server. Steigt die Last, muss er viel härter arbeiten. Irgendwann merkst du, dass alles langsamer wird, weil der Server mit der Arbeit nicht mehr hinterherkommt. Das heißt, deine App skaliert nicht gut. Anders gesagt: Du hast ein Problem.
Ausfall im entscheidenden Moment
Schlechte Skalierung ist ein großes Problem, das niemand unterschätzen sollte. Die meisten Apps (und ihre Geschäftsmodelle) leben von großen Mengen bei geringem Ertrag pro Nutzer. Soll deine App ein (finanzieller) Erfolg werden, muss sie auch bei plötzlich steigendem Traffic gut laufen. Stürzt deine App ab, wenn sie plötzlich die Aufmerksamkeit der Massen bekommt, verpasst du eine Chance auf (organisches) Wachstum, die vielleicht nie wiederkommt.
Skalierbarkeit einzuplanen ist ein bisschen wie Anschnallen in deiner selbst gebauten Rakete mit experimentellen Triebwerken. Du weißt nie genau, wann sie zündet, aber wenn es so weit ist, solltest du fest sitzen (und den Flug genießen!).
Gängige Wege zu mehr Skalierbarkeit
Zum Bau skalierbarer Apps ist schon viel geschrieben worden. Gängige Wege sind:
- Mehr Serverkapazität: mehr Arbeitsspeicher, CPU-Leistung, Speicherplatz. Das funktioniert, bringt dich aber nur "bis zu einem gewissen Punkt".
- Mehr Server: Statt eines einzelnen Servers als Flaschenhals kannst du weitere Server einsetzen, die sich die Arbeit teilen. Das ist leichter gesagt als getan, denn du brauchst Load Balancing und gemeinsame Daten, und das kann knifflig sein (zum Beispiel: Wenn du einen Bestellzähler hast und zwei Server zählen, wer bestimmt dann die nächste Nummer?).
- Den Programmcode optimieren: Nimm die richtigen Werkzeuge! Wähle eine leistungsorientierte Programmiersprache und passende Serversoftware. Schau dir funktionale Programmierung an, damit dein Code asynchron auf mehreren Kernen laufen kann. Mach ihn zustandslos. Miss die Leistung. Optimiere. Jede Millisekunde, die du bei einer einzelnen Anfrage gewinnst, summiert sich schnell, wenn viel los ist.
- Die Datenbank nutzen und vom Anwendungsserver trennen: Wenn du eine Datenbank einsetzt, hol alles aus ihr heraus! Nutze Query-Caching, Indizes und Suchfunktionen. Die meisten Datenbanken bieten erprobte Clustering-Optionen. Erfinde das Rad nicht neu: Andere haben es längst perfektioniert.
Am besten ziehst du alle diese Möglichkeiten in Betracht. Auch wenn du nicht sofort weitere Server hinzufügst, solltest du deinen Code so schreiben, dass es später möglich ist. Benchmarks und Optimierung gehören fest zu deiner Arbeit und sind kein nachträglicher Einfall.
Die Rechenleistung der Clients nutzen
Wenn du alle gängigen Wege durchdacht hast, gibt es noch "eine Sache".

Daran denken nur wenige Entwickler: Das Skalierungsproblem enthält die Lösung! Bei 1000 Nutzern hast du 1000 Computer mit leistungsstarken CPUs und viel Speicher. Moderne Computer und Smartphones können immer mehr.
Du musst nur einen Weg finden, die Rechenleistung des Clients anzuzapfen. Das klingt vielleicht gemein (die Rechenleistung des Nutzers zu benutzen), aber es verschafft ihm ein viel besseres, schnelleres Erlebnis. Das Beste daran: Seine Rechenleistung wird für sein tolles Erlebnis verwendet, und dein Skalierungsproblem löst sich ganz nebenbei als glücklicher Nebeneffekt.

Wenn du die Rechenleistung des Nutzers einsetzt, hat der Server weniger zu tun. Statt dass der Server jedes Mal ein bisschen arbeitet, wenn der Nutzer tippt oder klickt, erledigt das nun größtenteils das Gerät des Nutzers. Das ist effizienter, als über WLAN oder 4G mit einem Server zu sprechen (spart Akku und Zeit).
Das erfordert ein wenig Umdenken, aber das Prinzip lässt sich auch auf Webanwendungen übertragen. Statt alles auf dem Server zu erledigen, kannst du viel Arbeit auf den Client verlagern. Suchen, Stöbern, Filtern, Navigieren und sogar das Befüllen eines Warenkorbs funktionieren mit clientseitigem JavaScript. Du - lieber Entwickler - musst Wege finden, das umzusetzen. Aber wenn es dir gelingt, hast du nahezu unbegrenzte Skalierbarkeit in der Hand.
In der Praxis: die Bestell-App für Essen
Für die Bestell-App für Essen habe ich nach Wegen gesucht, die Rechenleistung der Clients für hohe Leistung zu nutzen. Die größte Herausforderung bei Liefer- und Abholbestellungen ist, dass sie zur Essenszeit ihren Höhepunkt erreichen. Viele Leute nutzen die App ungefähr gleichzeitig. Das ist ein Rezept für Skalierungsprobleme.

Wenn du an Essensbestellungen im Netz denkst, kannst du den ganzen Ablauf in kleinere Schritte zerlegen:
- 1) Die Seite öffnen: App oder Seite laden
- 2) Die Auswahl durchstöbern: Produkte auflisten, Beschreibungen, Preise, Fotos usw. ansehen
- 3) Gezielt suchen: nach Kategorie, Produktname usw.
- 4) Ein Produkt auswählen: zur Bestellung hinzufügen
- 5) Ein Produkt anpassen: Sauce, Beilage, Topping usw. wählen
- 6) Deine Daten eingeben: Name, Telefonnummer, Lieferdetails usw.
- 7) Die Bestellung bezahlen: Zahlungsmethode wählen, Verbindung zu deiner Bank oder Kreditkarte
- 8) Die App schließen
Die meisten dieser Schritte lassen sich so programmieren, dass der Server nicht nötig ist. Nur Schritt 1 (Laden) und Schritt 7 (Bezahlen) brauchen Kontakt zur Backend-Infrastruktur. Wichtig: Die Schritte 2 bis 5 wiederholen sich oft, denn Nutzer legen meist mehrere Produkte in ihre Bestellung.

Teilt man die restliche Arbeit nach Aufgaben auf, lässt sich die Software weiter optimieren. Der "Produktserver" ist darauf ausgelegt, statische Inhalte (Texte, Preise, Bilder) auszuliefern, mit denen der Client das Stöbern aufbaut. Diesen Servertyp kannst du stark optimieren, mit Caching, HTTP/2, Komprimierung usw.
Nur Nutzer, die ihre Bestellung abschließen, werden zum "Bestellserver" weitergeleitet, der die Zahlung abwickelt. Er verbindet sich mit dem Zahlungsanbieter. Ist eine Zahlung abgeschlossen, meldet sich der Zahlungsanbieter beim Bestellserver und teilt den Zahlungsstatus mit. Das willst du vollständig (serverseitig) selbst in der Hand haben, so wie ich es getan habe, als ich früher ein Zahlungssystem entworfen habe. Die Restlast auf dem "Bestellserver" ist viel kleiner, weil nicht jeder bestellt und ein Großteil der Bestellverarbeitung im Hintergrund laufen kann.

Für maximale Skalierbarkeit kannst du ganz einfach mehrere Server einsetzen (oder ein Content Delivery Network nutzen), die die statischen Inhalte ausliefern. Für die Bestell-App verwende ich pro Restaurant einen eigenen "Produktserver". Das sorgt für hohe Leistung und kurze Ladezeiten. Die Leute merken, dass es schnell ist, manche können nicht glauben, dass das Webtechnik ist. Es ist fast magisch.
Fazit
Eine skalierbare Anwendung zu entwerfen geht ohne Magie und ohne Millionenbudget. Du musst nur gründlich nachdenken und die Chancen nutzen, die zusammen mit den Herausforderungen auftauchen.
Das Problem enthält die Lösung. Viele gleichzeitige Nutzer sind nicht nur ein Problem: Sie bringen ihre eigene Rechenleistung mit, und die ist die Lösung! Du musst sie nur nutzen.