L'histoire de notre open source : comment nous avons créé un service d'analyse dans Go et l'avons rendu public

Actuellement, presque toutes les entreprises dans le monde collectent des statistiques sur les actions des utilisateurs sur une ressource Web. La motivation est claire : les entreprises veulent savoir comment leur produit/site Web est utilisé et mieux comprendre leurs utilisateurs. Bien entendu, il existe un grand nombre d'outils sur le marché pour résoudre ce problème - depuis les systèmes d'analyse qui fournissent des données sous forme de tableaux de bord et de graphiques (par exemple Google Analytics) à Customer Data Platform, qui vous permettent de collecter et d'agréger des données provenant de différentes sources dans n'importe quel entrepôt (par exemple Segment).

Mais nous avons découvert un problème qui n'a pas encore été résolu. Ainsi est né ÉvénementNatif — service d'analyse open source. Découvrez pourquoi nous avons décidé de développer notre propre service, ce qu'il nous a apporté et quel a été le résultat final (avec des morceaux de code).

L'histoire de notre open source : comment nous avons créé un service d'analyse dans Go et l'avons rendu public

Pourquoi devrions-nous développer notre propre service ?

C’était les années 2019, nous avons survécu du mieux que nous pouvions. XNUMX, nous avons développé l'API First Customer Data Platform kSense, qui a permis d'agréger des données provenant de différentes sources (publicités Facebook, Stripe, Salesforce, Google play, Google Analytics, etc.) pour une analyse plus pratique des données, l'identification des dépendances, etc. Nous avons constaté que de nombreux utilisateurs utilisent notre plateforme pour l'analyse de données, notamment Google Analytics (ci-après GA). Nous avons discuté avec certains utilisateurs et découvert qu'ils avaient besoin des données analytiques pour leur produit qu'ils reçoivent à l'aide de GA, mais Google échantillonne les données et pour beaucoup, l’interface utilisateur GA n’est pas la norme en matière de commodité. Nous avons eu suffisamment de conversations avec nos utilisateurs et avons réalisé que beaucoup utilisaient également la plateforme Segment (qui, d'ailleurs, était l'autre jour vendu pour 3.2 milliards de dollars).

Ils ont installé un pixel javascript Segment sur leur ressource Web et les données sur le comportement de leurs utilisateurs ont été chargées dans la base de données spécifiée (par exemple Postgres). Mais Segment a aussi son inconvénient : le prix. Par exemple, si une ressource Web dispose de 90,000 1,000 MTU (utilisateurs suivis mensuellement), vous devez alors payer environ XNUMX XNUMX $ par mois au caissier. Il y avait également un troisième problème : certaines extensions de navigateur (telles qu'AdBlock) bloquaient la collecte d'analyses parce que... Les requêtes http du navigateur ont été envoyées aux domaines GA et Segment. Sur la base des souhaits de nos clients, nous avons créé un service d'analyse qui collecte un ensemble complet de données (sans échantillonnage), est gratuit et peut fonctionner sur notre propre infrastructure.

Comment fonctionne le service

Le service se compose de trois parties : un pixel javascript (que nous avons ensuite réécrit en dactylographié), la partie serveur est implémentée dans le langage GO, et il était prévu d'utiliser Redshift et BigQuery comme base de données interne (ils ont ensuite ajouté la prise en charge de Postgres, ClickHouse et Snowflake).

Il a été décidé de laisser inchangée la structure des événements de l'AG et du Segment. Il suffisait de dupliquer tous les événements de la ressource Web sur laquelle le pixel est installé vers notre backend. Il s’avère que ce n’est pas difficile à faire. Le pixel Javascript a remplacé la méthode de la bibliothèque GA d'origine par une nouvelle, qui a dupliqué l'événement dans notre système.

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

Avec le pixel Segment, tout est plus simple, il dispose de méthodes middleware, dont nous avons utilisé une.


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

En plus de copier des événements, nous avons ajouté la possibilité d'envoyer du json arbitraire :


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

Parlons ensuite de la partie serveur. Le backend doit accepter les requêtes http, les remplir avec des informations supplémentaires, par exemple des données géographiques (merci maxmental pour cela) et enregistrez-le dans la base de données. Nous voulions rendre le service aussi pratique que possible afin qu'il puisse être utilisé avec une configuration minimale. Nous avons implémenté la fonctionnalité permettant de déterminer le schéma de données en fonction de la structure de l'événement json entrant. Les types de données sont définis par des valeurs. Les objets imbriqués sont décomposés et réduits à une structure plate :

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

Cependant, les tableaux sont actuellement simplement convertis en chaînes car Toutes les bases de données relationnelles ne prennent pas en charge les champs répétés. Il est également possible de modifier les noms de champs ou de les supprimer à l'aide de règles de mappage facultatives. Ils vous permettent de modifier le schéma des données si nécessaire ou de convertir un type de données en un autre. Par exemple, si un champ json contient une chaîne avec un horodatage (field_3_sub_field_1_sub_sub_field_1 de l'exemple ci-dessus), puis afin de créer un champ dans la base de données avec le type d'horodatage, vous devez écrire une règle de mappage dans la configuration. En d’autres termes, le type de données du champ est d’abord déterminé par la valeur json, puis la règle de conversion de type (si configurée) est appliquée. Nous avons identifié 4 types de données principaux : STRING, FLOAT64, INT64 et TIMESTAMP. Les règles de mappage et de transtypage ressemblent à ceci :

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

Algorithme de détermination du type de données :

  • convertir la structure json en structure plate
  • déterminer le type de données des champs par valeurs
  • application de règles de mappage et de transtypage

Puis à partir de la structure 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"
    }
}

le schéma de données sera obtenu :

"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

Nous avons également pensé que l'utilisateur devrait pouvoir configurer le partitionnement ou diviser les données dans la base de données selon d'autres critères et avons implémenté la possibilité de définir le nom de la table avec une constante ou expression dans la configuration. Dans l'exemple ci-dessous, l'événement sera enregistré dans une table avec un nom calculé en fonction des valeurs des champs product_type et _timestamp (par exemple fournitures_2020_10):

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

Cependant, la structure des événements entrants peut changer au moment de l'exécution. Nous avons implémenté un algorithme pour vérifier la différence entre la structure d'une table existante et la structure d'un événement entrant. Si une différence est trouvée, le tableau sera mis à jour avec de nouveaux champs. Pour ce faire, utilisez la requête SQL patch :

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

Architecture

L'histoire de notre open source : comment nous avons créé un service d'analyse dans Go et l'avons rendu public

Pourquoi avez-vous besoin d'écrire les événements dans le système de fichiers, et pas simplement de les écrire directement dans la base de données ? Les bases de données ne fonctionnent pas toujours correctement lorsqu'elles traitent un grand nombre d'insertions (Recommandations Postgres). Pour ce faire, Logger écrit les événements entrants dans un fichier et dans un lecteur de fichier goroutine (thread) séparé lit le fichier, puis les données sont converties et déterminées. Une fois que le gestionnaire de tables s'est assuré que le schéma de la table est à jour, les données seront écrites dans la base de données en un seul lot. Par la suite, nous avons ajouté la possibilité d'écrire des données directement dans la base de données, mais nous utilisons ce mode pour des événements peu nombreux - par exemple des conversions.

Open Source et projets pour l'avenir

À un moment donné, le service a commencé à ressembler à un produit à part entière et nous avons décidé de le publier en Open Source. Actuellement, des intégrations avec Postgres, ClickHouse, BigQuery, Redshift, S3, Snowflake ont été mises en œuvre. Toutes les intégrations prennent en charge les modes de chargement de données par lots et par streaming. Ajout de la prise en charge des requêtes via API.

Le schéma d'intégration actuel ressemble à ceci :

L'histoire de notre open source : comment nous avons créé un service d'analyse dans Go et l'avons rendu public

Bien que le service puisse être utilisé indépendamment (par exemple en utilisant Docker), nous avons également version hébergée, dans lequel vous pouvez configurer l'intégration avec un entrepôt de données, ajouter un CNAME à votre domaine et afficher des statistiques sur le nombre d'événements. Nos plans immédiats sont d'ajouter la possibilité d'agréger non seulement les statistiques d'une ressource Web, mais également les données provenant de sources de données externes et de les enregistrer dans n'importe quel stockage de votre choix !

→ GitHub
→ Documentation
→ Slack

Nous serons heureux si EventNative vous aide à résoudre vos problèmes !

Seuls les utilisateurs enregistrés peuvent participer à l'enquête. se connecters'il te plait.

Quel système de collecte de statistiques est utilisé dans votre entreprise ?

  • 48,0%Google Analytics12

  • 4,0%Segment1

  • 16,0%Un autre (écrire dans les commentaires)4

  • 32,0%Implémenté votre service8

25 utilisateurs ont voté. 6 utilisateurs se sont abstenus.

Source: habr.com

Ajouter un commentaire