Wie Braze Ruby im großen Maßstab nutzt

Veröffentlicht: 2022-08-18

Wenn Sie ein Ingenieur sind, der Hacker News, Developer Twitter oder andere ähnliche Informationsquellen da draußen liest, sind Sie mit ziemlicher Sicherheit auf tausend Artikel mit Titeln wie „Speed ​​of Rust vs. C“, „What Makes Node. js schneller als Java?“ oder „Warum Sie Golang verwenden sollten und wie Sie beginnen.“ Diese Artikel argumentieren im Allgemeinen, dass es diese eine bestimmte Sprache gibt, die die offensichtliche Wahl für Skalierbarkeit oder Geschwindigkeit ist – und dass das einzige, was Sie tun müssen, darin besteht, sie anzunehmen.

Während ich am College war und in meinen ersten ein oder zwei Jahren als Ingenieur, las ich diese Artikel und startete sofort ein Lieblingsprojekt, um die neue Sprache oder das Framework du jour zu lernen. Schließlich funktionierte es garantiert „global“ und „schneller als alles, was Sie je gesehen haben“, und wer kann dem widerstehen? Irgendwann fand ich heraus, dass ich für die meisten meiner Projekte keines dieser sehr spezifischen Dinge brauchte. Und im Laufe meiner Karriere wurde mir klar, dass keine Sprach- oder Framework-Wahl mir diese Dinge wirklich umsonst geben würde.

Stattdessen entdeckte ich, dass die Architektur der größte Hebel ist, wenn es darum geht, Systeme zu skalieren, nicht Sprachen oder Frameworks.

Hier bei Braze sind wir in einem immensen globalen Maßstab tätig. Und ja, wir verwenden Ruby und Rails als zwei unserer Hauptwerkzeuge dafür. Es gibt jedoch keinen Konfigurationswert „global_scale = true“, der all dies möglich macht – es ist das Ergebnis einer gut durchdachten Architektur, die tief in Anwendungen bis hin zu Bereitstellungstopologien reicht. Die Ingenieure von Braze untersuchen ständig Skalierungsengpässe und finden heraus, wie wir unser System schneller machen können, und die Antwort lautet normalerweise nicht „weg von Ruby“: Es wird mit ziemlicher Sicherheit eine Änderung der Architektur sein.

Werfen wir also einen Blick darauf, wie Braze eine durchdachte Architektur nutzt, um tatsächlich Lösungen für Geschwindigkeit und einen massiven globalen Umfang zu finden – und wo Ruby und Rails dazu passen (und nicht)!

Die Kraft der Best-in-Class-Architektur

Eine einfache Webanfrage

Aufgrund der Größenordnung, in der wir tätig sind, wissen wir, dass die Geräte, die mit den Benutzerbasen unserer Kunden verbunden sind, jeden Tag Milliarden von Webanfragen stellen, die von einem Braze-Webserver bedient werden müssen. Und selbst bei den einfachsten Websites werden Sie einen relativ komplexen Ablauf haben, der mit einer Anfrage von einem Client an den Server und zurück verbunden ist:

  1. Es beginnt damit, dass der DNS-Resolver des Clients (normalerweise sein ISP) basierend auf der Domain in der URL Ihrer Website herausfindet, zu welcher IP-Adresse er gehen soll.

  2. Sobald der Client eine IP-Adresse hat, sendet er die Anfrage an seinen Gateway-Router, der sie an den „nächsten Hop“-Router sendet (was mehrmals passieren kann), bis die Anfrage an die Ziel-IP-Adresse gelangt.

  3. Von dort aus verarbeitet das Betriebssystem auf dem Server, der die Anfrage empfängt, die Netzwerkdetails und benachrichtigt den wartenden Prozess des Webservers, dass eine eingehende Anfrage auf dem Socket/Port empfangen wurde, auf dem er lauschte.

  4. Der Webserver schreibt die Antwort (die angeforderte Ressource, vielleicht eine index.html) an diesen Socket, die rückwärts durch die Router zurück zum Client wandert.

Ziemlich kompliziertes Zeug für eine einfache Website, oder? Glücklicherweise werden viele dieser Dinge für uns erledigt (mehr dazu gleich). Aber unser System hat immer noch Datenspeicher, Hintergrundjobs, Parallelitätsprobleme und mehr, mit denen es fertig werden muss! Lassen Sie uns untersuchen, wie das aussieht.

Die ersten Systeme, die Skalierung unterstützen

DNS- und Nameserver erfordern in den meisten Fällen normalerweise keine große Aufmerksamkeit. Ihr Top-Level-Domain-Nameserver wird wahrscheinlich einige Einträge haben, um „yourwebsite.com“ den Nameservern für Ihre Domain zuzuordnen, und wenn Sie einen Dienst wie Amazon Route 53 oder Azure DNS verwenden, werden sie den Namen verarbeiten Server für Ihre Domain (z. B. Verwalten von A-, CNAME- oder anderen Arten von Datensätzen). Sie müssen normalerweise nicht daran denken, diesen Teil zu skalieren, da dies automatisch von den von Ihnen verwendeten Systemen gehandhabt wird.

Der Routing-Teil des Flusses kann jedoch interessant werden. Es gibt einige verschiedene Routing-Algorithmen, wie Open Shortest Path First oder Routing Information Protocol, die alle darauf ausgelegt sind, die schnellste/kürzeste Route vom Client zum Server zu finden. Da das Internet effektiv ein riesiger verbundener Graph (oder alternativ ein Flussnetzwerk) ist, können mehrere Wege genutzt werden, die jeweils mit entsprechenden höheren oder niedrigeren Kosten verbunden sind. Es wäre unerschwinglich, die Arbeit zu leisten, um die absolut schnellste Route zu finden, daher verwenden die meisten Algorithmen vernünftige Heuristiken, um eine akzeptable Route zu erhalten. Computer und Netzwerke sind nicht immer zuverlässig, daher verlassen wir uns auf Fastly, um die Fähigkeit unserer Kunden zu verbessern, schneller zu unseren Servern zu routen.

Fastly funktioniert, indem es Points-of-Presence (POPs) auf der ganzen Welt mit sehr schnellen, zuverlässigen Verbindungen zwischen ihnen bereitstellt. Betrachten Sie sie als die Autobahn des Internets. Die A- und CNAME-Einträge unserer Domains verweisen auf Fastly, was dazu führt, dass die Anfragen unserer Kunden direkt an die Autobahn gehen. Von dort aus kann Fastly sie an die richtige Stelle weiterleiten.

Die Haustür zum Löten

Okay, die Anfrage unseres Kunden ist also über den Fastly Highway gegangen und liegt direkt vor der Haustür der Braze-Plattform – was passiert als nächstes?

In einem einfachen Fall wäre diese Vordertür ein einzelner Server, der Anfragen entgegennimmt. Wie Sie sich vorstellen können, würde das nicht sehr gut skalieren, also verweisen wir Fastly tatsächlich auf eine Reihe von Load Balancern. Es gibt alle Arten von Strategien, die Load Balancer verwenden können, aber stellen Sie sich vor, dass Fastly in diesem Szenario Anfragen gleichmäßig an einen Pool von Load Balancern verteilt. Diese Load Balancer stellen Anfragen in eine Warteschlange und verteilen diese Anfragen dann an Webserver, von denen wir uns auch vorstellen können, dass sie Client-Anfragen im Round-Robin-Verfahren behandelt werden. (In der Praxis mag es Vorteile für bestimmte Arten von Affinität geben, aber das ist ein anderes Thema.)

Auf diese Weise können wir die Anzahl der Load Balancer und die Anzahl der Webserver je nach dem Durchsatz der Anfragen, die wir erhalten, und dem Durchsatz der Anfragen, die wir verarbeiten können, skalieren. Bisher haben wir eine Architektur gebaut, die einen riesigen Ansturm von Anfragen bewältigen kann, ohne ins Schwitzen zu geraten! Dank der Elastizität der Anforderungswarteschlangen von Load Balancern kann es sogar stoßartige Datenverkehrsmuster bewältigen – was großartig ist!

Die Webserver

Schließlich kommen wir zum spannenden (Ruby-)Teil: Der Webserver. Wir verwenden Ruby on Rails, aber das ist nur ein Web-Framework – der eigentliche Webserver ist Unicorn. Unicorn funktioniert, indem es eine Reihe von Worker-Prozessen auf einer Maschine startet, wobei jeder Worker-Prozess auf einem OS-Socket auf Arbeit lauscht. Es übernimmt das Prozessmanagement für uns und verschiebt den Lastausgleich von Anfragen an das Betriebssystem selbst. Wir brauchen nur unseren Ruby-Code, um die Anfragen so schnell wie möglich zu bearbeiten; alles andere wird außerhalb von Ruby effektiv für uns optimiert.

Da die Mehrheit der Anfragen, die entweder von unserem SDK innerhalb der Anwendungen unserer Kunden oder über unsere REST-API gestellt werden, asynchron sind (dh wir müssen nicht warten, bis der Vorgang abgeschlossen ist, um eine bestimmte Antwort an die Kunden zurückzugeben), ist die Mehrheit unserer API-Server sind außerordentlich einfach – sie validieren die Struktur der Anfrage, alle API-Schlüsseleinschränkungen, werfen die Anfrage dann in eine Redis-Warteschlange und geben eine 200-Antwort an den Client zurück, wenn alles in Ordnung ist.

Dieser Anforderungs-/Antwortzyklus dauert etwa 10 Millisekunden, bis Ruby-Code verarbeitet ist – und ein Teil davon wird für das Warten auf Memcached und Redis aufgewendet. Selbst wenn wir das alles in einer anderen Sprache umschreiben würden, ist es nicht wirklich möglich, viel mehr Leistung daraus herauszuholen. Und letztendlich ist es die Architektur von allem, was Sie bisher gelesen haben, die es uns ermöglicht, diesen Datenaufnahmeprozess zu skalieren, um den ständig wachsenden Anforderungen unserer Kunden gerecht zu werden.

Die Jobwarteschlangen

Dies ist ein Thema, das wir in der Vergangenheit untersucht haben, daher werde ich nicht so tief in diesen Aspekt einsteigen. Um mehr über unser Job-Warteschlangensystem zu erfahren, lesen Sie meinen Beitrag zum Erreichen von Resilienz mit Warteschlangen. Auf hoher Ebene nutzen wir zahlreiche Redis-Instanzen, die als Auftragswarteschlangen fungieren und die zu erledigende Arbeit weiter puffern. Ähnlich wie unsere Webserver sind diese Instanzen auf Verfügbarkeitszonen aufgeteilt – um im Falle eines Problems in einer bestimmten Verfügbarkeitszone eine höhere Verfügbarkeit zu bieten – und sie kommen in primären/sekundären Paaren mit Redis Sentinel für Redundanz. Wir können diese auch horizontal und vertikal skalieren, um sowohl die Kapazität als auch den Durchsatz zu optimieren.

Die Arbeiter

Dies ist sicherlich der interessanteste Teil – wie bringen wir Mitarbeiter dazu, zu skalieren?

Zuallererst sind unsere Mitarbeiter und Warteschlangen nach einer Reihe von Dimensionen segmentiert: Kunden, Arten von Arbeit, benötigte Datenspeicher usw. Dies ermöglicht uns eine hohe Verfügbarkeit; Wenn beispielsweise ein bestimmter Datenspeicher Schwierigkeiten hat, funktionieren andere Funktionen weiterhin einwandfrei. Es ermöglicht uns auch, Worker-Typen unabhängig von einer dieser Dimensionen automatisch zu skalieren. Am Ende sind wir in der Lage, die Arbeitskräftekapazität auf horizontal skalierbare Weise zu verwalten – das heißt, wenn wir mehr von einer bestimmten Art von Arbeit haben, können wir mehr Arbeitskräfte skalieren.

Hier ist der Punkt, an dem Sie möglicherweise erkennen, dass die Wahl der Sprache oder des Frameworks eine Rolle spielt. Letztendlich wird ein effizienterer Mitarbeiter in der Lage sein, mehr Arbeit schneller zu erledigen. Kompilierte Sprachen wie C oder Rust sind in der Regel viel schneller bei Rechenaufgaben als interpretierte Sprachen wie Ruby, und das kann bei einigen Workloads zu effizienteren Arbeitern führen. Allerdings verbringe ich viel Zeit damit, Spuren zu betrachten, und die rohe CPU-Verarbeitung macht im Gesamtbild bei Braze einen überraschend kleinen Teil davon aus. Der Großteil unserer Verarbeitungszeit wird damit verbracht, auf Antworten von Datenspeichern oder von externen Anfragen zu warten, und nicht mit dem Knacken von Zahlen. dafür brauchen wir keinen stark optimierten C-Code.

Die Datenspeicher

Bisher ist alles, was wir behandelt haben, ziemlich skalierbar. Nehmen wir uns also eine Minute Zeit und sprechen darüber, wo unsere Mitarbeiter die meiste Zeit verbringen – in Datenspeichern.

Jeder, der schon einmal Webserver oder asynchrone Worker hochskaliert hat, die eine SQL-Datenbank verwenden, ist wahrscheinlich auf ein bestimmtes Skalierungsproblem gestoßen: Transaktionen. Möglicherweise haben Sie einen Endpunkt, der sich um den Abschluss einer Bestellung kümmert, wodurch zwei FulfillmentRequests und ein PaymentReceipt erstellt werden. Wenn dies nicht alles in einer Transaktion passiert, können Sie mit inkonsistenten Daten enden. Das gleichzeitige Ausführen zahlreicher Transaktionen in einer einzigen Datenbank kann dazu führen, dass viel Zeit für Sperren oder sogar Deadlocks aufgewendet wird. Bei Braze gehen wir dieses Skalierungsproblem direkt mit den Datenmodellen selbst an, durch Objektunabhängigkeit und letztendliche Konsistenz. Mit diesen Prinzipien können wir viel Leistung aus unseren Datenspeichern herausholen.

Unabhängige Datenobjekte

Wir nutzen MongoDB bei Braze aus sehr guten Gründen stark: Es ermöglicht uns nämlich, MongoDB-Shards im Wesentlichen horizontal zu skalieren und eine nahezu lineare Steigerung von Speicher und Leistung zu erzielen. Dies funktioniert sehr gut für unsere Benutzerprofile, da sie voneinander unabhängig sind – es gibt keine JOIN-Anweisungen oder Beschränkungsbeziehungen, die zwischen Benutzerprofilen aufrechterhalten werden müssen. Wenn jeder unserer Kunden wächst oder wir neue Kunden hinzufügen (oder beides), können wir einfach neue Datenbanken und neue Shards zu bestehenden Datenbanken hinzufügen, um unsere Kapazität zu erhöhen. Wir verzichten ausdrücklich auf Funktionen wie Transaktionen mit mehreren Dokumenten, um dieses Maß an Skalierbarkeit aufrechtzuerhalten.

Abgesehen von MongoDB verwenden wir Redis häufig als temporären Datenspeicher für Dinge wie das Puffern von Analyseinformationen. Da die Quelle der Wahrheit für viele dieser Analysen in MongoDB als unabhängige Dokumente für einen bestimmten Zeitraum vorhanden ist, unterhalten wir einen horizontal skalierbaren Pool von Redis-Instanzen, die als Puffer fungieren. Bei diesem Ansatz wird die gehashte Dokument-ID in einem schlüsselbasierten Sharding-Schema verwendet, wodurch die Last aufgrund der Unabhängigkeit gleichmäßig verteilt wird. Regelmäßige Jobs leeren diese Puffer von einem horizontal skalierten Datenspeicher zu einem anderen horizontal skalierten Datenspeicher. Maßstab erreicht!

Darüber hinaus verwenden wir Redis Sentinel für diese Instanzen, genau wie wir es für die oben erwähnten Job-Warteschlangen tun. Wir setzen auch zahlreiche „Typen“ dieser Redis-Cluster für verschiedene Zwecke ein, die uns einen kontrollierten Fehlerfluss bieten (dh wenn ein bestimmter Typ von Redis-Cluster Probleme hat, sehen wir nicht, dass nicht verwandte Funktionen gleichzeitig ausfallen).

Endgültige Konsistenz

Braze nutzt auch die letztendliche Konsistenz als Grundsatz für die meisten Lesevorgänge. Dadurch können wir in den meisten Fällen das Lesen sowohl von primären als auch von sekundären Mitgliedern von MongoDB-Replikatsätzen nutzen, wodurch unsere Architektur effizienter wird. Dieses Prinzip in unserem Datenmodell ermöglicht es uns, das Caching in unserem gesamten Stack stark zu nutzen.

Wir verwenden einen mehrschichtigen Ansatz mit Memcached – im Grunde prüfen wir beim Anfordern eines Dokuments aus der Datenbank zuerst einen maschinenlokalen Memcached-Prozess mit einer sehr geringen Lebensdauer (TTL), dann prüfen wir eine entfernte Memcached-Instanz (mit eine höhere TTL), bevor Sie die Datenbank jemals direkt fragen. Dies hilft uns, Datenbanklesevorgänge für allgemeine Dokumente wie Kundeneinstellungen oder Kampagnendetails drastisch zu reduzieren. „Eventual“ mag beängstigend klingen, aber in Wirklichkeit dauert es nur ein paar Sekunden, und mit diesem Ansatz wird eine enorme Menge an Verkehr von der Quelle der Wahrheit reduziert. Wenn Sie jemals an einem Computerarchitekturkurs teilgenommen haben, werden Sie vielleicht erkennen, wie ähnlich dieser Ansatz der Funktionsweise eines L1-, L2- und L3-Cachesystems von CPUs ist!

Mit diesen Tricks können wir viel Leistung aus dem wohl langsamsten Teil unserer Architektur herausquetschen und ihn dann entsprechend horizontal skalieren, wenn unser Durchsatz oder unsere Kapazitätsanforderungen steigen.

Wo Ruby und Rails hineinpassen

Hier ist die Sache: Es stellt sich heraus, dass die Geschwindigkeit der Sprache oder Laufzeit viel weniger wichtig ist, als Sie vielleicht denken, wenn Sie viel Mühe darauf verwenden, eine ganzheitliche Architektur aufzubauen, in der jede Ebene horizontal gut skaliert werden kann. Das bedeutet, dass die Auswahl von Sprachen, Frameworks und Laufzeiten mit völlig anderen Anforderungen und Einschränkungen getroffen wird.

Als Braze 2011 gestartet wurde, hatten Ruby und Rails eine nachgewiesene Erfolgsbilanz darin, Teams dabei zu helfen, schnell Iterationen durchzuführen – und sie werden immer noch von GitHub, Shopify und anderen führenden Marken verwendet, weil sie dies weiterhin ermöglichen. Sie werden weiterhin von der Ruby- bzw. der Rails-Community aktiv weiterentwickelt, und beide verfügen nach wie vor über eine große Auswahl an Open-Source-Bibliotheken für eine Vielzahl von Anforderungen. Das Paar ist eine großartige Wahl für schnelle Iterationen, da es ein immenses Maß an Flexibilität bietet und ein erhebliches Maß an Einfachheit für gängige Anwendungsfälle beibehält. Wir stellen fest, dass dies jeden Tag, an dem wir es verwenden, überwältigend wahr ist.

Das soll jetzt nicht heißen, dass Ruby on Rails eine perfekte Lösung ist, die für jeden gut funktionieren wird. Aber bei Braze haben wir festgestellt, dass es sehr gut funktioniert, um einen großen Teil unserer Datenaufnahme-Pipeline, Nachrichtenversand-Pipeline und unseres kundenorientierten Dashboards zu betreiben, die alle eine schnelle Iteration erfordern und für den Erfolg von Braze von zentraler Bedeutung sind Plattform insgesamt.

Wenn wir Ruby nicht verwenden

Aber warte! Nicht alles, was wir bei Braze tun, ist in Ruby. Im Laufe der Jahre gab es einige Stellen, an denen wir aus verschiedenen Gründen dazu aufgerufen haben, die Dinge in Richtung anderer Sprachen und Technologien zu lenken. Werfen wir einen Blick auf drei davon, nur um einen zusätzlichen Einblick zu geben, wann wir uns auf Ruby stützen und wann nicht.

1. Absenderdienste

Wie sich herausstellt, ist Ruby nicht gut darin, ein sehr hohes Maß an gleichzeitigen Netzwerkanfragen in einem einzigen Prozess zu verarbeiten. Das ist ein Problem, denn wenn Braze Nachrichten im Namen unserer Kunden sendet, verlangen einige End-of-the-Line-Dienstanbieter möglicherweise eine Anfrage pro Benutzer. Wenn wir einen Stapel von 100 Nachrichten zum Senden bereit haben, wollen wir nicht warten, bis jede von ihnen fertig ist, bevor wir mit der nächsten fortfahren. Wir würden all diese Arbeiten viel lieber parallel erledigen.

Geben Sie unsere „Sender-Dienste“ ein – das sind zustandslose Mikrodienste, die in Golang geschrieben sind. Unser Ruby-Code im obigen Beispiel kann alle 100 Nachrichten an einen dieser Dienste senden, der alle Anfragen parallel ausführt, auf ihre Beendigung wartet und dann eine Massenantwort an Ruby zurücksendet. Diese Dienste sind wesentlich effizienter als das, was wir mit Ruby tun könnten, wenn es um gleichzeitige Vernetzung geht.

2. Stromanschlüsse

Unsere Braze Currents-Datenexportfunktion für große Datenmengen ermöglicht es Braze-Kunden, kontinuierlich Daten an einen oder mehrere unserer vielen Datenpartner zu streamen. Die Plattform wird von Apache Kafka betrieben und das Streaming erfolgt über Kafka Connectors. Sie können diese technisch in Ruby schreiben, aber der offiziell unterstützte Weg ist mit Java. Und wegen der hohen Java-Unterstützung ist das Schreiben dieser Konnektoren in Java viel einfacher als in Ruby.

3. Maschinelles Lernen

Wenn Sie jemals mit maschinellem Lernen gearbeitet haben, wissen Sie, dass Python die Sprache der Wahl ist. Die zahlreichen Pakete und Tools für maschinelles Lernen in Python stellen die entsprechende Ruby-Unterstützung in den Schatten – Dinge wie TensorFlow- und Jupyter-Notebooks sind für unser Team von entscheidender Bedeutung, und diese Arten von Tools existieren einfach nicht oder sind in der Ruby-Welt nicht gut etabliert. Dementsprechend haben wir uns bei der Entwicklung von Elementen unseres Produkts, die maschinelles Lernen nutzen, auf Python gestützt.

Wenn Sprache wichtig ist

Offensichtlich haben wir oben ein paar großartige Beispiele, bei denen Ruby nicht die ideale Wahl war. Es gibt viele Gründe, warum Sie eine andere Sprache wählen könnten – hier sind einige, die wir für besonders nützlich halten.

Neues bauen ohne Wechselkosten

Wenn Sie ein völlig neues System mit einem neuen Domänenmodell und ohne eng gekoppelte Integration mit vorhandener Funktionalität erstellen möchten, haben Sie möglicherweise die Möglichkeit, eine andere Sprache zu verwenden, wenn Sie dies wünschen. Besonders in Fällen, in denen Ihre Organisation verschiedene Möglichkeiten evaluiert, könnte ein kleineres, isoliertes Greenfield-Projekt ein großartiges reales Experiment zum Ausprobieren einer neuen Sprache oder eines neuen Frameworks sein.

Aufgabenspezifisches Sprachökosystem und Ergonomie

Einige Aufgaben sind mit einer bestimmten Sprache oder einem bestimmten Framework viel einfacher – wir mögen Rails und Grape besonders für die Entwicklung von Dashboard-Funktionen, aber maschineller Lerncode wäre ein absoluter Albtraum, um ihn in Ruby zu schreiben, da die Open-Source-Tools einfach nicht existieren. Möglicherweise möchten Sie ein bestimmtes Framework oder eine bestimmte Bibliothek verwenden, um eine Art von Funktionalität oder Integration zu implementieren, und manchmal wird Ihre Sprachwahl davon beeinflusst, da dies mit ziemlicher Sicherheit zu einer einfacheren oder schnelleren Entwicklungserfahrung führt.

Ausführungsgeschwindigkeit

Gelegentlich müssen Sie die reine Ausführungsgeschwindigkeit optimieren, und die verwendete Sprache wird dies stark beeinflussen. Es gibt einen guten Grund dafür, dass viele Hochfrequenz-Handelsplattformen und autonome Fahrsysteme in C++ geschrieben sind; nativ kompilierter Code kann schnell verrückt werden! Unsere Sender Services nutzen Golangs Parallelitäts-/Parallelitäts-Primitive, die aus genau diesem Grund in Ruby einfach nicht verfügbar sind.

Entwicklervertrautheit

Auf der anderen Seite bauen Sie möglicherweise etwas Isoliertes oder haben eine Bibliothek im Sinn, die Sie verwenden möchten, aber Ihre Sprachauswahl ist dem Rest Ihres Teams völlig unbekannt. Die Einführung eines neuen Projekts in Scala mit einer starken Neigung zur funktionalen Programmierung könnte eine Vertrautheitsbarriere für die anderen Entwickler in Ihrem Team darstellen, was letztendlich zu einer Wissensisolierung oder einer verringerten Nettogeschwindigkeit führen würde. Wir halten dies bei Braze für besonders wichtig, da wir großen Wert auf schnelle Iteration legen und daher dazu neigen, die Verwendung von Tools, Bibliotheken, Frameworks und Sprachen zu fördern, die in der Organisation bereits weit verbreitet sind.

Abschließende Gedanken

Wenn ich in der Zeit zurückgehen und mir eine Sache über Software-Engineering in riesigen Systemen sagen könnte, wäre es dies: Für die meisten Workloads wird Ihre Gesamtarchitektur Ihre Skalierungsgrenzen und Geschwindigkeit mehr definieren als eine Sprachauswahl. Diese Einsicht wird hier bei Braze jeden Tag bewiesen.

Ruby und Rails sind unglaubliche Tools, die, wenn sie Teil eines richtig aufgebauten Systems sind, unglaublich gut skalieren. Rails ist auch ein sehr ausgereiftes Framework und unterstützt unsere Kultur bei Braze, schnell zu iterieren und echten Kundennutzen zu schaffen. Dies macht Ruby und Rails zu idealen Werkzeugen für uns, Werkzeuge, die wir für die kommenden Jahre weiter verwenden möchten.

Interessiert an einer Arbeit bei Braze? Wir stellen für eine Vielzahl von Rollen in unseren Teams für Technik, Produktmanagement und Benutzererfahrung ein. Besuchen Sie unsere Karriereseite , um mehr über unsere offenen Stellen und unsere Kultur zu erfahren.