Ang kasaysayan ng aming open source: kung paano namin ginawa ang serbisyo ng analytics sa Go at ginawa itong available sa publiko

Sa kasalukuyan, halos lahat ng kumpanya sa mundo ay nangongolekta ng mga istatistika tungkol sa mga aksyon ng user sa isang web resource. Malinaw ang motibasyon - gustong malaman ng mga kumpanya kung paano ginagamit ang kanilang produkto/website at mas maunawaan ang kanilang mga user. Siyempre, mayroong isang malaking bilang ng mga tool sa merkado upang malutas ang problemang ito - mula sa mga sistema ng analytics na nagbibigay ng data sa anyo ng mga dashboard at graph (halimbawa Google Analytics) sa Customer Data Platform, na nagbibigay-daan sa iyong mangolekta at pagsama-samahin ang data mula sa iba't ibang mapagkukunan sa anumang bodega (halimbawa Bahagi).

Ngunit nakakita kami ng isang problema na hindi pa nalulutas. Kaya ipinanganak EventNative β€” open-source na serbisyo sa analytics. Basahin ang tungkol sa kung bakit nagpasya kaming bumuo ng sarili naming serbisyo, kung ano ang ibinigay nito sa amin, at kung ano ang resulta (na may mga piraso ng code).

Ang kasaysayan ng aming open source: kung paano namin ginawa ang serbisyo ng analytics sa Go at ginawa itong available sa publiko

Bakit tayo dapat bumuo ng sarili nating serbisyo?

Noong dekada nobenta, nakaligtas kami sa abot ng aming makakaya. 2019, binuo namin ang API First Customer Data Platform kSense, na naging posible na pagsama-samahin ang data mula sa iba't ibang pinagmulan (mga ad sa Facebook, Stripe, Salesforce, Google play, Google Analytics, atbp.) para sa mas maginhawang pagsusuri ng data, pagtukoy ng mga dependency, atbp. Napansin namin na maraming user ang gumagamit ng aming platform para sa pagsusuri ng data partikular sa Google Analytics (simula dito sa GA). Nakipag-usap kami sa ilang user at nalaman namin na kailangan nila ang data ng analytics para sa kanilang produkto na natatanggap nila gamit ang GA, ngunit Mga sample ng data ng Google at para sa marami, ang GA User interface ay hindi ang pamantayan ng kaginhawahan. Nagkaroon kami ng sapat na pakikipag-usap sa aming mga user at napagtanto na marami rin ang gumagamit ng Segment platform (na, sa pamamagitan ng paraan, ay noong isang araw lang naibenta sa halagang $3.2 bilyon).

Nag-install sila ng Segment javascript pixel sa kanilang web resource at ang data tungkol sa gawi ng kanilang mga user ay na-load sa tinukoy na database (halimbawa Postgres). Ngunit mayroon ding downside ang Segment - ang presyo. Halimbawa, kung ang isang web resource ay may 90,000 MTU (buwanang sinusubaybayang mga user), kailangan mong magbayad ng ~1,000 $ bawat buwan sa cashier. Nagkaroon din ng pangatlong problema - hinarangan ng ilang extension ng browser (gaya ng AdBlock) ang koleksyon ng analytics dahil... Ang mga kahilingan sa http mula sa browser ay ipinadala sa mga domain ng GA at Segment. Batay sa kagustuhan ng aming mga kliyente, gumawa kami ng serbisyo ng analytics na nangongolekta ng kumpletong hanay ng data (nang walang sampling), libre at maaaring gumana sa sarili naming imprastraktura.

Paano gumagana ang serbisyo

Binubuo ang serbisyo ng tatlong bahagi: isang javascript pixel (na sa kalaunan ay isinulat naming muli sa typescript), ang bahagi ng server ay ipinatupad sa wikang GO, at binalak itong gamitin ang Redshift at BigQuery bilang isang in-house na database (sa kalaunan ay nagdagdag sila ng suporta para sa Postgres, ClickHouse at Snowflake).

Napagpasyahan na iwanan ang istruktura ng mga kaganapan sa GA at Segment na hindi nagbabago. Ang kailangan lang ay i-duplicate ang lahat ng event mula sa web resource kung saan naka-install ang pixel sa aming backend. Sa lumalabas, hindi ito mahirap gawin. Na-override ng Javascript pixel ang orihinal na pamamaraan ng library ng GA gamit ang bago, na nag-duplicate ng kaganapan sa aming system.

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

Sa Segment pixel, mas simple ang lahat; mayroon itong mga middleware na pamamaraan, isa sa mga ginamit namin.


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

Bilang karagdagan sa pagkopya ng mga kaganapan, nagdagdag kami ng kakayahang magpadala ng arbitrary na 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'
});

Susunod, pag-usapan natin ang bahagi ng server. Dapat tanggapin ng backend ang mga kahilingan sa http, punan ang mga ito ng karagdagang impormasyon, halimbawa, geo data (salamat maxmind para dito) at itala ito sa database. Nais naming gawing maginhawa ang serbisyo hangga't maaari upang magamit ito nang may kaunting configuration. Ipinatupad namin ang functionality ng pagtukoy sa schema ng data batay sa istruktura ng papasok na kaganapan ng json. Ang mga uri ng data ay tinutukoy ng mga halaga. Ang mga nested na bagay ay nabubulok at nagiging patag na istraktura:

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

Gayunpaman, ang mga array ay kasalukuyang na-convert sa mga string dahil Hindi lahat ng relational database ay sumusuporta sa mga paulit-ulit na field. Posible ring baguhin ang mga pangalan ng field o tanggalin ang mga ito gamit ang mga opsyonal na panuntunan sa pagmamapa. Pinapayagan ka nitong baguhin ang schema ng data kung kinakailangan o i-convert ang isang uri ng data sa isa pa. Halimbawa, kung ang isang json field ay naglalaman ng isang string na may timestamp (field_3_sub_field_1_sub_sub_field_1 mula sa halimbawa sa itaas), pagkatapos ay upang lumikha ng isang patlang sa database na may uri ng timestamp, kailangan mong magsulat ng isang panuntunan sa pagmamapa sa pagsasaayos. Sa madaling salita, ang uri ng data ng field ay tinutukoy muna ng json value, at pagkatapos ay ilalapat ang uri ng casting rule (kung naka-configure). Natukoy namin ang 4 na pangunahing uri ng data: STRING, FLOAT64, INT64 at TIMESTAMP. Ang mga panuntunan sa pagmamapa at uri ng casting ay ganito:

rules:
  - "/field_1/subfield_1 -> " #ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ удалСния поля
  - "/field_2/subfield_1 -> /field_10/subfield_1" #ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ пСрСноса поля
  - "/field_3/subfield_1/subsubfield_1 -> (timestamp) /field_20" #ΠΏΡ€Π°Π²ΠΈΠ»ΠΎ пСрСноса поля ΠΈ привСдСния Ρ‚ΠΈΠΏΠ°

Algorithm para sa pagtukoy ng uri ng data:

  • i-convert ang json structure sa flat structure
  • pagtukoy sa uri ng data ng mga patlang sa pamamagitan ng mga halaga
  • paglalapat ng mapping at type casting rules

Pagkatapos mula sa papasok na istraktura ng json:

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

ang data schema ay makukuha:

"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

Naisip din namin na ang user ay dapat na mai-configure ang partitioning o hatiin ang data sa database ayon sa iba pang pamantayan at ipinatupad ang kakayahang itakda ang pangalan ng talahanayan na may pare-pareho o pagpapahayag sa pagsasaayos. Sa halimbawa sa ibaba, ang kaganapan ay ise-save sa isang talahanayan na may pangalan na kinakalkula batay sa mga halaga ng product_type at _timestamp na mga field (halimbawa supplies_2020_10):

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

Gayunpaman, ang istraktura ng mga papasok na kaganapan ay maaaring magbago sa runtime. Nagpatupad kami ng algorithm upang suriin ang pagkakaiba sa pagitan ng istraktura ng isang umiiral na talahanayan at ng istraktura ng isang papasok na kaganapan. Kung may nakitang pagkakaiba, ia-update ang talahanayan gamit ang mga bagong field. Upang gawin ito, gamitin ang patch SQL query:

#ΠŸΡ€ΠΈΠΌΠ΅Ρ€ для Postgres
ALTER TABLE "schema"."table" ADD COLUMN new_column character varying

arkitektura

Ang kasaysayan ng aming open source: kung paano namin ginawa ang serbisyo ng analytics sa Go at ginawa itong available sa publiko

Bakit kailangan mong magsulat ng mga kaganapan sa file system, at hindi lamang isulat ang mga ito nang direkta sa database? Ang mga database ay hindi palaging gumaganap nang maayos kapag nakikitungo sa malaking bilang ng mga pagsingit (Mga rekomendasyon sa postgres). Upang gawin ito, ang Logger ay nagsusulat ng mga papasok na kaganapan sa isang file at sa isang hiwalay na goroutine (thread) Binabasa ng file reader ang file, pagkatapos ay ang data ay na-convert at tinutukoy. Matapos tiyakin ng Tagapamahala ng talahanayan na ang schema ng talahanayan ay napapanahon, ang data ay isusulat sa database sa isang batch. Kasunod nito, idinagdag namin ang kakayahang magsulat ng data nang direkta sa database, ngunit ginagamit namin ang mode na ito para sa mga kaganapan na hindi marami - halimbawa, mga conversion.

Open Source at mga plano para sa hinaharap

Sa ilang mga punto, ang serbisyo ay nagsimulang magmukhang isang ganap na produkto at nagpasya kaming ilabas ito sa Open Source. Sa kasalukuyan, ipinatupad ang mga pagsasama sa Postgres, ClickHouse, BigQuery, Redshift, S3, Snowflake. Sinusuportahan ng lahat ng integration ang parehong batch at streaming mode ng paglo-load ng data. Nagdagdag ng suporta para sa mga kahilingan sa pamamagitan ng API.

Ang kasalukuyang scheme ng pagsasama ay ganito ang hitsura:

Ang kasaysayan ng aming open source: kung paano namin ginawa ang serbisyo ng analytics sa Go at ginawa itong available sa publiko

Kahit na ang serbisyo ay maaaring gamitin nang nakapag-iisa (halimbawa gamit ang Docker), mayroon din kami naka-host na bersyon, kung saan maaari kang mag-set up ng pagsasama sa isang data warehouse, magdagdag ng CNAME sa iyong domain at tingnan ang mga istatistika sa bilang ng mga kaganapan. Ang aming mga agarang plano ay magdagdag ng kakayahang pagsama-samahin hindi lamang ang mga istatistika mula sa isang mapagkukunan ng web, kundi pati na rin ang data mula sa mga panlabas na mapagkukunan ng data at i-save ang mga ito sa anumang storage na gusto mo!

β†’ GitHub
β†’ Records
β†’ Walang ingat

Matutuwa kami kung tutulong ang EventNative na malutas ang iyong mga problema!

Ang mga rehistradong user lamang ang maaaring lumahok sa survey. Mag-sign in, pakiusap

Anong sistema ng pangongolekta ng istatistika ang ginagamit sa iyong kumpanya?

  • 48,0%Google Analytics12

  • 4,0%Segment1

  • 16,0%Isa pa (isulat sa mga komento)4

  • 32,0%Ipinatupad ang iyong serbisyo8

25 user ang bumoto. 6 na user ang umiwas.

Pinagmulan: www.habr.com

Magdagdag ng komento