La historio de nia malferma fonto: kiel ni faris analizan servon en Go kaj faris ĝin publike havebla

Nuntempe, preskaŭ ĉiuj kompanioj en la mondo kolektas statistikojn pri uzant-agoj en interreta rimedo. La instigo estas klara - kompanioj volas scii kiel ilia produkto/retejo estas uzata kaj pli bone kompreni siajn uzantojn. Kompreneble, ekzistas granda nombro da iloj sur la merkato por solvi ĉi tiun problemon - de analizaj sistemoj, kiuj provizas datumojn en formo de paneloj kaj grafikaĵoj (ekzemple Google Analytics) al Klienta Datuma Platformo, kiu permesas vin kolekti kaj kunigi datumojn de malsamaj fontoj en iu ajn magazeno (ekzemple segmento).

Sed ni trovis problemon, kiu ankoraŭ ne estis solvita. Tiel naskiĝis EventNative - malfermfonta analizservo. Legu pri kial ni decidis evoluigi nian propran servon, kion ĝi donis al ni, kaj kia estis la fina rezulto (kun pecoj de kodo).

La historio de nia malferma fonto: kiel ni faris analizan servon en Go kaj faris ĝin publike havebla

Kial ni devus disvolvi nian propran servon?

Estis la naŭdekaj, ni pluvivis kiel eble plej bone. 2019, ni evoluigis la API Unua Klienta Datuma Platformo kSense, kiu ebligis kunigi datumojn de malsamaj fontoj (Facebook-reklamoj, Stripe, Salesforce, Google play, Google Analytics, ktp.) por pli oportuna analizo de datumoj, identigi dependecojn ktp. Ni rimarkis, ke multaj uzantoj uzas nian platformon por analizo de datumoj specife Google Analytics (ĉi-poste GA). Ni parolis kun kelkaj uzantoj kaj eksciis, ke ili bezonas la analizajn datumojn por sia produkto, kiun ili ricevas uzante GA, sed Guglo provas datumojn kaj por multaj, la GA Uzantinterfaco ne estas la normo de komforto. Ni havis sufiĉe da konversacioj kun niaj uzantoj kaj rimarkis, ke multaj ankaŭ uzis la Segment-platformon (kiu, cetere, estis ĵus la alia tago. vendiĝis por 3.2 miliardoj USD).

Ili instalis Segment-javascript-pikselon sur sia retejo-rimedo kaj datumoj pri la konduto de siaj uzantoj estis ŝarĝitaj en la specifitan datumbazon (ekzemple Postgres). Sed Segmento ankaŭ havas sian malavantaĝon - la prezon. Ekzemple, se interreta rimedo havas 90,000 MTU (monate spuritajn uzantojn), tiam vi devas pagi ~1,000 $ monate al la kasisto. Estis ankaŭ tria problemo - kelkaj retumiloj (kiel AdBlock) blokis la kolekton de analizoj ĉar... http-petoj de la retumilo estis senditaj al la domajnoj GA kaj Segment. Surbaze de la deziroj de niaj klientoj, ni kreis analitikan servon, kiu kolektas kompletan aron da datumoj (sen specimeno), estas senpaga kaj povas funkcii per nia propra infrastrukturo.

Kiel funkcias la servo

La servo konsistas el tri partoj: ĵavaskripto-pikselo (kiun ni poste reverkis per tajpskribo), la servila parto estas efektivigita en la GO-lingvo, kaj estis planite uzi Redshift kaj BigQuery kiel endoman datumbazon (poste ili aldonis subtenon por Postgres, ClickHouse kaj Snowflake).

Estis decidite lasi la strukturon de la GA kaj Segment-okazaĵoj senŝanĝa. Ĉio, kio estis bezonata, estis duobligi ĉiujn eventojn de la interreta rimedo kie la pikselo estas instalita al nia backend. Kiel ĝi rezultas, ĉi tio ne estas malfacile fari. La Javascript-pikselo superregis la originan GA-bibliotekan metodon per nova, kiu duobligis la eventon en nian sistemon.

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

Kun la Segment-pikselo ĉio estas pli simpla; ĝi havas mezvarmetodojn, unu el kiuj ni uzis.


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

Krom kopii eventojn, ni aldonis la kapablon sendi arbitran json:


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

Poste, ni parolu pri la servila parto. La backend devas akcepti http-petojn, plenigi ilin per pliaj informoj, ekzemple geodatenoj (dankon maxmind por tio) kaj registri ĝin en la datumbazo. Ni volis fari la servon kiel eble plej oportuna por ke ĝi estu uzata kun minimuma agordo. Ni efektivigis la funkciecon determini la datuman skemon bazitan sur la strukturo de la envenanta json-okazaĵo. Datumtipoj estas difinitaj per valoroj. Nestitaj objektoj estas malkomponitaj kaj reduktitaj al plata strukturo:

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

Tamen, tabeloj estas nuntempe simple konvertitaj al ŝnuroj ĉar Ne ĉiuj rilataj datumbazoj subtenas ripetajn kampojn. Eblas ankaŭ ŝanĝi kamponomojn aŭ forigi ilin uzante laŭvolajn mapajn regulojn. Ili permesas vin ŝanĝi la datumskemon se necese aŭ konverti unu datumtipo al alia. Ekzemple, se json-kampo enhavas ĉenon kun tempomarko (kampo_3_sub_kampo_1_sub_sub_kampo_1 de la supra ekzemplo), tiam por krei kampon en la datumbazo kun la tempomarko-tipo, vi devas skribi mapan regulon en la agordo. Alivorte, la datumtipo de la kampo unue estas determinita per la json-valoro, kaj tiam la tipa casting-regulo (se agordita) estas aplikata. Ni identigis 4 ĉefajn datumtipojn: STRING, FLOAT64, INT64 kaj TIMESTAMP. La reguloj pri mapado kaj tajpado aspektas jene:

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

Algoritmo por determini la datumtipo:

  • konverti json-strukturon al plata strukturo
  • determinante la datumspecon de kampoj per valoroj
  • aplikante mapajn kaj tajpajn regulojn

Tiam de la envenanta json-strukturo:

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

la datumskemo estos akirita:

"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

Ni ankaŭ pensis, ke la uzanto devus povi agordi dispartigon aŭ dividi datumojn en la datumbazo laŭ aliaj kriterioj kaj efektivigis la kapablon agordi la tabelnomon per konstanta aŭ esprimo en la agordo. En la malsupra ekzemplo, la evento estos konservita al tabelo kun nomo kalkulita surbaze de la valoroj de la kampoj product_type kaj _timestamp (ekzemple provizoj_2020_10):

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

Tamen, la strukturo de envenantaj eventoj povas ŝanĝiĝi ĉe rultempo. Ni efektivigis algoritmon por kontroli la diferencon inter la strukturo de ekzistanta tabelo kaj la strukturo de envenanta evento. Se oni trovas diferencon, la tabelo estos ĝisdatigita kun novaj kampoj. Por fari tion, uzu la flikan SQL-demandon:

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

arkitekturo

La historio de nia malferma fonto: kiel ni faris analizan servon en Go kaj faris ĝin publike havebla

Kial vi bezonas skribi eventojn al la dosiersistemo, kaj ne nur skribi ilin rekte al la datumbazo? Datumbazoj ne ĉiam funkcias bone kiam traktas grandajn nombrojn da enigaĵoj (Postgres-rekomendoj). Por fari tion, Logger skribas envenantajn eventojn al dosiero kaj en aparta goroutino (fadeno) Dosierleganto legas la dosieron, tiam la datumoj estas konvertitaj kaj determinitaj. Post kiam la Tabla administranto certigas, ke la tabelskemo estas ĝisdatigita, la datumoj estos skribitaj al la datumbazo en unu aro. Poste, ni aldonis la kapablon skribi datumojn rekte al la datumbazo, sed ni uzas ĉi tiun reĝimon por eventoj ne multnombraj - ekzemple konvertiĝoj.

Malferma Kodo kaj planoj por la estonteco

Iam, la servo komencis aspekti kiel plentaŭga produkto kaj ni decidis liberigi ĝin al Malferma Fonto. Nuntempe, integriĝoj kun Postgres, ClickHouse, BigQuery, Redshift, S3, Snowflake estis efektivigitaj. Ĉiuj integriĝoj subtenas ambaŭ batajn kaj fluajn reĝimojn de datumŝarĝado. Aldonita subteno por petoj per API.

La nuna integriga skemo aspektas jene:

La historio de nia malferma fonto: kiel ni faris analizan servon en Go kaj faris ĝin publike havebla

Kvankam la servo povas esti uzata sendepende (ekzemple uzante Docker), ni ankaŭ havas gastigita versio, en kiu vi povas agordi integriĝon kun datumstokejo, aldoni CNAME al via domajno kaj vidi statistikojn pri la nombro da eventoj. Niaj tujaj planoj estas aldoni la kapablon kunigi ne nur statistikojn de interreta rimedo, sed ankaŭ datumojn de eksteraj datumfontoj kaj konservi ilin al iu ajn stokado de via elekto!

→ GitHub
→ Dokumentado
→ slack

Ni ĝojos, se EventNative helpos solvi viajn problemojn!

Nur registritaj uzantoj povas partopreni la enketon. Ensaluti, bonvolu.

Kia statistika kolektosistemo estas uzata en via kompanio?

  • 48,0%Google Analytics12

  • 4,0%Segmento 1

  • 16,0%Alia (skribi en la komentoj)4

  • 32,0%Efektivigis vian servon8

25 uzantoj voĉdonis. 6 uzantoj sindetenis.

fonto: www.habr.com

Aldoni komenton