Nutze dein bestehendes System so weit wie möglich aus
(blog.danslimmon.com)- Der Postgres-Engpass einer einzelnen SaaS-Anwendung verschärfte sich, sodass die CPU-Auslastung meist bei 60–80 % lag und zeitweise auf 100 % anstieg, was zu einem kurzen Ausfall führte
- Die vertikale Skalierung durch den Wechsel auf eine größere DB-Instanz hatte bereits ihre Grenze erreicht, sodass sich mit derselben Methode zusätzliche Last kaum noch abfangen ließ
- Write Sharding und Microservices können Kapazität und operative Resilienz erhöhen, verursachen aber weiterhin Komplexitätskosten bei Backups, Monitoring, Migrationen, ORM und sogar der Netzwerktopologie
- Tatsächlich wurden über 3 Monate hinweg schwere Queries entfernt, Rails-Code optimiert, Postgres getunt und einige teure schreibgeschützte Queries auf eine Replik-Datenbank ausgelagert
- Dadurch sank die wöchentliche maximale DB-CPU-Auslastung von 90 % auf 30 %, sodass vor dem Wechsel zu einer Architektur der nächsten Generation mehr Spielraum entstand, das aktuelle System länger zu nutzen
Postgres-Engpässe und das Ende der vertikalen Skalierung
- Mit wachsender Last einer einzelnen SaaS-Anwendung wurde die Postgres-Performance zum zentralen Engpass
- Die CPU-Auslastung lag im Bereich von 60–80 %
- Mindestens einmal schoss sie auf 100 %, was einen kurzen Ausfall verursachte
- Zuvor wurde immer dann, wenn die DB ausgelastet war, auf eine größere Instanz gewechselt, um Zeit zu gewinnen
- Dadurch konnte man sich auf andere Arbeiten wie die Entwicklung neuer Features konzentrieren
- Zu diesem Zeitpunkt lief bereits die größte Instanz, sodass eine weitere vertikale Skalierung nicht mehr möglich war
Attraktive, aber teure Architektur der nächsten Generation
- Die wichtigsten diskutierten Optionen waren Write Sharding und Microservices
- Beim Write Sharding gibt es unabhängige DB-Cluster, und Daten werden je nach Partitionierungsstrategie in eine bestimmte DB geschrieben
- Potenziell lässt sich die Kapazität damit um zwei bis drei Größenordnungen erhöhen
- Bei Microservices wird der Monolith in mehrere Services aufgeteilt, wobei jeder Service seinen eigenen Datenspeicher erhält
- So kann für jeden Service ein Datenspeicher passend zur jeweiligen Workload gewählt werden
- Beide Ansätze erweitern die Optionen in Bezug auf Fehlertoleranz und operative Resilienz, aber das unmittelbare Ziel war, die DB-Performance wieder in einen kontrollierbaren Zustand zu bringen
Einmal eingeführte Komplexität erzeugt dauerhaft Kosten
- Mehr Komplexität verursacht nicht nur Implementierungskosten für die neue Struktur, sondern auch fortlaufende Aufmerksamkeitskosten
- Wenn man sich für DB-Sharding entscheidet, muss man diese neue Komplexität bei jeder künftigen technischen Entscheidung mitdenken
- Backups
- Monitoring
- Migrationen
- ORM
- Netzwerktopologie
- Auch Microservices schaffen eine ähnliche Belastung, und das Pflegen zusätzlicher Architektur kann die Auslieferung von Features verzögern oder ganz verhindern
Zuerst Spielraum im bestehenden System finden
- Selbst wenn eine große Architekturänderung nötig erscheint, steckt im bestehenden System oft noch zusätzlicher Spielraum
- Schon durch Anpassung der Workload, Performance-Tuning und das Ergänzen unterstützender Systeme lassen sich Monate oder Jahre gewinnen
- Wenn solche Optionen praktikabel sind, lohnt es sich, sie vor dem Aufbau eines Systems der nächsten Generation zuerst auszuprobieren
Tatsächlich durchgeführte Optimierungen
- Im ersten Arbeitsblock kümmerten sich zwei Engineers etwa 3 Monate lang vor allem um Probleme bei der DB-Performance
- Es gab keinen einzelnen Trick
- Mit Telemetrie wurden schwere Queries gefunden
- Im Rails-Codebestand wurden die Stellen ermittelt, an denen diese Queries entstanden
- Die Queries wurden optimiert oder entfernt
- Mehrere Postgres-Einstellungen wurden getunt
- Im zweiten Arbeitsblock passten zwei andere Engineers den Codebestand so an, dass einige teure schreibgeschützte Queries auf einer Replik-Datenbank ausgeführt wurden
- Die am häufigsten auftretenden
SELECT-Queries, ausgelöst durch Polling des Web-Clients, wurden von der Haupt-DB getrennt
- Die am häufigsten auftretenden
Ergebnis und betriebliche Grundsätze
- Zusammengenommen senkten beide Maßnahmen die wöchentliche maximale CPU-Auslastung der DB von 90 % auf 30 %
- Wöchentliche maximale CPU-Auslastung: {l:90,30}
- Es entstand viel CPU-Reserve, und auch die Fähigkeit, Last von der Haupt-DB wegzunehmen, stieg deutlich
- Weil an vielen Teilen des Codebestands gearbeitet wurde und mehrere Entwickler zusammenarbeiteten, entstand außerdem verteiltes Wissen über das bestehende System
- Komplexität ist nicht immer schlecht, und irgendwann muss man zu einer komplexeren Struktur übergehen, bevor die grundlegenden Grenzen der DB-Architektur erreicht sind
- Bis dahin kann man mit dem bestehenden System, wenn man es zuerst maximal ausreizt, länger mit einem möglichst langweiligen und einfachen System arbeiten, was sowohl bei Kosten als auch bei der Praktikabilität vorteilhaft ist
1 Kommentare
Kommentare auf Hacker News
Angesichts von Datenbank-Performanceproblemen ist mein heißester Gedanke für neue Projekte: Die Hot Paths der Anwendung sollten so entworfen werden, dass sie überhaupt keine Joins verwenden.
Speicherplatz ist billig, also kann man alles denormalisieren und innerhalb von Transaktionen alles aktualisieren. Es ist wirklich erstaunlich, wie viel schneller es wird, wenn man Joins eliminiert. Ad-hoc-Analyseabfragen kann man in eine separate Analyse-Datenbank replizieren.
Zu Amazons DynamoDB habe ich gemischte Gefühle, aber der Punkt, dass man für eine sinnvolle Nutzung zuerst die Zugriffsmuster planen und das Schema erst danach festlegen muss, ist etwas, das man auch auf relationale Datenbanken übertragen kann. Inzwischen würde ich sogar sagen, dass Joins unnötig sind, sofern es nicht um Analysezwecke geht. Die großen Datenbanken haben ACID-Eigenschaften, und Speicherplatz ist absurd billig, also denormalisiert man einfach.
Um Hot Partitions zu vermeiden, ist es besser, statt Integern etwas UUID-Ähnliches zu verwenden. Das ist kein Allheilmittel und hat auch Nachteile, aber man kann sich an horizontal skalierbare, „immer ausreichend gute“ Performance gewöhnen, statt an die hervorragende Performance von Integern, die irgendwann zusammenbricht.
Ein noch heißerer Gedanke wäre, jede Spalte zu indizieren, aber das ist ein Thema für einen anderen Tag.
Aus Sicht technischer Schulden wäre es viel teurer gewesen, von Anfang an alles schnell machen zu wollen. Die Entwicklungsgeschwindigkeit wäre deutlich gesunken, und wir hätten in mehreren Krisen möglicherweise scheitern können.
Stattdessen haben wir monatlich ein paar Tausend Dollar mehr für Maschinenkosten bezahlt, als eigentlich nötig gewesen wäre, und in einer Phase, in der wir nicht genug Engineers einstellen konnten und die Opportunitätskosten der Feature-Entwicklung hoch waren, mehrere Personenmonate eingespart. Da wir im Voraus nicht wissen konnten, wo die Bottlenecks liegen würden, hätte es 10- bis 20-mal mehr Arbeit erfordert, alles von Anfang an schnell zu machen. Einige Bottlenecks waren überraschend.
Joins können im großen Maßstab schädlich sein, aber die meisten Startups haben zumindest anfangs kein Skalierungsproblem. Denormalisierung kann eine gute Optimierung sein, aber man zahlt bei jeder Änderung den Kostenfaktor, alle Kopien synchron zu halten. Irgendjemand wird einen Bug einbauen, bei dem ein denormalisiertes, nicht maßgebliches Feld nicht aktualisiert wird und Nutzer veraltete Daten sehen. Meist ist es in den Gesamtkosten günstiger, Joins zu verwenden und später etwa mit einem read-aside Cache zu optimieren, als das Schema zu verbiegen.
Umgekehrt ist es aus Performance-Sicht viel langsamer, ein Datenelement an 20 Stellen statt an einer Stelle zu schreiben; außerdem werden Abfragen enorm kompliziert und fehleranfällig. Es passiert dann, dass 18 Stellen aktualisiert werden, aber 2 vergessen werden.
Als Vorteil der Denormalisierung wird billiger Speicherplatz genannt, aber Speicherplatz ist hier das kleinste Problem. Die viel größere Bug-Oberfläche und die schlechte Schreibperformance sind die eigentlichen Probleme, und diese Schreibperformance kann leicht auch die Leseperformance untergraben.
Hohe Anwendungen tun im Grunde nur eine Sache, und alles andere unterstützt diese Sache. Die meisten Big-Tech-Unternehmen, die einem einfallen, gehören dazu. In ihrem Datenmodell gibt es nur wenige wirklich zentrale, antreibende Konzepte.
Facebook besteht im Grunde aus Menschen, Beiträgen und Anzeigen. Netflix ungefähr aus Accounts und Serien/Filmen. Bei Amazon-Produkten sind Verkäufer, Käufer und Artikel zentral, dahinter kommen vielleicht noch einige Logistikkonzepte.
Es gibt viele hohe Anwendungen, weil sie einfach sind. Sie sind viel einfacher als breite Anwendungen, die oft als „Enterprise“ bezeichnet werden. Enterprise-Software ist schlecht, weil sie schwierig ist, und genau dort liegt das am wenigsten erforschte Gebiet mit enormen Chancen. Etablierte Anbieter wie Oracle schlagen sich hier erbärmlich; und wenn man mit der Denkweise hoher Anwendungen hineingeht, wird es einem genauso ergehen.
Ratschläge wie „Verwende niemals Joins“ oder „Entwirf alles um eine einzelne Tabelle herum“ ergeben für hohe Anwendungen Sinn, sind aber für breite Anwendungen schreckliche Ratschläge. Man sieht immer wieder sehr hohe Anwendungsunternehmen kläglich scheitern, wenn sie etwas außerhalb ihrer Kernkompetenz versuchen, weil sie mit Leuten besetzt sind, die solche Ratschläge wie heilige Wahrheiten behandeln.
Dieser Rat ist für Unternehmen gedacht, die bereits erfolgreich sind, einfache Dinge tun und die tief hängenden Früchte bereits abgeerntet haben. Selbst hohe Anwendungen, die noch nicht Opfer ihres eigenen Erfolgs geworden sind, müssen ihr Datenmodell nicht zugunsten von Performance zerhacken. Nur Unternehmen, die bereits riesig erfolgreich sind und den letzten Tropfen Performance herauspressen, müssen sich solche Gedanken machen – und gerade diese Unternehmen brauchen am wenigsten Ratschläge. Ratschläge aus der Perspektive hoher Anwendungen wie „FAANG macht das, also solltest du es auch tun“ oder „Was machst du, wenn du eine Milliarde Nutzer hast?“ vergiften die Köpfe von Menschen, die Interessanteres tun wollen, als Milliarden Menschen Werbung zu zeigen.
Für große Joins gibt es Möglichkeiten, die Performance später zu beheben, etwa indem man sie in materialisierte Views verschiebt oder per ETL in einen Column Store überführt. Wenn aber jemand eine Spalte
subtotal_centsin die Modelle Order, Invoice, Payment, NotificationEmail und UserProfileRecentOrders kopiert und sie an 296 Stellen referenzieren oder aktualisieren lässt, ist der Weg zurück in einen sauberen Zustand lang.Meist entstehen Probleme bei Tabellen mit Verlaufsdatensätzen. Die Daten, die für den täglichen Betrieb nötig sind, sind nur ein winziger Teil der eigentlichen Tabelle, aber Operationen auf riesigen Tabellen werden langsam, egal welche Indizes vorhanden sind. Schon das Hinzufügen weiterer Indizes wird selbst zum Problem.
Zumindest bei traditionellen relationalen Datenbanken reicht es nicht, einfach jede Spalte zu indizieren; man muss die richtigen Mehrspaltenindizes anlegen, die auch verwendet werden können. Bei DynamoDB kann das anders sein.
Holt so viel wie möglich aus dem heraus, was ihr habt. Und nachdem ihr eine Weile alles herausgequetscht habt, ändert die Art, wie ihr auf das Problem schaut: Wenn man hier, hier und auch noch hier etwas herausholt, merkt man plötzlich, dass noch viel übrig ist.
Wir haben einen riesigen Monolithen etwa zwei Monate lang optimiert. Aus einem Zustand unter 2.000 RPS, bei dem PM und Team meinten, da sei nichts mehr herauszuholen, kamen wir durch Hardwaretausch auf knapp unter 3.200 RPS; nach ein paar Tagen Nacharbeit auf 4.000 RPS, mit etwas mehr Aufwand auf 10.000 RPS und etwa eine Woche später auf 40.000 RPS.
Es hieß: „Das reicht, wir müssen nicht weitergehen.“ Aber nachdem wir ziemlich viel geändert hatten, sprang es auf einer einzelnen Maschine auf über 2 Millionen RPS; einen Monat später verarbeitete sie stabil über 40 Millionen RPS bei niedriger Latenz. Ein bisschen Spielraum zum Weiterpushen gibt es immer noch.
Heute nutzen wir nicht einmal 5 % der Kapazität, die sich herausholen lässt. Allein dadurch, dass wir die Denkweise über das Problem geändert haben, war so eine Veränderung möglich. Der reine Umzug von alten auf neue Server brachte uns nur von 1.800 RPS auf etwas über 3.000 RPS. Zusätzliche Hardware hat das Grundproblem nicht behoben, und zusätzliche Komplexität hat das Problem nur auf später verschoben. Als wir die Art änderten, über das Problem nachzudenken, änderten sich das Problem selbst und die Antwort.
Ergänzend zu „den Monolithen in mehrere miteinander verbundene Services aufteilen, wobei jeder Service seinen eigenen Datenspeicher hat, der nach seinen Bedingungen skalieren kann“: An diesem Punkt muss man nicht alle möglichen Microservices herauslösen. Es reicht zu fragen: „Welche Aufteilung hätte den größten Effekt?“
In meinem Fall habe ich einige Zeitreihendaten aus Mongo nach Cassandra ausgelagert. Die Tabellenstruktur von Cassandra passte viel besser. Dieses Dataset hatte ein klar definiertes Schema, und Cassandra konnte die Daten deutlich effizienter packen. Die Flexibilität von JSON-Dokumenten brauchten wir an dieser Stelle nicht.
Weil diese Daten den Großteil der Gesamtdaten ausmachten, war Mongo danach in einem ziemlich zufriedenstellenden Zustand. Es brauchte genau eine Abspaltung. Technisch gesehen war es davor und danach immer noch ein Monolith; derselbe Service schrieb nur in zwei Datenbanken.
Ironischerweise wollte später irgendein praxisferner Architekt alle Daten in einem JSON-Dokumentenspeicher zusammenführen, und es gab mehrfach Diskussionen nach dem Motto: „Diesen Weg sind wir schon gegangen, und wir wissen, wo er endet.“
Vertikale Skalierung sollte die naheliegende erste Lösung sein. Das übersehen viele, und der Artikel behandelt es teilweise auch: Vertikale Skalierung ist fast wie horizontale Skalierung, ohne die Konsistenz der Datenbank aufzubrechen.
Es gibt viel Spielraum zum Ausreizen, und nur weil Performance-Probleme auftreten, muss man selten leichtfertig Anti-Patterns wie das Ignorieren von Joins oder Datenvalidierung einführen.
Wenn Entwickler EXPLAIN/ANALYZE-Ergebnisse lesen und korrektes Indexing sowie Query-Optimierung beherrschen, lassen sich unzählige Overengineering-Entscheidungen vermeiden.
Man protokolliert Queries, filtert die sehr häufig ausgeführten oder lange laufenden heraus, cached die häufigen und optimiert die schweren. Wenn man das systematisch macht, wird das System gesünder.
Nach meiner Erfahrung helfen vor allem APM, Slow-Query-Logs, Datenbank-Read/Write-Replicas sowie Partitionierung und Sharding.
Tools wie https://explainmysql.com zeigen klarer, was tatsächlich optimiert werden sollte, und bieten damit einen leichter zugänglichen Rahmen für Entwickler, die genug wissen, um eine Datenbank einzurichten, aber nicht genug, um ihre interne Nutzung zu verstehen.
Irgendjemand baut bestimmt schon ein AI-System, in das man Schema und Logs steckt und das dann wie von Zauberhand SQL zur Verbesserung ausführt. Ob ich dem trauen würde, weiß ich nicht, aber viele Unternehmen würden so etwas lieber nutzen, als einen dedizierten DBA einzustellen.
Ich weiß nicht, ob man für jede Query immer Index-Hints angeben muss. Manchmal sieht es so aus, als würde eine Query einen vorhandenen Index trotzdem nicht verwenden. Mit SQL-Ausführungsplänen könnte ich dieses Problem vermutlich besser verstehen.
Schlechten Code kann man nicht durch eine neue Architektur reparieren. Man schiebt das Problem nur eine Weile auf.
Mir fällt eines meiner Lieblingszitate ein: „Man zieht nicht mit der Armee in den Krieg, die man sich wünscht oder später gern hätte, sondern mit der Armee, die man gerade hat.“
Dass dieser Satz von Donald Rumsfeld stammt, möchte man vielleicht lieber ignorieren. Trotzdem gibt es von ihm auch gute Formulierungen wie „unknown unknowns“.
Wenn ich in Teams arbeite, denke ich oft daran. Nicht alle sind vollkommen einer Meinung oder haben dasselbe Verständnis und ein gemeinsames Ziel. Manche arbeiten vielleicht ineffizient oder auf eine Art, die ich nicht bevorzuge. Aber weil ein Team besser ist als kein Team, muss man den besten Weg finden, mit dem vorhandenen Team das Ziel zu erreichen.
Das lässt sich auch gut auf Systeme übertragen.
https://www.youtube.com/watch?v=XH_34tqxAjA
https://www.google.com/search?q=no+battle+plan+survives
Das passt eher auf uns selbst als auf Systeme.
Meiner Erfahrung nach gibt es in Web-Apps, die auf einem ORM aufbauen, beim Optimieren von Queries unglaublich viele tief hängende Früchte, wenn Datenbanklast zum Problem wird.
Über Basics wie „Gibt es ein N+1-Problem?“ hinaus erzeugen ORMs manchmal schlicht nicht die optimale Query. Ich würde keine komplexe Produktions-Web-App ohne ORM bauen wollen, aber man muss gelegentlich aus dem ORM ausbrechen können.
Man sollte die Queries profilieren, die in Produktion tatsächlich laufen und die meisten Ressourcen verbrauchen. Dann muss man sich diese Query ansehen und die Struktur der betroffenen Tabellen verstehen. Manchmal nutzt das ORM Joins, obwohl eigentlich Subqueries nötig wären, und umgekehrt. Mitunter muss man Teile der Ergebnisse voraggregieren oder WHERE-Bedingungen in komplexen Joins anpassen.
Ich habe schon Fälle gesehen, in denen eine halbwegs häufig ausgeführte, vom ORM erzeugte Query die Datenbank lahmlegte und mit ein paar kleinen Änderungen von über 20 Sekunden Laufzeit auf unter 1 Sekunde kam.
expire_on_commit=Falsesetzen.Selbst diese Option garantiert die Nutzung von RETURNING nicht; es wird verwendet, wenn der Datenbanktreiber und die Datenbank es unterstützen und das ORM diese Treiber/Datenbank-Kombination unterstützt. Das erzeugte SQL erscheint zwar in den Logs, aber es gibt keine API, um es tatsächlich zu prüfen. Daher gibt es keine Möglichkeit, die Nutzung von RETURNING in der Testsuite zu erzwingen, außer man fängt die eigenen Logs ab und parst sie. Im Pytest-Framework ist das zum Glück ziemlich einfach.
Ich mag ORMs, aber solche Dinge sind auf mehreren Ebenen frustrierend komplex. Ich verstehe auch, dass SQLAlchemy eine riesige Library ist und nicht alles einfach sein kann. Trotzdem zeigt dieses Beispiel die Trade-offs bei der Nutzung eines ORMs sehr gut.
Mir ist klar, dass es mit
insert()direkt in Core so funktioniert, wie man es möchte. Hier geht es um den Fall, dass man ORM-Objekte per.add()zu einerAsyncSessionhinzufügt.Die meiste Business-Logik lässt sich besser in der Sprache der relationalen Algebra und mit ein paar Erweiterungen ausdrücken als mit OOP.
„Die wirklichen Kosten zunehmender Komplexität, und oft die viel größeren Kosten, sind Aufmerksamkeit“ bedeutet letztlich auch kognitive Last.
Ich bin es leid, an Microservices-Systemen zu arbeiten, die immer noch Downtime haben und bei denen niemand weiß, wie das Ganze funktioniert. Meist sind es in Wirklichkeit verteilte Monolithen, bei denen Änderungen mehrere Services betreffen und in einer bestimmten Reihenfolge deployt werden müssen. Daten müssen repliziert, Aufgaben synchronisiert und Zustand geteilt werden.
https://www.youtube.com/watch?v=y8OnoxKotPQ
Wenn aber jemand das Ganze kennen muss, um das System zu reparieren oder zu ändern, ist das ein starkes Signal dafür, dass Regeln wie Single Responsibility oder die richtige Abstraktion über APIs verletzt wurden. Meiner Erfahrung nach ist das allerdings sehr verbreitet. Um eine Pipeline aus N Microservices zu debuggen, muss man oft alle N Services lokal ausführen und bauen.
Streng genommen ist das ein Monolith plus Netzwerkpartitionen und unendliche Build-/Deployment-Variabilität. Am Ende ist das meiner Ansicht nach eine extrem schwierige Arbeitsumgebung, die die Fähigkeiten jedes menschlichen Programmierers übersteigt.
Ich mag diesen Artikel. In den letzten Monaten habe ich versucht, meinem Manager dieselbe Botschaft zu vermitteln, allerdings ohne viel Erfolg.
Unser überlasteter Redshift-Cluster ist mehrfach zusammengebrochen, und da wir ihn bereits mit RA3-Nodes bis ans Maximum hochskaliert haben, stehen wir nun kurz davor, eine große Migration auf die „Next-Generation-Infrastruktur“ zu starten. Mit Next-Generation-Infrastruktur sind hier drei Redshift-Cluster gemeint, die per CDK verwaltet werden.
Die neue Infrastruktur ist deutlich komplexer als die aktuelle Konfiguration, und ich bin nicht sicher, ob sie die Silberkugel sein wird, die alle erwarten.
Das ist die langweilige Lösung. Wenn man kein hochkritisches System baut, bei dem Leben und körperliche Unversehrtheit direkt auf dem Spiel stehen, sollte sie die Standardantwort sein. Die Gesamtkosten des Betriebs sind definitiv viel niedriger.
Wenn man nicht die Ressourcen hat, ein großes redundantes System zu betreiben, habe ich viel zu oft gesehen, dass die durch das redundante System hinzugefügte Komplexität selbst zum Problem wird. Es ist besser, sich auf Einfachheit zu konzentrieren.
Wenn man viele zusätzliche Leute braucht, um die Komplexität zu unterstützen, es aber aus Kosten- und Risikobewertungssicht nicht nötig ist, ist die einfachere Variante viel besser. Ich habe zwar Fälle gesehen, in denen am Ende ein riesiges Projekt nötig wurde, um weiterzukommen, aber manchmal ist selbst das weniger als die Summe dessen, was es gekostet hat, die Komplexität bis zu diesem Zeitpunkt weiterzutragen. Es hängt stark davon ab, was man baut.
Lösungen, die in einem System Performance-Optimierungen finden und die verbleibende Leistung herauspressen, machen wirklich Spaß
Ich muss an Richard L. Sites’ Buch Understanding_Software_Dynamics denken. Es lehrt, wie man Latenzprobleme misst und behebt – und wie viel Einsparung eine Verringerung der Latenz in großem Maßstab bringen kann
Solche Probleme zu messen und darüber zu schließen ist schwierig, aber die Lösungen sind oft simpel. Zum Beispiel heißt es auf Seite 9: „[a] simple change paid for 10 years of my salary.“
Irgendwann möchte ich auch einmal eine Optimierung mit so großer Wirkung umsetzen
https://research.google/pubs/pub36575/
Allerdings gibt es dort auch viele kluge Leute um einen herum; wenn man also eine große Chance gefunden hat, gibt es meist einen Grund, warum nicht schon jemand anderes darauf angesprungen ist. Das kann technische Gründe haben oder organisatorische
Als Beispiel für Letzteres: Google belohnt diese Art von Arbeit normalerweise nicht besonders stark, außer wenn gerade Ressourcendruck besteht. Vielleicht habe ich für irgendeine Optimierung rund 100 Dollar Peer-Bonus bekommen, aber ganz sicher keine 10 % Provision, keine Beförderung und auch nicht das Recht, zehn Jahre lang nicht zur Arbeit zu kommen und trotzdem Gehalt zu beziehen. Im Allgemeinen bevorzugt das Unternehmen, dass Engineers an Umsatzwachstum arbeiten statt an Kostensenkungen. Ob diese Policy richtig ist, liegt über meiner Gehaltsstufe
Danach konnte man die Konfiguration in der Admin-Konsole nicht mehr mit ein paar Klicks weiter erhöhen, und deshalb musste man wohl wirklich den Kopf anstrengen, um das Kapazitätsproblem zu lösen. Hätte man diesen bestimmten Teil des Codes früher optimiert, wäre eine derart große Instanzkonfiguration vielleicht von Anfang an gar nicht nötig gewesen