A storia di a nostra fonte aperta: cumu avemu fattu un serviziu analiticu in Go è l'hà fattu dispunibule publicamente

Attualmente, quasi ogni cumpagnia in u mondu recullà statistiche nantu à l'azzioni di l'utilizatori nantu à una risorsa web. A motivazione hè chjara - l'imprese volenu sapè cumu si usa u so pruduttu / situ web è capisce megliu i so utilizatori. Di sicuru, ci sò un gran numaru di strumenti nantu à u mercatu per risolve stu prublema - da i sistemi analitici chì furniscenu dati in forma di dashboards è grafici (per esempiu. Google Analytics) à a Piattaforma di Dati di Clienti, chì vi permette di cullà è aggregate dati da diverse fonti in ogni magazzinu (per esempiu Segmentu).

Ma avemu trovu un prublema chì ùn hè micca statu risoltu. Hè natu cusì EventNative - serviziu di analisi open-source. Leghjite per quessa chì avemu decisu di sviluppà u nostru propiu serviziu, ciò chì ci hà datu, è quale era u risultatu finali (cù pezzi di codice).

A storia di a nostra fonte aperta: cumu avemu fattu un serviziu analiticu in Go è l'hà fattu dispunibule publicamente

Perchè duvemu sviluppà u nostru serviziu?

Era l'anni novanta, sopravvivemu cum'è pudemu. 2019, avemu sviluppatu l'API First Customer Data Platform kSense, chì hà permessu di aggregate data da diverse fonti (Facebook ads, Stripe, Salesforce, Google play, Google Analytics, etc.) per l'analisi di dati più còmuda, identificà dependenzii, etc. Avemu nutatu chì parechji utilizatori utilizanu a nostra piattaforma per l'analisi di dati specificamente Google Analytics (in seguitu GA). Avemu parlatu cù alcuni utilizatori è scupertu chì anu bisognu di i dati analitici per u so pruduttu chì ricevenu cù GA, ma Google mostra dati è per parechji, l'interfaccia User GA ùn hè micca u standard di cunvenzione. Avemu avutu abbastanza cunversazione cù i nostri utilizatori è hà capitu chì parechji usavanu ancu a piattaforma Segment (chì, per via, era solu l'altru ghjornu). vendutu per $ 3.2 miliardi).

Anu stallatu un pixel di Segment javascript nantu à a so risorsa web è e dati nantu à u cumpurtamentu di i so utilizatori sò stati caricati in a basa di dati specifica (per esempiu Postgres). Ma u segmentu hà ancu u so svantaghju - u prezzu. Per esempiu, se una risorsa web hà 90,000 1,000 MTU (utilizatori mensili tracciati), allora avete bisognu di pagà ~ XNUMX XNUMX $ à u mese à u cashier. Ci era ancu un terzu prublema - alcune estensioni di navigatore (cum'è AdBlock) bluccatu a cullizzioni di analitiche perchè ... E dumande http da u navigatore sò state mandate à i domini GA è Segment. Basatu nantu à i desideri di i nostri clienti, avemu creatu un serviziu analiticu chì recullà un inseme cumpletu di dati (senza campionamentu), hè liberu è pò travaglià nantu à a nostra propria infrastruttura.

Cumu funziona u serviziu

U serviziu hè custituitu di trè parte: un pixel javascript (chì dopu avemu riscritto in dattilografu), a parte di u servitore hè implementata in a lingua GO, è hè stata pianificata per utilizà Redshift è BigQuery cum'è una basa di dati interna (più tardi anu aghjustatu supportu per Postgres, ClickHouse è Snowflake).

Hè statu decisu di lascià a struttura di l'avvenimenti GA è Segment invariata. Tuttu ciò chì era necessariu era duplicà tutti l'avvenimenti da a risorsa web induve u pixel hè stallatu à u nostru backend. Comu risulta, questu ùn hè micca difficiule di fà. U pixel Javascript hà annullatu u metudu originale di a biblioteca GA cun un novu, chì duplicò l'avvenimentu in u nostru sistema.

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

Cù u pixel di Segmentu tuttu hè più simplice; hà metudi di middleware, unu di i quali avemu usatu.


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

In più di cupià avvenimenti, avemu aghjustatu a capacità di mandà json arbitrariu:


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

Dopu, parlemu di a parte di u servitore. U backend duveria accettà e richieste http, riempite cù infurmazioni supplementari, per esempiu, dati geo (grazie maxmind per questu) è arregistrallu in a basa di dati. Vulemu fà u serviziu u più còmuda pussibule in modu chì pò esse usatu cù cunfigurazione minima. Avemu implementatu a funziunalità di determinà u schema di dati basatu annantu à a struttura di l'avvenimentu json entrante. Tipi di dati sò definiti da i valori. L'oggetti nidificati sò scomposti è ridotti à una struttura piatta:

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

Tuttavia, i matrici sò attualmente cunvertiti in strings perchè Micca tutte e basa di dati relazionale supportanu campi ripetuti. Hè ancu pussibule di cambià i nomi di campu o sguassate cù e regule di mapping opzionali. Permettenu di cambià u schema di dati se necessariu o cunvertisce un tipu di dati à un altru. Per esempiu, se un campu json cuntene una stringa cù timestamp (field_3_sub_field_1_sub_sub_field_1 da l'esempiu sopra), allora per creà un campu in a basa di dati cù u tipu di timestamp, avete bisognu di scrive una regula di mapping in a cunfigurazione. In altre parolle, u tipu di dati di u campu hè determinatu prima da u valore json, è dopu a regula di casting di tipu (se cunfigurata) hè applicata. Avemu identificatu 4 tippi di dati principali: STRING, FLOAT64, INT64 è TIMESTAMP. E regule di cartografica è di casting di tipu sò cusì:

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

Algoritmu per a determinazione di u tipu di dati:

  • cunvertisce a struttura json in una struttura piatta
  • determinà u tipu di dati di campi per valori
  • applicà e regule di cartografica è tippi casting

Allora da a struttura json entrante:

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

u schema di dati serà ottenutu:

"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

Avemu ancu pensatu chì l'utilizatore duveria esse capace di cunfigurà u particionamentu o dividite e dati in a basa di dati secondu altri criterii è implementatu a capacità di stabilisce u nome di a tavola cun una constante o spressione in a cunfigurazione. In l'esempiu sottu, l'avvenimentu serà salvatu in una tavola cù un nome calculatu basatu annantu à i valori di i campi product_type è _timestamp (per esempiu furniture_2020_10):

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

Tuttavia, a struttura di l'avvenimenti entranti pò cambià in runtime. Avemu implementatu un algoritmu per verificà a diffarenza trà a struttura di una tavola esistente è a struttura di un avvenimentu in entrata. Se si trova una diferenza, a tavula serà aghjurnata cù novi campi. Per fà questu, utilizate a dumanda di patch SQL:

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

architettura

A storia di a nostra fonte aperta: cumu avemu fattu un serviziu analiticu in Go è l'hà fattu dispunibule publicamente

Perchè avete bisognu di scrive l'avvenimenti à u sistema di fugliale, è micca solu scrivite direttamente à a basa di dati? E basa di dati ùn sò micca sempre boni quandu si tratta di un gran numaru di inserti (Raccomandazioni di Postgres). Per fà questu, Logger scrive l'avvenimenti entranti in un schedariu è in una goroutine separata (filu) U lettore di file leghje u schedariu, dopu i dati sò cunvertiti è determinati. Dopu chì u Manager di a Tavola assicura chì u schema di a tavola hè aghjurnatu, i dati seranu scritti in a basa di dati in un batch. In seguitu, avemu aghjustatu a capacità di scrive dati direttamente à a basa di dati, ma avemu aduprà stu modu per avvenimenti chì ùn sò micca numerosi - per esempiu, cunversione.

Open Source è piani per u futuru

À un certu puntu, u serviziu hà cuminciatu à vede cum'è un pruduttu cumpletu è avemu decisu di liberà à Open Source. Attualmente, integrazioni cù Postgres, ClickHouse, BigQuery, Redshift, S3, Snowflake sò state implementate. Tutte l'integrazioni supportanu i modi batch è streaming di carica di dati. Aghjunghje supportu per e dumande via API.

U schema di integrazione attuale hè cusì:

A storia di a nostra fonte aperta: cumu avemu fattu un serviziu analiticu in Go è l'hà fattu dispunibule publicamente

Ancu se u serviziu pò esse usatu indipindente (per esempiu cù Docker), avemu ancu versione ospitata, in quale pudete stabilisce l'integrazione cù un magazzinu di dati, aghjunghje un CNAME à u vostru duminiu è vede statistiche nantu à u numeru di avvenimenti. I nostri piani immediati sò di aghjunghje l'abilità di aggregate micca solu statistiche da una risorsa web, ma ancu dati da fonti di dati esterni è salvà in ogni almacenamentu di a vostra scelta!

→ GitHub
→ Documentazione
→ Slack

Saremu felici se EventNative aiuta à risolve i vostri prublemi!

Solu l'utilizatori registrati ponu participà à l'indagine. Firmà lu, per piacè.

Chì sistema di cullizzioni di statistiche hè utilizatu in a vostra cumpagnia?

  • 48,0%Google Analytics 12

  • 4,0%Segmentu 1

  • 16,0%Un altru (scrivite in i cumenti)4

  • 32,0%Implementatu u vostru serviziu 8

25 utilizatori anu vutatu. 6 utilizatori si sò astenuti.

Source: www.habr.com

Add a comment