Die Geschichte unserer Open Source: Wie wir einen Analysedienst in Go erstellt und ihn öffentlich zugänglich gemacht haben

Derzeit sammelt fast jedes Unternehmen auf der Welt Statistiken über Benutzeraktionen auf einer Webressource. Die Motivation ist klar: Unternehmen wollen wissen, wie ihr Produkt/ihre Website genutzt wird und ihre Nutzer besser verstehen. Natürlich gibt es auf dem Markt eine Vielzahl von Tools zur Lösung dieses Problems – von Analysesystemen, die Daten in Form von Dashboards und Grafiken bereitstellen (z. B Google Analytics) zur Customer Data Platform, mit der Sie Daten aus verschiedenen Quellen in jedem Lager (z. B Segment).

Aber wir haben ein Problem gefunden, das noch nicht gelöst wurde. So wurde geboren EventNative — Open-Source-Analysedienst. Lesen Sie, warum wir uns entschieden haben, unseren eigenen Dienst zu entwickeln, was es uns gebracht hat und was das Endergebnis war (mit Codeteilen).

Die Geschichte unserer Open Source: Wie wir einen Analysedienst in Go erstellt und ihn öffentlich zugänglich gemacht haben

Warum sollten wir unseren eigenen Service entwickeln?

Es waren die Neunzigerjahre, wir haben überlebt, so gut wir konnten. 2019 haben wir die API First Customer Data Platform entwickelt kSense, was es ermöglichte, Daten aus verschiedenen Quellen (Facebook-Anzeigen, Stripe, Salesforce, Google Play, Google Analytics usw.) zu aggregieren, um die Datenanalyse bequemer zu gestalten, Abhängigkeiten zu identifizieren usw. Wir haben festgestellt, dass viele Nutzer unsere Plattform zur Datenanalyse nutzen, insbesondere Google Analytics (im Folgenden GA). Wir haben mit einigen Nutzern gesprochen und herausgefunden, dass sie für ihr Produkt die Analysedaten benötigen, die sie über GA erhalten, aber Google erfasst Daten Und für viele ist die GA-Benutzeroberfläche nicht der Maßstab für Komfort. Wir haben genügend Gespräche mit unseren Benutzern geführt und festgestellt, dass viele auch die Segment-Plattform nutzen (was übrigens erst neulich der Fall war). für 3.2 Milliarden US-Dollar verkauft).

Sie installierten ein Segment-Javascript-Pixel auf ihrer Webressource und Daten über das Verhalten ihrer Benutzer wurden in die angegebene Datenbank (z. B. Postgres) geladen. Aber Segment hat auch seine Kehrseite – den Preis. Wenn eine Webressource beispielsweise 90,000 MTU (monatlich verfolgte Benutzer) hat, müssen Sie etwa 1,000 $ pro Monat an die Kasse zahlen. Es gab auch ein drittes Problem: Einige Browsererweiterungen (z. B. AdBlock) blockierten die Erfassung von Analysen, weil ... HTTP-Anfragen vom Browser wurden an die GA- und Segment-Domänen gesendet. Basierend auf den Wünschen unserer Kunden haben wir einen Analysedienst geschaffen, der einen vollständigen Datensatz (ohne Stichprobenentnahme) sammelt, kostenlos ist und auf unserer eigenen Infrastruktur arbeiten kann.

So funktioniert der Dienst

Der Dienst besteht aus drei Teilen: einem Javascript-Pixel (das wir später in Typoskript umgeschrieben haben), der Serverteil ist in der GO-Sprache implementiert und es war geplant, Redshift und BigQuery als interne Datenbank zu verwenden (später wurde Unterstützung für hinzugefügt). Postgres, ClickHouse und Snowflake).

Es wurde beschlossen, die Struktur der GA- und Segmentveranstaltungen unverändert zu lassen. Alles, was nötig war, war, alle Ereignisse von der Webressource, in der das Pixel installiert ist, in unser Backend zu duplizieren. Wie sich herausstellt, ist dies nicht schwierig. Das Javascript-Pixel überschrieb die ursprüngliche GA-Bibliotheksmethode durch eine neue, wodurch das Ereignis in unserem System dupliziert wurde.

//'ga' - стандартное название переменной Google Analytics
if (window.ga) {
    ga(tracker => {
        var originalSendHitTask = tracker.get('sendHitTask');
        tracker.set('sendHitTask', (model) => {
            var payLoad = model.get('hitPayload');
            //отправка оригинального события в GA
            originalSendHitTask(model);
            let jsonPayload = this.parseQuery(payLoad);
            //отправка события в наш сервис
            this.send3p('ga', jsonPayload);
        });
    });
}

Mit dem Segmentpixel ist alles einfacher; es verfügt über Middleware-Methoden, von denen wir eine verwendet haben.


//'analytics' - стандартное название переменной Segment
if (window.analytics) {
    if (window.analytics.addSourceMiddleware) {
        window.analytics.addSourceMiddleware(chain => {
            try {
		//дублирование события в наш сервис
                this.send3p('ajs', chain.payload);
            } catch (e) {
                LOG.warn('Failed to send an event', e)
            }
	    //отправка оригинального события в Segment
            chain.next(chain.payload);
        });
    } else {
        LOG.warn("Invalid interceptor state. Analytics js initialized, but not completely");
    }
} else {
    LOG.warn('Analytics.js listener is not set.');
}

Zusätzlich zum Kopieren von Ereignissen haben wir die Möglichkeit hinzugefügt, beliebige JSON-Dateien zu senden:


//Отправка событий с произвольным json объектом
eventN.track('product_page_view', {
    product_id: '1e48fb70-ef12-4ea9-ab10-fd0b910c49ce',
    product_price: 399.99,
    price_currency: 'USD'
    product_release_start: '2020-09-25T12:38:27.763000Z'
});

Lassen Sie uns als Nächstes über den Serverteil sprechen. Das Backend sollte HTTP-Anfragen akzeptieren und diese mit zusätzlichen Informationen füllen, zum Beispiel Geodaten (danke). maxmind (zu diesem Zweck) und erfassen Sie es in der Datenbank. Wir wollten den Dienst so komfortabel wie möglich gestalten, damit er mit minimaler Konfiguration genutzt werden kann. Wir haben die Funktionalität zur Bestimmung des Datenschemas basierend auf der Struktur des eingehenden JSON-Ereignisses implementiert. Datentypen werden durch Werte definiert. Verschachtelte Objekte werden zerlegt und auf eine flache Struktur reduziert:

//входящий json
{
  "field_1":  {
    "sub_field_1": "text1",
    "sub_field_2": 100
  },
  "field_2": "text2",
  "field_3": {
    "sub_field_1": {
      "sub_sub_field_1": "2020-09-25T12:38:27.763000Z"
    }
  }
}

//результат
{
  "field_1_sub_field_1":  "text1",
  "field_1_sub_field_2":  100,
  "field_2": "text2",
  "field_3_sub_field_1_sub_sub_field_1": "2020-09-25T12:38:27.763000Z"
}

Allerdings werden Arrays derzeit einfach in Strings umgewandelt, weil Nicht alle relationalen Datenbanken unterstützen wiederholte Felder. Über optionale Mapping-Regeln ist es auch möglich, Feldnamen zu ändern oder zu löschen. Sie ermöglichen Ihnen, bei Bedarf das Datenschema zu ändern oder einen Datentyp in einen anderen zu konvertieren. Wenn beispielsweise ein JSON-Feld eine Zeichenfolge mit Zeitstempel enthält (field_3_sub_field_1_sub_sub_field_1 aus dem Beispiel oben), müssen Sie zum Erstellen eines Felds in der Datenbank mit dem Typ Zeitstempel eine Zuordnungsregel in die Konfiguration schreiben. Mit anderen Worten: Der Datentyp des Felds wird zuerst durch den JSON-Wert bestimmt und dann wird die Typumwandlungsregel (falls konfiguriert) angewendet. Wir haben 4 Hauptdatentypen identifiziert: STRING, FLOAT64, INT64 und TIMESTAMP. Die Zuordnungs- und Typumwandlungsregeln sehen folgendermaßen aus:

rules:
  - "/field_1/subfield_1 -> " #правило удаления поля
  - "/field_2/subfield_1 -> /field_10/subfield_1" #правило переноса поля
  - "/field_3/subfield_1/subsubfield_1 -> (timestamp) /field_20" #правило переноса поля и приведения типа

Algorithmus zur Bestimmung des Datentyps:

  • Konvertieren Sie die JSON-Struktur in eine flache Struktur
  • Bestimmen des Datentyps von Feldern anhand von Werten
  • Anwenden von Zuordnungs- und Typumwandlungsregeln

Dann aus der eingehenden JSON-Struktur:

{
    "product_id":  "1e48fb70-ef12-4ea9-ab10-fd0b910c49ce",
    "product_price": 399.99,
    "price_currency": "USD",
    "product_type": "supplies",
    "product_release_start": "2020-09-25T12:38:27.763000Z",
    "images": {
      "main": "picture1",
      "sub":  "picture2"
    }
}

Das Datenschema wird erhalten:

"product_id" character varying,
"product_price" numeric (38,18),
"price_currency" character varying,
"product_type" character varying,
"product_release_start" timestamp,
"images_main" character varying,
"images_sub" character varying

Wir dachten auch, dass der Benutzer in der Lage sein sollte, die Partitionierung zu konfigurieren oder Daten in der Datenbank nach anderen Kriterien aufzuteilen, und haben die Möglichkeit implementiert, den Tabellennamen mit einer Konstante oder festzulegen Ausdruck in der Konfiguration. Im folgenden Beispiel wird das Ereignis in einer Tabelle mit einem Namen gespeichert, der basierend auf den Werten der Felder „product_type“ und „_timestamp“ berechnet wird (z. B Lieferungen_2020_10):

tableName: '{{.product_type}}_{{._timestamp.Format "2006_01"}}'

Allerdings kann sich die Struktur eingehender Ereignisse zur Laufzeit ändern. Wir haben einen Algorithmus implementiert, um den Unterschied zwischen der Struktur einer vorhandenen Tabelle und der Struktur eines eingehenden Ereignisses zu überprüfen. Wenn ein Unterschied festgestellt wird, wird die Tabelle mit neuen Feldern aktualisiert. Verwenden Sie dazu die Patch-SQL-Abfrage:

#Пример для Postgres
ALTER TABLE "schema"."table" ADD COLUMN new_column character varying

Architektur

Die Geschichte unserer Open Source: Wie wir einen Analysedienst in Go erstellt und ihn öffentlich zugänglich gemacht haben

Warum müssen Sie Ereignisse in das Dateisystem schreiben und nicht nur direkt in die Datenbank? Datenbanken funktionieren nicht immer gut, wenn sie eine große Anzahl von Einfügungen verarbeiten (Postgres-Empfehlungen). Dazu schreibt der Logger eingehende Ereignisse in eine Datei und in einer separaten Goroutine (Thread) liest der Dateireader die Datei, anschließend werden die Daten konvertiert und ermittelt. Nachdem der Tabellenmanager sichergestellt hat, dass das Tabellenschema auf dem neuesten Stand ist, werden die Daten in einem Stapel in die Datenbank geschrieben. Anschließend haben wir die Möglichkeit hinzugefügt, Daten direkt in die Datenbank zu schreiben. Wir verwenden diesen Modus jedoch für Ereignisse, die nicht zahlreich sind, beispielsweise für Konvertierungen.

Open Source und Pläne für die Zukunft

Irgendwann sah der Dienst wie ein vollwertiges Produkt aus und wir beschlossen, ihn als Open Source zu veröffentlichen. Derzeit wurden Integrationen mit Postgres, ClickHouse, BigQuery, Redshift, S3, Snowflake implementiert. Alle Integrationen unterstützen sowohl Batch- als auch Streaming-Modi zum Laden von Daten. Unterstützung für Anfragen über API hinzugefügt.

Das aktuelle Integrationsschema sieht folgendermaßen aus:

Die Geschichte unserer Open Source: Wie wir einen Analysedienst in Go erstellt und ihn öffentlich zugänglich gemacht haben

Obwohl der Dienst unabhängig genutzt werden kann (z. B. mit Docker), haben wir dies auch gehostete VersionHier können Sie die Integration mit einem Data Warehouse einrichten, Ihrer Domain einen CNAME hinzufügen und Statistiken zur Anzahl der Ereignisse anzeigen. Unsere unmittelbaren Pläne bestehen darin, die Möglichkeit hinzuzufügen, nicht nur Statistiken aus einer Webressource, sondern auch Daten aus externen Datenquellen zu aggregieren und sie in einem beliebigen Speicher Ihrer Wahl zu speichern!

→ GitHub
→ Dokumentation
→ Slack

Wir freuen uns, wenn EventNative bei der Lösung Ihrer Probleme hilft!

An der Umfrage können nur registrierte Benutzer teilnehmen. Einloggenbitte.

Welches Statistikerfassungssystem wird in Ihrem Unternehmen verwendet?

  • 48,0%Google Analytics12

  • 4,0%Segment1

  • 16,0%Ein weiterer (schreibe in die Kommentare)4

  • 32,0%Ihren Dienst implementiert8

25 Benutzer haben abgestimmt. 6 Benutzer enthielten sich der Stimme.

Source: habr.com

Kommentar hinzufügen