De skiednis fan ús iepen boarne: hoe't wy in analytyske tsjinst yn Go makken en it iepenbier beskikber makken

Op it stuit sammelet hast elk bedriuw yn 'e wrâld statistiken oer brûkersaksjes op in webboarne. De motivaasje is dúdlik - bedriuwen wolle witte hoe't har produkt / webside wurdt brûkt en har brûkers better begripe. Fansels binne d'r in grut oantal ark op 'e merke om dit probleem op te lossen - fan analytyske systemen dy't gegevens leverje yn' e foarm fan dashboards en grafiken (bygelyks, Google Analytics) nei it Platfoarm foar klantgegevens, wêrmei jo gegevens kinne sammelje en sammelje út ferskate boarnen yn elke opslach (bygelyks, Segmint).

Mar wy hawwe in probleem fûn dat noch net oplost is. Sa berne EventNative - iepen boarne analytyske tsjinst. Oer wêrom't wy ús eigen tsjinst gongen te ûntwikkeljen, wat it ús joech en wat der op it lêst barde (mei stikken koade), lês ûnder de besuniging.

De skiednis fan ús iepen boarne: hoe't wy in analytyske tsjinst yn Go makken en it iepenbier beskikber makken

Wêrom moatte wy ús eigen tsjinst ûntwikkelje?

It wiene de jierren njoggentich, wy oerlibbe sa goed as wy koenen. 2019 hawwe wy de First Customer Data Platform API ûntwikkele kSense, wêrtroch it sammeljen fan gegevens út ferskate boarnen (Facebook-advertinsjes, Stripe, Salesforce, Google play, Google Analytics, ensfh.) Wy hawwe opmurken dat in protte brûkers ús data analytics platfoarm brûke, spesifyk Google Analytics (hjirnei oantsjutten as GA). Wy sprieken mei guon brûkers en fûn út dat se nedich harren produkt analytics gegevens, dy't se ûntfange mei help fan GA, mar Google samples gegevens en foar in protte GA User Interface is gjin standert fan gemak. Wy hiene genôch petearen mei ús brûkers en realisearre dat in protte ek it Segment-platfoarm brûkten (dat trouwens mar in pear dagen lyn wie ferkocht foar $ 3.2 miljard).

Se ynstalleare in Segment javascript-pixel op har webboarne en har brûkersgedrachsgegevens waarden laden yn in spesifisearre database (bgl. Postgres). Mar Segment hat ek syn minus - de priis. Bygelyks, as in webboarne 90,000 MTU hat (moanlikse tracked brûkers), dan moatte jo ~ $ 1,000 per moanne betelje oan de kassier. D'r wie ek in tredde probleem - guon browser-útwreidings (lykas AdBlock) blokkearren de kolleksje fan analytiken. http-oanfragen fan 'e browser waarden stjoerd nei de GA- en Segment-domeinen. Op grûn fan 'e winsk fan ús kliïnten hawwe wy in analytyske tsjinst makke dy't in folsleine set gegevens sammelt (sûnder sampling), fergees en kin wurkje oan ús eigen ynfrastruktuer.

Hoe't de tsjinst wurket

De tsjinst bestiet út trije dielen: in javascript-piksel (dy't wy letter omskreaun hawwe nei typescript), in serverdiel ymplementearre yn 'e GO-taal, en it wie pland om Redshift en BigQuery te brûken as in eigen databank (letter hawwe se stipe tafoege foar Postgres , ClickHouse en Snowflake).

De struktuer fan eveneminten GA en Segment besletten om te ferlitten ûnferoare. Alles wat nedich wie wie om alle eveneminten te duplikearjen fan 'e webboarne wêr't de piksel is ynstalleare nei ús efterkant. As it docht bliken, is dit maklik te dwaan. De Javascript-piksel hat de orizjinele GA-bibleteekmetoade oerskreaun mei in nije dy't it evenemint yn ús systeem duplikearre.

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

Mei de Segment-piksel is alles ienfâldiger, it hat middelware-metoaden, en wy hawwe ien fan har brûkt.


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

Neist it kopiearjen fan eveneminten hawwe wy de mooglikheid tafoege om willekeurige json te stjoeren:


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

Lit ús dan prate oer de serverkant. De backend moat http-oanfragen akseptearje, folje se mei oanfoljende ynformaasje, bygelyks geodata (tank maxmind foar it) en skriuw nei de databank. Wy woenen de tsjinst sa handich mooglik meitsje, sadat it kin wurde brûkt mei minimale konfiguraasje. Wy hawwe de funksjonaliteit ymplementearre fan it bepalen fan it gegevensskema basearre op de struktuer fan it ynkommende evenemint json. Gegevenstypen wurde definieare troch wearden. Neste objekten wurde ûntbûn en werombrocht ta in platte struktuer:

//входящий 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 wurde op it stuit lykwols gewoan omboud ta strings. net alle relasjonele databases stypje werhelle fjilden. It is ek mooglik om fjildnammen te feroarjen of te ferwiderjen mei opsjonele mappingregels. Se tastean jo te feroarjen it gegevens skema, as it nedich is, of cast ien gegevens type nei in oar. Bygelyks, as it json-fjild in tekenrige befettet mei in tiidstempel (field_3_sub_field_1_sub_sub_field_1 út it foarbyld hjirboppe), dan moatte jo in mappingregel skriuwe yn 'e konfiguraasje om in fjild yn' e database te meitsjen mei it tiidstempeltype. Mei oare wurden, it gegevenstype fan it fjild wurdt earst bepaald troch de json-wearde, en dan wurdt de type casting-regel (as ynsteld) tapast. Wy hawwe 4 haadgegevenstypen identifisearre: STRING, FLOAT64, INT64 en TIMESTAMP. De regels foar mapping en casting sjogge der sa út:

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

Algoritme foar it bepalen fan it gegevenstype:

  • konvertearje json struct nei flat struct
  • it bepalen fan it gegevenstype fan fjilden troch wearden
  • it tapassen fan mapping en type casting regels

Dan fan 'e ynkommende json-struktuer:

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

gegevensskema sil wurde krigen:

"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

Wy tochten ek dat de brûker partitionearjen of splitsen fan gegevens yn 'e databank neffens oare kritearia soe moatte ynstelle en ymplementearre de mooglikheid om de tabelnamme yn te stellen as in konstante of útdrukking yn konfiguraasje. Yn it foarbyld hjirûnder sil it evenemint wurde bewarre yn in tabel mei in namme berekkene op basis fan 'e wearden fan' e fjilden product_type en _timestamp (bygelyks supplies_2020_10):

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

De struktuer fan ynkommende eveneminten kin lykwols feroarje by runtime. Wy hawwe in algoritme ymplementearre foar it kontrolearjen fan it ferskil tusken de struktuer fan in besteande tabel en de struktuer fan in ynkommende evenemint. As in ferskil wurdt fûn, sil de tabel bywurke wurde mei nije fjilden. Om dit te dwaan, brûk de patch SQL-query:

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

arsjitektuer

De skiednis fan ús iepen boarne: hoe't wy in analytyske tsjinst yn Go makken en it iepenbier beskikber makken

Wêrom moatte jo eveneminten skriuwe nei it bestânsysteem, en net gewoan direkt nei de databank skriuwe? Databanken litte net altyd hege prestaasjes sjen mei in grut oantal ynserts (postgres oanbefellings). Om dit te dwaan, Logger skriuwt ynkommende eveneminten nei in bestân en al yn in aparte goroutine (thread) Triemlêzer lêst it bestân, dan fynt de transformaasje en definysje fan it gegevensskema plak. Nei't de tabelmanager der wis fan hat dat it tabelskema aktueel is, wurde de gegevens yn ien batch nei de databank skreaun. Dêrnei hawwe wy de mooglikheid tafoege om gegevens direkt nei de databank te skriuwen, mar wy brûke dizze modus foar eveneminten dy't net folle binne - bygelyks konversaasjes.

Iepen boarne en takomstplannen

Op in stuit waard de tsjinst as in folweardich produkt en wy besletten it yn Open Source te pleatsen. Op it stuit binne yntegraasjes mei Postgres, ClickHouse, BigQuery, Redshift, S3, Snowflake ymplementearre. Alle yntegraasjes stypje sawol batch- as streamende gegevensladingsmodi. Stipe tafoege foar oanfragen fia API.

It hjoeddeiske yntegraasjeskema sjocht der sa út:

De skiednis fan ús iepen boarne: hoe't wy in analytyske tsjinst yn Go makken en it iepenbier beskikber makken

Hoewol de tsjinst selsstannich kin wurde brûkt (bygelyks mei Docker), hawwe wy ek hosted ferzje, wêr't jo yntegraasje mei it datapakhûs kinne ynstelle, in CNAME tafoegje oan jo domein en statistiken sjen oer it oantal eveneminten. Us direkte plannen binne om de mooglikheid ta te foegjen om net allinich statistiken fan in webboarne te aggregearjen, mar ek gegevens fan eksterne gegevensboarnen en op te slaan yn elke opslach fan jo kar!

→ GitHub
→ Dokumintaasje
→ slack

Wy sille bliid wêze as EventNative jo sil helpe jo problemen op te lossen!

Allinnich registrearre brûkers kinne meidwaan oan 'e enkête. Ynlogge, asjebleaft.

Hokker statistyksammelsysteem wurdt brûkt yn jo bedriuw

  • 48,0%Google Analytics 12

  • 4,0%Segment 1

  • 16,0%Oare (skriuw yn 'e opmerkings) 4

  • 32,0%Implementearre jo tsjinst8

25 brûkers stimden. 6 brûkers ûntholden har.

Boarne: www.habr.com

Add a comment