La storia del nostro open source: come abbiamo realizzato un servizio di analisi in Go e lo abbiamo reso disponibile al pubblico

Attualmente, quasi tutte le aziende nel mondo raccolgono statistiche sulle azioni degli utenti su una risorsa web. La motivazione è chiara: le aziende vogliono sapere come viene utilizzato il loro prodotto/sito web e comprendere meglio i propri utenti. Naturalmente, sul mercato sono disponibili numerosi strumenti per risolvere questo problema, dai sistemi di analisi che forniscono dati sotto forma di dashboard e grafici (ad esempio Google Analytics) alla Customer Data Platform, che consentono di raccogliere e aggregare dati da diverse fonti in qualsiasi magazzino (ad es Segmento).

Ma abbiamo riscontrato un problema che non è stato ancora risolto. Così è nato EventoNative - servizio di analisi open source. Leggi perché abbiamo deciso di sviluppare il nostro servizio, cosa ci ha dato e quale è stato il risultato finale (con pezzi di codice).

La storia del nostro open source: come abbiamo realizzato un servizio di analisi in Go e lo abbiamo reso disponibile al pubblico

Perché dovremmo sviluppare il nostro servizio?

Erano gli anni Novanta, sopravvivevamo come meglio potevamo. Nel 2019 abbiamo sviluppato l'API First Customer Data Platform kSense, che ha permesso di aggregare dati provenienti da diverse fonti (annunci Facebook, Stripe, Salesforce, Google Play, Google Analytics, ecc.) per un'analisi dei dati più conveniente, l'identificazione delle dipendenze, ecc. Abbiamo notato che molti utenti utilizzano la nostra piattaforma per l'analisi dei dati nello specifico Google Analytics (di seguito GA). Abbiamo parlato con alcuni utenti e abbiamo scoperto che hanno bisogno dei dati analitici per il loro prodotto che ricevono utilizzando GA, ma Google campiona i dati e per molti, l'interfaccia utente GA non è lo standard di comodità. Abbiamo avuto abbastanza conversazioni con i nostri utenti e ci siamo resi conto che molti utilizzavano anche la piattaforma Segment (che, tra l'altro, era proprio l'altro giorno venduto per 3.2 miliardi di dollari).

Hanno installato un pixel javascript Segment sulla loro risorsa web e i dati sul comportamento dei loro utenti sono stati caricati nel database specificato (ad esempio Postgres). Ma Segment ha anche il suo svantaggio: il prezzo. Ad esempio, se una risorsa web ha 90,000 MTU (utenti tracciati mensilmente), dovrai pagare circa 1,000 $ al mese al cassiere. C'era anche un terzo problema: alcune estensioni del browser (come AdBlock) bloccavano la raccolta di analisi perché... Le richieste http dal browser sono state inviate ai domini GA e Segment. Sulla base dei desideri dei nostri clienti, abbiamo creato un servizio di analisi che raccoglie un set completo di dati (senza campionamento), è gratuito e può funzionare sulla nostra infrastruttura.

Come funziona il servizio

Il servizio è composto da tre parti: un pixel javascript (che successivamente abbiamo riscritto in dattiloscritto), la parte server è implementata nel linguaggio GO, ed era previsto l'utilizzo di Redshift e BigQuery come database interno (successivamente hanno aggiunto il supporto per Postgres, ClickHouse e Snowflake).

Si è deciso di lasciare invariata la struttura degli eventi GA e Segmento. Tutto ciò che serviva era duplicare tutti gli eventi dalla risorsa web in cui è installato il pixel al nostro backend. A quanto pare, questo non è difficile da fare. Il pixel Javascript ha sovrascritto il metodo della libreria GA originale con uno nuovo, che ha duplicato l'evento nel nostro 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);
        });
    });
}

Con il pixel Segment è tutto più semplice, ha metodi middleware, uno dei quali abbiamo utilizzato.


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

Oltre a copiare gli eventi, abbiamo aggiunto la possibilità di inviare json arbitrari:


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

Successivamente, parliamo della parte server. Il backend dovrebbe accettare richieste http, riempirle con informazioni aggiuntive, ad esempio dati geografici (grazie maxmind per questo) e registrarlo nel database. Volevamo rendere il servizio il più conveniente possibile in modo che possa essere utilizzato con una configurazione minima. Abbiamo implementato la funzionalità per determinare lo schema dei dati in base alla struttura dell'evento json in entrata. I tipi di dati sono definiti da valori. Gli oggetti nidificati vengono scomposti e ridotti a 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, gli array attualmente vengono semplicemente convertiti in stringhe perché Non tutti i database relazionali supportano campi ripetuti. È anche possibile modificare i nomi dei campi o eliminarli utilizzando regole di mappatura opzionali. Consentono di modificare lo schema dei dati, se necessario, o di convertire un tipo di dati in un altro. Ad esempio, se un campo JSON contiene una stringa con timestamp (field_3_sub_field_1_sub_sub_field_1 dall'esempio sopra), quindi per creare nel database un campo di tipo timestamp è necessario scrivere nella configurazione una regola di mappatura. In altre parole, il tipo di dati del campo viene determinato prima dal valore json, quindi viene applicata la regola di casting del tipo (se configurata). Abbiamo identificato 4 tipi di dati principali: STRING, FLOAT64, INT64 e TIMESTAMP. Le regole di mappatura e cast del tipo sono simili a queste:

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

Algoritmo per determinare il tipo di dati:

  • convertire la struttura JSON in struttura piatta
  • determinare il tipo di dati dei campi in base ai valori
  • applicare regole di mappatura e casting del tipo

Quindi dalla struttura json in entrata:

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

si otterrà lo schema dei dati:

"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

Abbiamo anche pensato che l'utente dovesse essere in grado di configurare il partizionamento o dividere i dati nel database secondo altri criteri e abbiamo implementato la possibilità di impostare il nome della tabella con una costante o espressione nella configurazione. Nell'esempio seguente l'evento verrà salvato in una tabella con un nome calcolato in base ai valori dei campi product_type e _timestamp (ad esempio forniture_2020_10):

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

Tuttavia, la struttura degli eventi in entrata può cambiare in fase di esecuzione. Abbiamo implementato un algoritmo per verificare la differenza tra la struttura di una tabella esistente e la struttura di un evento in arrivo. Se viene trovata una differenza, la tabella verrà aggiornata con nuovi campi. Per fare ciò, utilizzare la query SQL patch:

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

Architettura

La storia del nostro open source: come abbiamo realizzato un servizio di analisi in Go e lo abbiamo reso disponibile al pubblico

Perché è necessario scrivere eventi nel file system e non semplicemente scriverli direttamente nel database? I database non sempre funzionano bene quando si tratta di gestire un numero elevato di inserimenti (Raccomandazioni Postgres). Per fare ciò, Logger scrive gli eventi in entrata in un file e in un lettore di file goroutine (thread) separato legge il file, quindi i dati vengono convertiti e determinati. Dopo che Gestione tabelle si è assicurato che lo schema della tabella sia aggiornato, i dati verranno scritti nel database in un unico batch. Successivamente abbiamo aggiunto la possibilità di scrivere i dati direttamente nel database, ma utilizziamo questa modalità per eventi non numerosi, ad esempio le conversioni.

Open Source e progetti per il futuro

Ad un certo punto, il servizio ha iniziato ad assomigliare a un prodotto a tutti gli effetti e abbiamo deciso di rilasciarlo in Open Source. Attualmente sono state implementate integrazioni con Postgres, ClickHouse, BigQuery, Redshift, S3, Snowflake. Tutte le integrazioni supportano sia la modalità batch che quella streaming di caricamento dei dati. Aggiunto supporto per richieste tramite API.

L’attuale schema di integrazione si presenta così:

La storia del nostro open source: come abbiamo realizzato un servizio di analisi in Go e lo abbiamo reso disponibile al pubblico

Sebbene il servizio possa essere utilizzato in modo indipendente (ad esempio utilizzando Docker), abbiamo anche versione ospitata, in cui puoi impostare l'integrazione con un data warehouse, aggiungere un CNAME al tuo dominio e visualizzare le statistiche sul numero di eventi. I nostri piani immediati sono di aggiungere la possibilità di aggregare non solo le statistiche da una risorsa web, ma anche dati da fonti di dati esterne e salvarli in qualsiasi spazio di archiviazione di tua scelta!

→ GitHub
→ Documentazione
→ Slack

Saremo lieti se EventNative ti aiuta a risolvere i tuoi problemi!

Solo gli utenti registrati possono partecipare al sondaggio. AccediPer favore.

Quale sistema di raccolta statistiche viene utilizzato nella vostra azienda?

  • 48,0%Google Analytics12

  • 4,0%Segmento1

  • 16,0%Un altro (scrivi nei commenti)4

  • 32,0%Implementato il tuo servizio8

25 utenti hanno votato. 6 utenti si sono astenuti.

Fonte: habr.com

Aggiungi un commento