Veröffentlicht am 30. April 2020

Wie OpenDoor die Seitenladegeschwindigkeit durch Experimentieren mit Cloudflare und Optimizely verbessert hat

Josiah Grace
von Josiah Grace
company name

Wir bei Optimizely lieben es, innovative Anwendungsfälle unserer Kunden vorzustellen, damit die Community neue Wege und Ansätze für das Experimentieren kennenlernt. Opendoor dokumentierte und teilte einen erstaunlichen Anwendungsfall, wie sie Optimizely und Cloudflare nutzten, um inkrementell zu experimentieren und datengestützt die besten Landing Pages zu ermitteln. Nachdem der ursprüngliche Medium-Beitrag hier bei Optimizely für viel Aufregung gesorgt hat, haben wir uns an Opendoor gewandt, die uns grünes Licht für eine erneute Veröffentlichung gegeben haben. Vielen Dank an Josiah von Opendoor für den tollen Bericht!

company name

Dies ist der erste Teil einer Serie, die die Nutzung von Cloudflare Workers durch Opendoor beleuchtet. Wenn Sie zu irgendeinem Zeitpunkt direkt zum Arbeitscode springen möchten, finden Sie ihn hier (und im Abschnitt Implementierung).

Opendoor setzt Cloudflare Workers seit bald zwei Jahren in der Produktion ein. Cloudflare Workers sind eine serverlose Laufzeitumgebung, die es ermöglicht, mit ein wenig Javascript eingehende Anfragen auf CDN-Ebene abzufangen, auszuführen und zu ändern (einen hervorragenden Überblick bietet der Vortrag von Ashley Williams auf der JSConf EU). Worker sind leichtgewichtig genug, um vor jeder Anfrage ausgeführt zu werden, und bieten leistungsstarke Bausteine, um gängige Operationen wie Proxying, das Setzen von Headern und Ähnliches durchzuführen. Bei Opendoor wurden Worker ursprünglich für einen einzigen Anwendungsfall eingesetzt, nämlich für die Weiterleitung von Anfragen von opendoor.com/w/* an unsere interne WordPress-Instanz. Unser Worker ermöglicht die Bündelung der erforderlichen npm-Pakete, verfügt über eine vollständige Testabdeckung und wird von den Entwicklern bei Opendoor jeden Tag aktiv weiterentwickelt. (Bonus: Er ist in Typescript geschrieben und Opendoor ❤s Typescript). Im Laufe der Zeit haben ihr Nutzen, ihre Benutzerfreundlichkeit und ihre ständig wachsenden Funktionen zu einer explosionsartigen Zunahme von Anwendungsfällen geführt; sie werden jetzt vor jeder Anfrage an eine Opendoor-Web-Eigenschaft ausgeführt.

Cloudflare Workers versorgen unsere Landing Page-Infrastruktur und ermöglichten es uns,:

  1. A/B-Tests über die alte und die neue Infrastruktur durchzuführen
  2. A/B-Tests für zwei verschiedene Landing Page-Designs durchzuführen
  3. Mehrere A/B-Tests mit verschiedenen Varianten unseres neuen Designs durchzuführen
  4. Vereinfachung des Erlebnisses der Entwickler in der neuen Infrastruktur

Und das alles mit einem ziemlich geringen Aufwand an Experimentieren und Routing-Code. Wir hoffen, dass Sie alle genauso begeistert sind wie wir von diesem Ansatz und der Leistungsfähigkeit von Workern.

Hintergrund

Zu Beginn des Jahres 2019 waren unsere Landing Pages in die Jahre gekommen. Sie waren schwer zu iterieren und zu experimentieren, luden langsam (vor allem auf mobilen Geräten) und brauchten ein neues Design. Wir wollten diese Probleme angehen und unsere Conversion steigern (und dabei alles messen). Nach unserer ersten Untersuchung beschlossen wir, dass wir:

  1. Unsere Landing Pages mit einer neuen Infrastruktur/einem neuen Framework (optimiert für die Verbesserung der Seitengeschwindigkeit, in diesem Fall nextjs) mit dem bestehenden Design neu zu schreiben und dies auszurollen (um sicherzustellen, dass die Conversion gleich bleibt oder positiv ist).
  2. A/B-Testing des neuen Designs gegenüber dem alten Design mit der neuen Infrastruktur
  3. Führen Sie mehrere Experimente mit der erfolgreichen Variante durch, um die Conversion zu steigern.

Alle diese Schritte waren so angeordnet, dass wir zuerst unsere neue Infrastruktur validierten, dann unser neues Design für die Landing Page und schließlich mit Variationen des erfolgreichen Designs experimentierten.

Architektur

Wenn eine Anfrage eingeht, gibt es einige wichtige Schritte:

  1. Identifizieren Sie den Benutzer (oder erstellen Sie eine Identität, wenn es sich um einen neuen Benutzer handelt)
  2. Wählen Sie die Experiment-Gruppe aus, der der Benutzer zugewiesen ist (oder zuvor zugewiesen war), und erfüllen Sie seine Anfrage nach diesem Inhalt
  3. Protokollieren Sie den Benutzer und die Variation zur Verwendung in der Conversion-Analyse

Die beiden Gemeinsamkeiten bei allen Experimenten sind die Experimentierplattform(Optimizely) und eine eindeutige Benutzerkennung (die in einem First-Party-Cookie auf der Domain opendoor.com gespeichert ist). Die drei A/B-Testing-Bereiche sind Infrastruktur, Design und Variation. Jeder dieser Bereiche muss ausgerollt und gemessen werden, und entweder A oder B wird zum Sieger erklärt. Wir können diese drei Kategorien nicht mischen und aufeinander abstimmen, da es sonst zu viele Variablen gibt, die bei der Analyse isoliert werden müssen. Um diese Ziele zu erreichen, müssen wir entscheiden, wo sich die gemeinsame Infrastruktur befindet.

Unsere erste Experiment-Domäne ist die alte vs. die neue Infrastruktur. Dabei handelt es sich um zwei separat bereitgestellte Dienste. Der erste wird von unserem auf Heroku bereitgestellten Rails-Monolithen bedient, der zweite ist ein auf Kubernetes gehosteter Node-Dienst, auf dem serverseitig gerendertes Nextjs läuft - diese unterscheiden sich nach Hosting (z.B. heroku.opendoor.com vs. k8s.opendoor.com). Als Nächstes findet unser Designtest auf der ausgewählten Serving-Infrastruktur statt. Jede Designvariante wird auf demselben Hosting laufen, aber nicht viel Code gemeinsam haben. Schließlich testen wir Variationen des Siegerdesigns (Überschriften, Call-to-Action und Ähnliches). Dabei handelt es sich größtenteils um denselben Code mit bedingten Variationen. Angesichts dieser Anforderungen haben wir hier zwei Möglichkeiten.

Option 1: Experiment-Logik im Anwendungscode

Wenn sich die Logik des Experiments im Anwendungscode befindet, würde ein Infrastrukturtest nicht funktionieren, da wir warten müssten, bis ein Backend die Anfrage erfüllt hat, bevor wir bestimmen können, welche Infrastruktur für die Erfüllung der Anfrage verwendet werden soll. Bei einer neuen Infrastruktur könnten wir die Zuweisung im Anwendungscode vornehmen, aber dann könnten wir die Anfragen an unser Backend nicht zwischenspeichern, da wir auf unser Upstream zugreifen müssten, um die Gruppe für das Experiment zu bestimmen. Außerdem müssten die Entwickler während der Entwicklung Benutzerinformationen und Experiment-Gruppen vortäuschen.

Option 2: Experiment-Logik vor dem Anwendungscode

Wenn die Logik des Experiments vor dem Anwendungscode ausgeführt wird, können wir die Anfragen an unser Backend zwischenspeichern und feststellen, auf welche Infrastruktur wir zugreifen müssen, bevor wir im Anwendungscode sind. Dadurch wird die Anforderung eingeführt, dass unsere Anfrage-URL die einzige Datenabhängigkeit für das Backend ist. Diese Anforderung vereinfacht auch das Erlebnis für die Entwickler, da es nicht notwendig ist, während der Entwicklung Experiment-Gruppen zu simulieren oder vorzutäuschen. Das nextjs-Backend weiß nicht einmal, wer der Benutzer ist, es rendert ausschließlich auf der Grundlage der Anfrage-URL.

Entscheidung: Option 2

Da Cloudflare Workers vor jeder Anfrage ausgeführt werden können, verschiedene Backends abrufen, Benutzer-Cookies lesen und setzen und das Optimizely Javascript sdk laden und ausführen können, haben wir sie zur Implementierung von Option 2 verwendet. Wenn eine Anfrage eingeht, prüfen wir die Experiment-Gruppen eines Benutzers und formatieren dann eine Upstream-Anfrage mit dem Worker. Der Worker ändert den Host für einen Infrastrukturtest, den Pfad für einen Designtest und die Abfrageparameter für einen Variationstest. Da die Anfrage-URL die einzige Datenabhängigkeit ist, wird auch dies alles am Rand zwischengespeichert.

diagram

Visualisierung der gewählten Architektur (Option 2)

Implementierung

Bei jeder Anfrage nach einer Landing Page führt unser Worker folgende Schritte aus:

  1. Abgleich der Anfrage mit dem Pfad der Landing Page (GET + $landing_page_path)
  2. Analysieren Sie das Cookie "anonymous_id" (generieren Sie eine zufällige uuid, wenn sie nicht vorhanden ist)
  3. Prüfen Sie, in welcher Experiment-Gruppe der Benutzer ist (dies ist eine stabile Zuordnung auf der Grundlage der Benutzer-ID)
  4. Holen Sie den Inhalt der Url, die mit dieser Gruppe verbunden ist (und geben Sie die Antwort an den Benutzer zurück, normalerweise im Cache)
  5. Protokollieren Sie die Zuweisung zum Experiment

Ein vollständiges Arbeitsbeispiel für die Konzepte in diesem Blogbeitrag (einschließlich Beispielinfrastruktur, Design und Variationstests) ist als Cloudflare Workers-Vorlage mit den folgenden Befehlen verfügbar. Diese enthält die gesamte Konfiguration, die für die Integration von Optimizely mit einem Worker erforderlich ist (einschließlich einer funktionierenden Webpack-Konfiguration, gebündelter Bibliotheken und Cookie-Verwaltung):

Ein vereinfachtes Beispiel für den Worker-Code lautet wie folgt:

import { createInstance } from "@optimizely/optimizely-sdk"; import cookie from "cookie"; import uuid from "uuid"; addEventListener("fetch", (event) => { event.respondWith(handleLandingPageRequest(event.request)); }); async function handleLandingPageRequest(request) { const cookies = cookie.parse(request.headers.get("Cookie")) || {}; const userId = cookies["anonymous_id"] || `${v4()}`; const datafile = await fetch( "https://cdn.optimizely.com/datafiles/OPTIMIZELY_CDN_FILE.json" ).then((res) => res.json()); const optimizely = optimizely.createInstance({ datafile: datafile }); const variation = optimizelyClient.activate( "landing_page_infra_test", userId ); let response; if (variation === "control") { response = await fetch(`https://heroku.opendoor.com/design-a`); } else { response = await fetch(`https://k8s.opendoor.com/design-a`); } // Kopfzeilen veränderbar machen response = new Response(response.body, response); response.headers.set("Cookie", cookie.serialize("anonymous_id", userId)); return response; }

Wenn Sie Fragen oder Feedback haben, können Sie im verlinkten Repository ein Problem eröffnen oder sich auf Twitter(@defjosiah) melden.

Mitbringsel

Letztendlich konnten wir alle unsere geplanten A/B-Tests auf dieser neuen Landing Page-Infrastruktur erfolgreich durchführen. Die Logik von Cloudflare Worker war felsenfest (was zum Teil auf unsere Teststrategie zurückzuführen ist), und wir hatten keine Probleme mit der Produktion. Die Entwickler lieben es, mit diesem Service zu entwickeln, und das Hinzufügen neuer Funktionen und Experimente ist einfach (vor allem wegen der Anforderungen an die Request-URL). Der Schwerpunkt dieses Beitrags liegt nicht auf den tatsächlichen Conversion-Ergebnissen, aber es ist uns gelungen, die Conversion mit der neuen Infrastruktur erfolgreich zu steigern (aufgrund der Verbesserung der Seitengeschwindigkeit), ein neues Design für die Landing Page zu veröffentlichen und schließlich eine Reihe von erfolgreichen Experimenten mit Variationen durchzuführen. Im Vergleich zur Implementierung eines einzelnen Proxy-Servers (nginx oder ähnlich) mit diesen Funktionen sind Cloudflare Workers ein Kinderspiel.

Abgestimmt bleiben

Unsere gesamte Worker-Architektur, die Tests, die Bereitstellung und die Implementierung verdienen einen eigenen Blog-Beitrag. Wir verwenden Worker auch für die Bereitstellung von organischen Inhalten, die Verwaltung von First-Party-Cookies, differenzierte Antworten für Benutzer-Agenten, "serverlose" Lambda-Funktionen, Lokalisierung und die Bereitstellung von Frontend-Anwendungen. Wir möchten diese Anwendungsfälle mit Ihnen allen teilen, denn wir lieben Worker und glauben, dass Sie es auch tun werden!

Sind Sie bereit, mit dem serverseitigen Experimentieren zu beginnen? Sprechen Sie noch heute mit uns über Optimizely Full Stack oder melden Sie sich für einen kostenlosen Optimizely Rollouts Account an.