Wir sammeln täglich mehr als zwei Milliarden analytische Ereignisse. Dank dessen können wir eine Reihe notwendiger Dinge herausfinden: ob sie mehr auf die Herzen als auf die Sterne klicken, zu welchen Stunden sie detailliertere Beschreibungen schreiben, in welchen Regionen sie häufig die grünen Knöpfe übersehen.
Das Ereigniserfassungs- und -analysesystem kann allgemein als Clickstream bezeichnet werden. Ich erzähle Ihnen etwas über die technische Seite des Clickstreams in Avito: die Anordnung von Ereignissen, deren Versand und Zustellung, Analysen und Berichte. Warum wollen Sie Ihre eigenen, wenn es Google Analytics und Yandex.Metrica gibt, die Clickstream-Entwickler das Leben verderben und warum Go-Codierer PHP nicht vergessen können?

Über mich
Dmitry Khasanov, zehn Jahre in der Webentwicklung, drei Jahre in Avito. Ich arbeite in einem Plattformteam und entwickle gemeinsame Infrastruktur-Tools. Ich liebe Hackathons .
Herausforderung
Das Geschäft erfordert ein tiefes Verständnis der Prozesse auf der Website. Wenn ich beispielsweise einen Benutzer registriere, möchte ich wissen, aus welcher Region, von welchem Gerät und über welchen Browser sich der Benutzer angemeldet hat. Wie die Formularfelder ausgefüllt werden, ob sie gesendet wurden oder der Benutzer aufgegeben hat. Und wenn Sie aufgegeben haben, bei welchem Schritt. Und wie lange es gedauert hat.
Ich würde gerne wissen, ob sie öfter auf die Schaltfläche klicken, wenn sie grün gestrichen ist. Wird der grüne Knopf in Murmansk oder in Wladiwostok Tag und Nacht von Benutzern mobiler Anwendungen oder der Website häufiger gedrückt? Benutzer, die von der Haupt- oder von der Suche kamen; wer hat vorher bei Avito gekauft oder wer ist zum ersten Mal gekommen.
Alle diese Zeichen: Betriebssystem, Benutzer-ID, Anforderungszeit, Gerät, Browser, Werte in den Feldern - müssen für die Analyse zur Verfügung gestellt werden. Sammeln, strukturieren, schnellen Zugriff auf Daten gewähren.
Darüber hinaus ist es häufig erforderlich, den Ereignisfluss aufzuteilen. Projekte müssen Maßnahmen ergreifen, wenn bestimmte Ereignisse eintreten. Auf diese Weise wird beispielsweise eine Rückmeldung erhalten, um das Modell für die Mustererkennung und automatische Moderation neu zu trainieren, und es werden Echtzeitstatistiken erstellt.
Mit Clickstream als Produkt sollte es Programmierern leicht fallen, Ereignisse aus einem Projekt zu senden, und Analysten sollten es ermöglichen, gesammelte Ereignisse zu verwalten und eine Vielzahl von Berichten zu erstellen, die Trends und unterstützende Hypothesen zeigen.
Berichte basierend auf dem Ereignisfluss.
Beispiel 1

Beispiel 2

Fertige Werkzeuge
Wir kennen Yandex Metric und Google Analytics und verwenden es für einige Aufgaben. Mit ihrer Hilfe ist es gut und schnell, analytische Daten von den Frontends zu sammeln. Um Daten aus Backends in externe Analysesysteme zu exportieren, müssen Sie jedoch schwierige Integrationen durchführen.
Mit externen Tools müssen Sie das Problem der Aufteilung des Ereignisflusses unabhängig lösen.
Analytische Informationen sind sehr wertvoll. Wir sammeln es seit Jahren und können so detailliert wissen, wie sich unsere Benutzer verhalten. Ich möchte dieses Wissen nicht mit der Außenwelt teilen.
Die Gesetzgebung verpflichtet, Daten auf dem Territorium Russlands zu speichern.
Diese Gründe reichten völlig aus, um unsere eigene Lösung als Hauptwerkzeug für die Erfassung und Verarbeitung von Analysedaten zu entwickeln.
Lösung
Ereignisse werden über einen Hochleistungstransport (Event Streaming Processing, ESP) im Speicher (Data Warehouse, DWH) ausgelöst. Basierend auf den Daten im Repository werden Analyseberichte erstellt.
Ereignis
Zentrale Einheit. An sich bedeutet es Tatsache. In der angegebenen Zeiteinheit ist etwas Konkretes passiert.
Es ist notwendig, ein Ereignis von einem anderen zu unterscheiden. Dies ist die eindeutige Kennung des Ereignisses.
Interessiert sich auch für den Zeitpunkt des Auftretens von Ereignissen. Wir senden es in jedem Fall mit einer Genauigkeit von Mikrosekunden. Bei Ereignissen, die von den Frontends eingehen, legen wir zusätzlich die Zeit auf dem Clientgerät fest, um die Abfolge der Aktionen genauer wiederherzustellen.
Das Feld
Ein Ereignis besteht aus Feldern. Feld ist die kleinste semantische Einheit des analytischen Systems. Im vorherigen Absatz gibt es Beispiele für Felder: Ereigniskennung, Sendezeit.
Feldattribute: Typ (Zeichenfolge, Nummer, Array), obligatorisch.
Die Umwelt
Das gleiche Ereignis kann in verschiedenen Teilen des Systems auftreten: Beispielsweise ist eine Autorisierung auf der Site oder in einer mobilen Anwendung möglich. In diesem Fall senden wir dasselbe Ereignis, fügen jedoch immer die eindeutige Kennung der Ereignisquelle hinzu.
Quellen unterscheiden sich merklich voneinander. Dies können interne Dämonen und Kronen, ein Frontend- oder Backend-Service oder eine mobile Anwendung sein. Ein Teil der Felder muss mit jedem Ereignis einer bestimmten Quelle gesendet werden.
Es gibt das Konzept der "Umwelt". Dies ist eine logische Gruppierung von Ereignissen nach Quelle mit der Möglichkeit, gemeinsame Felder für alle Quellenereignisse festzulegen.
Beispiele für Umgebungen: "Backend von Service A", "Frontend von Service A", "ios-Anwendung von Service A".
Ereignisverzeichnis
Alle vorhandenen Ereignisse werden in einem Verzeichnis beschrieben, das Entwickler und Analysten bearbeiten können. Ereignisse werden logisch nach Umgebung gruppiert, jedes Ereignis hat einen Eigentümer, ein Protokoll der Änderungen im Verzeichnis wird geführt.
Derzeit beschreibt das Verzeichnis mehrere hundert Felder, mehrere zehn Umgebungen und mehr als tausend Ereignisse.
Langpack
Wir haben Folter abgelehnt und Entwickler nicht länger gezwungen, den Code für den Ereignisversand manuell zu schreiben. Stattdessen generieren wir basierend auf dem Verzeichnis eine Reihe von Dateien für jede der von der Firma unterstützten Serversprachen: PHP, Go oder Python. Ein solcher generierter Code wird als "Langpack" bezeichnet.
Die Dateien im Langpack sind so einfach wie möglich, sie kennen die Geschäftslogik der Projekte nicht. Dies ist eine Reihe von Gettern und Feldsetzern für jedes der Ereignisse und ein Code zum Senden des Ereignisses.
Für jede Umgebung wird ein Langpack erstellt. Es zerfällt in ein Paket-Repository (satis für PHP, Pypi für Python). Es wird automatisch aktualisiert, wenn Änderungen am Verzeichnis vorgenommen werden.
Sie können nicht aufhören, in PHP zu schreiben. Der Code für den Dienst, der die Langpacks generiert, ist in Go geschrieben. Das Unternehmen hat genug PHP-Projekte, daher musste ich mich an meine Lieblings-Programmiersprache mit drei Buchstaben erinnern und PHP-Code auf Go generieren. Wenn Sie ein wenig mitgerissen werden, können Sie auch Tests generieren, um den generierten Code mit diesen Tests zu testen.
Versionierung
Referenz kann bearbeitet werden. Der Code im Kampf kann nicht gebrochen werden. Wir generieren den Kampfcode basierend auf dem Verzeichnis. Gefährlich.
Nach jeder Änderung des Ereignisses wird eine neue Version im Verzeichnis erstellt. Alle jemals erstellten Versionen von Ereignissen leben für immer im Verzeichnis. Wir lösen also das Problem der Unveränderlichkeit bestimmter Ereignisse. Projekte geben immer an, mit welcher Version des Ereignisses wir arbeiten.
Wenn sich der Langpack-Code ändert (zum Beispiel gab es nur Setter, aber jetzt haben wir auch beschlossen, Getter hinzuzufügen), erstellen Sie eine neue Version des Langpacks. Sie wird auch für immer leben. Projekte fragen immer nach einer bestimmten Version des Langpacks für ihre Umgebung. Wir lösen also das Problem der Invarianz der Langpack-Schnittstelle.
Wir benutzen Semver. Die Version jedes Langpacks besteht aus drei Zahlen. Der erste ist immer Null, der zweite ist die Version des Langpack-Codes, der dritte ist das Inkrement. Die dritte Ziffer ändert sich am häufigsten nach jeder Änderung von Ereignissen.
Mit der zweistufigen Versionierung können Sie das Verzeichnis bearbeiten, ohne den Code im Kampf zu beschädigen. Es basiert auf zwei Prinzipien: Sie können nichts löschen; Sie können erstellte Entitäten nicht bearbeiten, sondern nur geänderte Kopien nebeneinander erstellen.
Transport
Im Gegensatz zu den Jungs von Badoo auf LSD haben wir nie gelernt , Dateien schön zu schreiben . Und wir glauben, dass NSQ nicht nur ein Warteschlangenserver ist , sondern auch ein Transport für Ereignisse.
Sie versteckten NSQ hinter einer kleinen Schicht Go-Code, legten mithilfe von Daemon-Sets Kollektoren für jeden Knoten im Kubernetes-Cluster an und schrieben Konsumenten, die Ereignisse zu verschiedenen Quellen hinzufügen können.
Derzeit liefert der Transport täglich rund zwei Milliarden Veranstaltungen. Unter einer solchen Last arbeiten dreißig Sammler mit einem gewissen Spielraum. Jeder verbraucht etwas mehr Prozessorkern und etwas mehr als ein Gigabyte Speicher.
Ereignisrouting
Event-Absender können Projekte sein, die innerhalb oder außerhalb unseres Clusters leben. Innerhalb von Clustern sind dies Service-Backends, Crowns, Daemons, Infrastrukturprojekte und ein Intranet. Draußen kommen Veranstaltungen von den Frontends öffentlicher Projekte, von mobilen Anwendungen und Partnerprojekten.
Um Ereignisse außerhalb des Clusters zu empfangen, verwenden wir einen Proxy. Ein gemeinsamer Einstiegspunkt mit einer kleinen Filterung des Ereignisflusses mit der Möglichkeit ihrer Anreicherung. Weiteres Versenden an den Transport gemäß dem allgemeinen Schema.
Allgemeines Routing-Schema: Jedes Ereignis kann eine Reihe von Empfängern haben. Mögliche Empfänger sind ein Shared Analytic Repository (DWH), Rebbits oder Monga-Projekte, die an bestimmten Ereignissen interessiert sind. Der letztere Fall wird beispielsweise verwendet, um die automatischen Moderationsmodelle von Anzeigen neu zu trainieren. Models hören sich bestimmte Ereignisse an und erhalten das notwendige Feedback.
Von Seiten der Projekte gibt es keine Kenntnisse über das Routing. Sie senden Ereignisse mit Langpacks, in die Adressen gängiger Sammler eingenäht sind.
Lagerung
Das Hauptereignis-Repository ist HP Vertica, einige Dutzend Terabyte. Eine Spaltenbasis mit Funktionen, die für unsere Analysten geeignet sind. Schnittstelle - Tableau für die Berichterstellung.
Es ist effizienter, Ereignisse in unserem Speicher in großen Mengen aufzuzeichnen. Vor dem Speicher befindet sich ein Puffer in Form von Mongo. Automatisch erstellte automatische Löschsammlungen für jede Stunde. Sammlungen werden mehrere Tage gespeichert, um das Korrekturlesen in Vertica neu starten zu können, wenn etwas schief geht.
Auslesen aus Puffer Mongo auf Haustier-Skripten. Skripte werden von einer Referenz geleitet. Wir versuchen, die Geschäftslogik hier nicht beizubehalten. In dieser Phase ist eine Ereignisanreicherung möglich.
Evolution
Hand tanzt im Dunkeln
Die Notwendigkeit, Ereignisse zu protokollieren, trat viel früher auf als das Bewusstsein, dass ein Verzeichnis gepflegt werden muss. Die Entwickler in jedem der Projekte haben eine Möglichkeit gefunden, Ereignisse zu senden und nach Transportmitteln zu suchen. Dies erzeugte viel Code in verschiedenen Sprachen, der in verschiedenen Projekten lag, aber ein Problem löste.
Oft lebten im Ereignisversandcode Teile der Geschäftslogik weiter. Code mit diesem Wissen kann nicht auf andere Projekte portiert werden. Beim Refactoring muss die Geschäftslogik an das Projekt zurückgegeben werden, sodass im Ereigniscode nur das angegebene Datenformat eingehalten wird.
Zu diesem Zeitpunkt gab es kein Verzeichnis der Ereignisse. Um zu verstehen, welche Ereignisse bereits protokolliert werden, welche Felder die Ereignisse hatten, war es nur möglich, in den Code zu schauen. Um zu erfahren, dass der Entwickler versehentlich aufgehört hat, Daten in das erforderliche Feld zu schreiben, war es beim Erstellen des Berichts möglich, wenn Sie dies besonders beachten.
Es gab nicht viele Ereignisse. Puffersammlungen in Mongos wurden nach Bedarf hinzugefügt. Mit zunehmender Anzahl von Ereignissen war es erforderlich, Ereignisse manuell in andere Sammlungen umzuleiten, um die erforderlichen Sammlungen zu erstellen. Die Entscheidung, das Ereignis in einer bestimmten Puffersammlung zu platzieren, wurde zum Zeitpunkt des Sendens auf der Projektseite getroffen. Der Transport war fließend, der Kunde dafür war td-agent.
Bewusst asynchron
Es wurde beschlossen, ein Verzeichnis aller vorhandenen Ereignisse zu erstellen. Wir haben den Code der Backends analysiert und einige Informationen von dort abgerufen. Wir haben die Entwickler verpflichtet, dies bei jeder Änderung des Ereigniscodes im Verzeichnis zu vermerken.
Ereignisse, die von den Frontends und von mobilen Anwendungen ankommen, wurden manuell beschrieben, wobei manchmal die erforderlichen Informationen aus dem Ereignisstrom auf Transportebene abgerufen wurden.
Entwickler wissen zu vergessen. Dies führte zu einer Desynchronisation des Verzeichnisses und des Codes, aber das Verzeichnis zeigte das allgemeine Bild.
Die Anzahl der Puffersammlungen ist erheblich gestiegen, die manuelle Arbeit zu deren Wartung hat erheblich zugenommen. Eine unersetzliche Person erschien mit einer Menge geheimer Kenntnisse über den Pufferspeicher.
Neuer Transport
Sie erstellten einen gemeinsamen Transport, ESP, der alle Ereignisübermittlungspunkte kennt. Sie machten es zu einem einzigen Empfangspunkt. Dies ermöglichte die Steuerung aller Ereignisflüsse. Projekte haben direkt auf den Pufferspeicher zugegriffen.
Erleuchteter Clickstreamismus
Basierend auf dem Verzeichnis wurden Langpacks generiert. Sie erlauben nicht die Erstellung ungültiger Ereignisse.
Einführung automatischer Überprüfungen der Richtigkeit von Ereignissen, die von Frontends und mobilen Anwendungen eingehen. In diesem Fall hören wir nicht auf, Ereignisse zu schreiben, um keine Daten zu verlieren, sondern protokollieren Fehler und signalisieren den Entwicklern.
Seltene Ereignisse in Backends, die schwer umzugestalten sind und die noch nicht über Langpacks gesendet werden, werden von einer separaten Bibliothek gemäß den Regeln aus dem Verzeichnis überprüft. Lösen Sie bei Fehlern eine Ausnahme aus, die den Rollout blockiert.
Ich habe ein System, das dazu neigt, mit dem Verzeichnis übereinzustimmen. Boni: Transparenz, Verwaltbarkeit, Geschwindigkeit der Erstellung und Änderung von Ereignissen.
Nachwort
Die Hauptschwierigkeiten und Lektionen waren organisatorisch. Es ist schwierig, Initiativen mit mehreren Teams zu verknüpfen. Es ist nicht einfach, den Code eines großen alten Projekts zu ändern. Die Fähigkeit, mit anderen Teams zu kommunizieren, Aufgaben in relativ unabhängige und durchdachte Integration mit der Möglichkeit der unabhängigen Einführung von Hilfe aufzuteilen. Clickstream-Entwickler lieben Produktteams nicht mehr, wenn die Integrationsphase einer neuen Lösung beginnt. Wenn sich die Schnittstellen ändern, wird allen Arbeit hinzugefügt.
Das Erstellen eines Verzeichnisses war eine sehr gute Idee. Er wurde zur einzigen Quelle der Wahrheit, bei Unstimmigkeiten im Code kann man sich immer an ihn wenden. Ein Großteil der Automatisierung ist an das Verzeichnis gebunden: Überprüfungen, Ereignisrouting, Codegenerierung.
Die Infrastruktur muss nichts über Geschäftslogik wissen. Anzeichen für die Entstehung der Geschäftslogik: Ereignisse ändern sich auf dem Weg vom Projekt zum Repository. Transportwechsel ohne Projektwechsel werden unmöglich. Auf der Infrastrukturseite sollte Wissen über die Zusammensetzung von Ereignissen, Feldtypen und deren Verbindlichkeit vorhanden sein. Auf der Produktseite die logische Bedeutung dieser Felder.
Es gibt immer Raum zum Wachsen. Technisch gesehen bedeutet dies eine Zunahme der Anzahl von Ereignissen, eine Verkürzung der Zeit von der Erstellung des Ereignisses bis zum Beginn der Datenaufzeichnung und den Wegfall manueller Arbeit in allen Phasen.
Es gibt ein paar mutige Ideen. Abrufen eines detaillierten Diagramms der Benutzerübergänge, Konfigurieren von Ereignissen im laufenden Betrieb, ohne den Dienst einzuführen. Aber mehr dazu in den folgenden Artikeln.
PS Ich habe auf dem Backend United # 1-Meeting zu diesem Thema gesprochen. Vinaigrette. Kann sehen
Präsentation oder Video von der Besprechung.