Гісторыя нашага open source: як мы зрабілі сэрвіс аналітыкі на Go і выклалі яго ў адчынены доступ

У наш час практычна кожная кампанія ў міры збірае статыстыку аб дзеяннях карыстача на web рэсурсе. Матывацыя зразумелая - кампаніі жадаюць ведаць як выкарыстоўваецца іх прадукт/вэб сайт і лепш разумець сваіх карыстачоў. Вядома на рынку існуе вялікая колькасць інструментаў для вырашэння дадзенай праблемы - ад сістэм аналітыкі, якія падаюць дадзеныя ў выглядзе дашбордаў і графікаў (напрыклад Google Analytics) да Customer Data Platform, якія дазваляюць збіраць і агрэгаваць дадзеныя з розных крыніц у любым сховішчы (напрыклад Сегмент).

Але мы знайшлі праблему, якая яшчэ не была вырашана. Так нарадзіўся EventNative - open-source сэрвіс аналітыкі. Пра тое, чаму мы пайшлі на распрацоўку ўласнага сэрвісу, што нам гэта дало і што ў выніку атрымалася (з кавалкамі кода), чытайце пад катом.

Гісторыя нашага open source: як мы зрабілі сэрвіс аналітыкі на Go і выклалі яго ў адчынены доступ

Навошта нам распрацоўваць уласны сервіс?

Гэта былі дзевяностыя, мы выжывалі як маглі. 2019 год, мы распрацоўвалі API First Customer Data Platform kSense, якая дазваляла агрэгаваць дадзеныя з розных крыніц (Facebook ads, Stripe, Salesforce, Google play, Google Analytics і інш) для зручнейшага аналізу дадзеных, выяўленні залежнасцяў і г.д. Мы заўважылі, што шматлікія карыстачы выкарыстоўваюць нашу платформу для аналізу дадзеных менавіта Google Analytics (далей GA). З некаторымі карыстальнікамі мы пагаварылі і высветлілі, што ім патрэбныя дадзеныя аналітыкі іх прадукта, якія яны атрымліваюць з дапамогай GA, але Google сэмплюе дадзеныя і для шматлікіх GA User interface не з'яўляецца эталонам выгоды. Мы правялі дастатковую колькасць гутарак з нашымі карыстальнікамі і зразумелі, што многія таксама выкарыстоўвалі платформу Segment (якая, дарэчы, літаральна на днях была прададзена за 3.2 млрд$).

Яны ўсталёўвалі Segment javascript піксель на свой web рэсурс і дадзеныя аб паводзінах іх карыстачоў загружаліся ў паказаную базу дадзеных (напрыклад Postgres). Але і ў Segment ёсць свой мінус – кошт. Напрыклад, калі ў інтэрнэт рэсурсу 90,000 MTU (monthly tracked users) то трэба аплаціць у касу ~1,000 $ у месяц. Таксама была і трэцяя праблема - некаторыя пашырэнні для браўзэра (такія як AdBlock) блакавалі збор аналітыкі т.к. http запыты з браўзэра адпраўляліся на дамены GA і Segment. Зыходзячы з жадання нашых кліентаў, мы зрабілі сэрвіс аналітыкі, які збірае поўны набор даных (без сэмплінга), бясплатны і можа працаваць на ўласнай інфраструктуры.

Як уладкованы сэрвіс

Сэрвіс складаецца з трох частак: javascript піксель (які мы пасля перапісалі на typescript), серверная частка рэалізаваная на мове GO і ў якасці in-house базы дадзеных планавалася выкарыстоўваць Redshift і BigQuery (пазней дадалі падтрымку Postgres, ClickHouse і Snowflake).

Структуру падзей GA і Segment вырашылі пакінуць без змены. Усё, што было патрэбна, гэта дубліраваць усе падзеі з web рэсурсу, дзе ўсталяваны піксель, у наш бэкенд. Як аказалася, гэта зрабіць нескладана. Javascript піксель перавызначаў арыгінальны метад бібліятэкі GA на новы, які дубліраваў падзею ў нашу сістэму.

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

З пікселем Segment ўсё прасцей, ен мае middleware метады, адным з іх мы і скарысталіся.


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

Апроч капіявання падзей мы дадалі магчымасць адпраўляць адвольны 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'
});

Далей пагаворым пра серверную частку. Backend павінен прымаць http запыты, напаўняць іх дадатковай інфармацыяй, да прыкладу, геа дадзенымі (дзякуй maxmind за гэта) і запісваць у базу дадзеных. Мы хацелі зрабіць сэрвіс максімальна зручным, каб яго можна было выкарыстоўваць з мінімальнай канфігурацыяй. Мы рэалізавалі функцыянал вызначэння схемы даных на аснове структуры ўваходнага json падзеі. Тыпы дадзеных вызначаюцца па значэннях. Укладзеныя аб'екты раскладваюцца і прыводзяцца да плоскай структуры:

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

Аднак масівы на дадзены момант проста канвертуюцца ў радок т.я. не ўсе рэляцыйныя базы дадзеных падтрымліваюць паўтаральныя палі (repeated fields). Таксама ёсць магчымасць змяняць назвы палёў ці выдаляць іх з дапамогай апцыянальных правіл мапінгу. Яны дазваляюць змяняць схему дадзеных, калі гэта запатрабуецца ці прыводзіць адзін тып дадзеных да іншага. Напрыклад, калі ў json поле знаходзіцца радок з timestamp (field_3_sub_field_1_sub_sub_field_1 з прыкладу вышэй) то для таго каб стварыць поле ў базе дадзеных з тыпам timestamp, неабходна напісаць правіла мапінгу ў канфігурацыі. Іншымі словамі, тып дадзеных поля вызначаецца спачатку па json значэнню, а затым ужываецца правіла прывядзення тыпаў (калі яно сканфігуравана). Мы вылучылі 4 асноўных тыпу даных: STRING, FLOAT64, INT64 і TIMESTAMP. Правілы мапінгу і прывядзенні тыпаў выглядаюць наступным чынам:

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

Алгарытм вызначэння тыпу дадзеных:

  • пераўтварэнне json структуры ў плоскую структуру
  • вызначэнне тыпу дадзеных палёў па значэннях
  • прымяненне правілаў мапінгу і прывядзення тыпаў

Тады з уваходнай 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"
    }
}

будзе атрымана схема дадзеных:

"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

Таксама мы падумалі, што карыстач павінен мець магчымасць наладзіць партыцыянаванне або падзяляць дадзеныя ў БД па іншых крытэрыях і рэалізавалі магчымасць задаваць імя табліцы канстантай або выразам у канфігурацыі. У прыкладзе ніжэй падзея будзе захавана ў табліцу з імем, вылічаным на аснове значэнняў палёў product_type і _timestamp (напрыклад supplies_2020_10):

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

Аднак структура ўваходных падзей можа змяняцца ў runtime. Мы рэалізавалі алгарытм праверкі розніцы паміж структурай існуючай табліцы і структурай уваходнай падзеі. Калі розніца знойдзена - табліца будзе абноўлена новымі палямі. Для гэтага выкарыстоўваецца patch SQL запыт:

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

Архітэктура

Гісторыя нашага open source: як мы зрабілі сэрвіс аналітыкі на Go і выклалі яго ў адчынены доступ

Навошта трэба запісваць падзеі на файлавую сістэму, а не проста пісаць іх адразу ў БД? Базы дадзеных не заўсёды дэманструюць высокую прадукцыйнасць пры вялікай колькасці ўставак (рэкамендацыі Postgres). Для гэтага Logger запісвае ўваходныя падзеі ў файл і ўжо ў асобнай гаруціне (струмені) File reader чытае файл, далей адбываецца пераўтварэнне і азначэнне схемы дадзеных. Пасля таго як Table manager пераканаецца, што схема табліцы актуальная – дадзеныя будуць запісаныя ў БД адным батчам. Пасля мы дадалі магчымасць запісваць дадзеныя напрамую ў БД, але ўжываем такі рэжым для падзей, якіх не шмат - напрыклад канверсіі.

Open Source і планы на будучыню

У нейкі момант сэрвіс стаў падобны на паўнавартасны прадукт і мы вырашылі выкласці яго ў Open Source. На дадзены момант рэалізаваны інтэграцыі з Postgres, ClickHouse, BigQuery, Redshift, S3, Snowflake. Усе інтэграцыі падтрымліваюць як batch, так і streaming рэжымы загрузкі даных. Дададзена падтрымка запытаў праз API.

Бягучая інтэграцыйная схема выглядае наступным чынам:

Гісторыя нашага open source: як мы зрабілі сэрвіс аналітыкі на Go і выклалі яго ў адчынены доступ

Нягледзячы на ​​тое, што сэрвіс можна выкарыстоўваць самастойна (напрыклад з дапамогай Docker), у нас таксама ёсць hosted версія, у якой можна наладзіць інтэграцыю са сховішчам дадзеных, дадаць CNAME на свой дамен і паглядзець статыстыку па колькасці падзей. Нашы бліжэйшыя планы - даданне магчымасці агрэгаваць не толькі статыстыку з вэб рэсурсу, але і дадзеныя са знешніх крыніц дадзеных і захоўваць іх у любое сховішча на выбар!

→ GitHub
→ Дакументацыя
→ Млявы

Будзем рады калі EventNative дапаможа вырашыць вашыя задачы!

Толькі зарэгістраваныя карыстачы могуць удзельнічаць у апытанні. Увайдзіце, Калі ласка.

Якая сістэма збору статыстыкі выкарыстоўваецца ў вашай кампаніі

  • 48,0%Google Analytics12

  • 4,0%Segment1

  • 16,0%Іншую (напішыце ў каментарах)4

  • 32,0%Рэалізавалі свой сервіс8

Прагаласавалі 25 карыстальнікаў. Устрымаліся 6 карыстальнікаў.

Крыніца: habr.com

Дадаць каментар