De geschiedenis van onze open source: hoe we een analyseservice in Go hebben gemaakt en openbaar beschikbaar hebben gemaakt

Momenteel verzamelt bijna elk bedrijf ter wereld statistieken over gebruikersacties op een webbron. De motivatie is duidelijk: bedrijven willen weten hoe hun product/website wordt gebruikt en willen hun gebruikers beter begrijpen. Uiteraard zijn er een groot aantal tools op de markt om dit probleem op te lossen – van analysesystemen die data leveren in de vorm van dashboards en grafieken (bijvoorbeeld Google Analytics) naar het Klantgegevensplatform, waarmee u gegevens uit verschillende bronnen in elke opslag kunt verzamelen en samenvoegen (bijvoorbeeld Segment).

Maar we hebben een probleem gevonden dat nog niet is opgelost. Zo geboren EvenementNatief — open-source analysedienst. Over waarom we onze eigen dienst zijn gaan ontwikkelen, wat het ons heeft opgeleverd en wat er uiteindelijk is gebeurd (met stukjes code), lees onder de knip.

De geschiedenis van onze open source: hoe we een analyseservice in Go hebben gemaakt en openbaar beschikbaar hebben gemaakt

Waarom zouden we onze eigen dienst ontwikkelen?

Het waren de jaren negentig, we overleefden zo goed als we konden. In 2019 hebben we de First Customer Data Platform API ontwikkeld kSense, waarmee gegevens uit verschillende bronnen konden worden samengevoegd (Facebook-advertenties, Stripe, Salesforce, Google Play, Google Analytics, enz.) voor gemakkelijkere gegevensanalyse, het identificeren van afhankelijkheden, enz. We hebben gemerkt dat veel gebruikers ons data-analyseplatform gebruiken, met name Google Analytics (hierna GA genoemd). We spraken met enkele gebruikers en kwamen erachter dat ze hun productanalysegegevens nodig hebben, die ze ontvangen via GA, maar Google voorbeeldgegevens en voor velen is de GA-gebruikersinterface geen standaard voor gemak. We hadden genoeg gesprekken met onze gebruikers en realiseerden ons dat velen ook het Segment-platform gebruikten (wat overigens nog maar een paar dagen geleden was verkocht voor $ 3.2 miljard).

Ze installeerden een Segment javascript-pixel op hun webbron en hun gebruikersgedragsgegevens werden in een gespecificeerde database (bijvoorbeeld Postgres) geladen. Maar Segment heeft ook zijn minpunt: de prijs. Als een webbron bijvoorbeeld 90,000 MTU (maandelijks bijgehouden gebruikers) heeft, moet u ~ $ 1,000 per maand aan de kassier betalen. Er was ook een derde probleem: sommige browserextensies (zoals AdBlock) blokkeerden het verzamelen van analyses. http-verzoeken van de browser zijn verzonden naar de GA- en Segment-domeinen. Op basis van de wens van onze klanten hebben we een analysedienst gecreëerd die gratis een volledige set gegevens verzamelt (zonder steekproeven) en op onze eigen infrastructuur kan werken.

Hoe de dienst werkt

De dienst bestaat uit drie delen: een javascript-pixel (die we later hebben herschreven tot typoscript), een serverdeel geïmplementeerd in de GO-taal, en het was de bedoeling om Redshift en BigQuery te gebruiken als interne database (later voegden ze ondersteuning toe voor Postgres , ClickHouse en Sneeuwvlok).

De structuur van de evenementen GA en Segment besloten ongewijzigd te blijven. Het enige dat nodig was, was het dupliceren van alle gebeurtenissen van de webbron waarop de pixel is geïnstalleerd naar onze backend. Het blijkt dat dit gemakkelijk te doen is. De Javascript-pixel heeft de oorspronkelijke GA-bibliotheekmethode overschreven door een nieuwe die de gebeurtenis in ons systeem dupliceerde.

//'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);
        });
    });
}

Met de Segment-pixel is alles eenvoudiger, het heeft middleware-methoden en we hebben er een van gebruikt.


//'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.');
}

Naast het kopiëren van gebeurtenissen hebben we de mogelijkheid toegevoegd om willekeurige json te verzenden:


//Отправка событий с произвольным 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'
});

Laten we het vervolgens hebben over de serverkant. De backend moet http-verzoeken accepteren en deze vullen met aanvullende informatie, bijvoorbeeld geogegevens (bedankt maxmind ervoor) en schrijf naar de database. We wilden de service zo gemakkelijk mogelijk maken, zodat deze met minimale configuratie kan worden gebruikt. We hebben de functionaliteit geïmplementeerd voor het bepalen van het gegevensschema op basis van de structuur van de binnenkomende gebeurtenis-json. Gegevenstypen worden gedefinieerd door waarden. Geneste objecten worden ontleed en teruggebracht tot een platte structuur:

//входящий 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"
}

Arrays worden momenteel echter eenvoudigweg geconverteerd naar strings. niet alle relationele databases ondersteunen herhaalde velden. Het is ook mogelijk om veldnamen te wijzigen of te verwijderen met behulp van optionele toewijzingsregels. Hiermee kunt u indien nodig het gegevensschema wijzigen of het ene gegevenstype naar het andere casten. Als het json-veld bijvoorbeeld een tekenreeks met een tijdstempel (veld_3_sub_veld_1_sub_sub_veld_1 uit het bovenstaande voorbeeld), en om een ​​veld in de database te maken met het tijdstempeltype, moet u een toewijzingsregel in de configuratie schrijven. Met andere woorden: het gegevenstype van het veld wordt eerst bepaald door de json-waarde en vervolgens wordt de typecast-regel (indien geconfigureerd) toegepast. We hebben vier hoofdgegevenstypen geïdentificeerd: STRING, FLOAT4, INT64 en TIMESTAMP. De mapping- en castingregels zien er als volgt uit:

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

Algoritme voor het bepalen van het gegevenstype:

  • converteer json-struct naar platte struct
  • het gegevenstype van velden bepalen aan de hand van waarden
  • het toepassen van mapping- en typecasting-regels

Vervolgens vanuit de binnenkomende json-structuur:

{
    "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"
    }
}

gegevensschema wordt verkregen:

"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

We dachten ook dat de gebruiker partities zou moeten kunnen instellen of gegevens in de database zou kunnen splitsen op basis van andere criteria, en implementeerden de mogelijkheid om de tabelnaam in te stellen als een constante of uitdrukking in configuratie. In het onderstaande voorbeeld wordt de gebeurtenis opgeslagen in een tabel met een naam die wordt berekend op basis van de waarden van de velden product_type en _timestamp (bijvoorbeeld benodigdheden_2020_10):

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

De structuur van binnenkomende gebeurtenissen kan echter tijdens runtime veranderen. We hebben een algoritme geïmplementeerd om het verschil te controleren tussen de structuur van een bestaande tabel en de structuur van een binnenkomende gebeurtenis. Als er een verschil wordt gevonden, wordt de tabel bijgewerkt met nieuwe velden. Gebruik hiervoor de patch-SQL-query:

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

Architectuur

De geschiedenis van onze open source: hoe we een analyseservice in Go hebben gemaakt en openbaar beschikbaar hebben gemaakt

Waarom moet u gebeurtenissen naar het bestandssysteem schrijven, en ze niet alleen rechtstreeks naar de database schrijven? Databases presteren niet altijd goed met een groot aantal invoegingen (postgres-aanbevelingen). Om dit te doen, schrijft Logger binnenkomende gebeurtenissen naar een bestand en al in een aparte goroutine (thread) Bestandslezer leest het bestand, waarna de transformatie en definitie van het gegevensschema plaatsvindt. Nadat de Tabelmanager ervoor heeft gezorgd dat het tabelschema up-to-date is, worden de gegevens in één batch naar de database geschreven. Vervolgens hebben we de mogelijkheid toegevoegd om gegevens rechtstreeks naar de database te schrijven, maar we gebruiken deze modus voor niet veel gebeurtenissen, bijvoorbeeld conversies.

Open Source en toekomstplannen

Op een gegeven moment werd de dienst een volwaardig product en besloten we deze in Open Source te zetten. Op dit moment zijn integraties met Postgres, ClickHouse, BigQuery, Redshift, S3, Snowflake geïmplementeerd. Alle integraties ondersteunen zowel batch- als streaminggegevenslaadmodi. Ondersteuning toegevoegd voor verzoeken via API.

Het huidige integratieschema ziet er als volgt uit:

De geschiedenis van onze open source: hoe we een analyseservice in Go hebben gemaakt en openbaar beschikbaar hebben gemaakt

Hoewel de dienst zelfstandig kan worden gebruikt (bijvoorbeeld met Docker), hebben wij dat ook gehoste versie, waar u integratie met het datawarehouse kunt opzetten, een CNAME aan uw domein kunt toevoegen en statistieken over het aantal gebeurtenissen kunt bekijken. Onze onmiddellijke plannen zijn om de mogelijkheid toe te voegen om niet alleen statistieken uit een webbron te verzamelen, maar ook gegevens uit externe gegevensbronnen en deze op te slaan in elke opslag naar keuze!

→ GitHub
→ Документация
→ Slack

Wij zullen blij zijn als EventNative u helpt bij het oplossen van uw problemen!

Alleen geregistreerde gebruikers kunnen deelnemen aan het onderzoek. Inloggen, Alsjeblieft.

Welk systeem voor het verzamelen van statistieken wordt in uw bedrijf gebruikt

  • 48,0%Google Analytics12

  • 4,0%Segment1

  • 16,0%Anders (schrijf in de reacties) 4

  • 32,0%Uw dienst geïmplementeerd8

25 gebruikers hebben gestemd. 6 gebruikers onthielden zich van stemming.

Bron: www.habr.com

Voeg een reactie