A história do nosso código aberto: como criamos um serviço de análise em Go e o disponibilizamos ao público

Atualmente, quase todas as empresas do mundo coletam estatísticas sobre as ações dos usuários em um recurso da web. A motivação é clara - as empresas querem saber como o seu produto/website é utilizado e compreender melhor os seus utilizadores. É claro que existe um grande número de ferramentas no mercado para resolver este problema - desde sistemas analíticos que fornecem dados na forma de dashboards e gráficos (por exemplo Google Analytics) para a Plataforma de Dados do Cliente, que permite coletar e agregar dados de diferentes fontes em qualquer armazém (por exemplo Segmento).

Mas encontramos um problema que ainda não foi resolvido. Assim nasceu EventNative — serviço analítico de código aberto. Leia sobre por que decidimos desenvolver nosso próprio serviço, o que ele nos proporcionou e qual foi o resultado final (com trechos de código).

A história do nosso código aberto: como criamos um serviço de análise em Go e o disponibilizamos ao público

Por que devemos desenvolver nosso próprio serviço?

Eram os anos noventa, sobrevivemos o melhor que pudemos. 2019, desenvolvemos a API First Customer Data Platform kSense, o que possibilitou agregar dados de diferentes fontes (Facebook ads, Stripe, Salesforce, Google play, Google Analytics, etc.) para uma análise de dados mais conveniente, identificação de dependências, etc. Percebemos que muitos usuários utilizam nossa plataforma para análise de dados especificamente Google Analytics (doravante GA). Conversamos com alguns usuários e descobrimos que eles precisam dos dados analíticos de seus produtos que recebem usando o GA, mas Dados de amostras do Google e para muitos, a interface do usuário do GA não é o padrão de conveniência. Já conversamos bastante com nossos usuários e percebemos que muitos também usavam a plataforma Segment (que, aliás, foi outro dia vendido por US$ 3.2 bilhões).

Eles instalaram um pixel javascript Segment em seu recurso da web e os dados sobre o comportamento de seus usuários foram carregados no banco de dados especificado (por exemplo, Postgres). Mas o Segment também tem sua desvantagem: o preço. Por exemplo, se um recurso da web tiver 90,000 MTU (usuários monitorados mensalmente), você precisará pagar aproximadamente US$ 1,000 por mês ao caixa. Houve também um terceiro problema - algumas extensões de navegador (como AdBlock) bloquearam a coleta de análises porque... solicitações http do navegador foram enviadas para os domínios GA e Segment. A partir dos desejos dos nossos clientes, criamos um serviço analítico que coleta um conjunto completo de dados (sem amostragem), é gratuito e pode funcionar em nossa própria infraestrutura.

Como funciona o serviço

O serviço consiste em três partes: um pixel javascript (que mais tarde reescrevemos em TypeScript), a parte do servidor é implementada na linguagem GO e foi planejado usar Redshift e BigQuery como um banco de dados interno (mais tarde eles adicionaram suporte para Postgres, ClickHouse e Snowflake).

Foi decidido deixar inalterada a estrutura dos eventos GA e Segmento. Tudo o que foi necessário foi duplicar todos os eventos do recurso web onde o pixel está instalado para o nosso backend. Acontece que isso não é difícil de fazer. O pixel Javascript substituiu o método original da biblioteca GA por um novo, que duplicou o evento em nosso 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);
        });
    });
}

Com o Segment pixel tudo fica mais simples, possui métodos de middleware, um dos quais utilizamos.


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

Além de copiar eventos, adicionamos a capacidade de enviar JSON arbitrário:


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

A seguir, vamos falar sobre a parte do servidor. O backend deve aceitar solicitações http, preenchê-las com informações adicionais, por exemplo, dados geográficos (obrigado mente máxima para isso) e registre-o no banco de dados. Queríamos tornar o serviço o mais conveniente possível para que pudesse ser usado com configuração mínima. Implementamos a funcionalidade de determinar o esquema de dados com base na estrutura do evento JSON recebido. Os tipos de dados são definidos por valores. Os objetos aninhados são decompostos e reduzidos a uma estrutura plana:

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

No entanto, atualmente os arrays são simplesmente convertidos em strings porque Nem todos os bancos de dados relacionais suportam campos repetidos. Também é possível alterar os nomes dos campos ou excluí-los usando regras de mapeamento opcionais. Eles permitem alterar o esquema de dados, se necessário, ou converter um tipo de dados em outro. Por exemplo, se um campo JSON contiver uma string com carimbo de data/hora (campo_3_sub_field_1_sub_sub_field_1 do exemplo acima), então para criar um campo no banco de dados com o tipo timestamp, você precisa escrever uma regra de mapeamento na configuração. Em outras palavras, o tipo de dados do campo é determinado primeiro pelo valor json e, em seguida, a regra de conversão de tipo (se configurada) é aplicada. Identificamos 4 tipos de dados principais: STRING, FLOAT64, INT64 e TIMESTAMP. As regras de mapeamento e conversão de tipo são assim:

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

Algoritmo para determinar o tipo de dados:

  • converter estrutura json em estrutura plana
  • determinando o tipo de dados dos campos por valores
  • aplicando mapeamento e regras de conversão de tipo

Então, a partir da estrutura json de entrada:

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

o esquema de dados será obtido:

"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

Também pensamos que o usuário deveria ser capaz de configurar o particionamento ou dividir os dados no banco de dados de acordo com outros critérios e implementamos a capacidade de definir o nome da tabela com uma constante ou expressão na configuração. No exemplo abaixo, o evento será salvo em uma tabela com nome calculado com base nos valores dos campos product_type e _timestamp (por exemplo suprimentos_2020_10):

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

No entanto, a estrutura dos eventos recebidos pode mudar em tempo de execução. Implementamos um algoritmo para verificar a diferença entre a estrutura de uma tabela existente e a estrutura de um evento recebido. Caso seja encontrada diferença, a tabela será atualizada com novos campos. Para fazer isso, use a consulta SQL de patch:

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

Arquitetura

A história do nosso código aberto: como criamos um serviço de análise em Go e o disponibilizamos ao público

Por que você precisa gravar eventos no sistema de arquivos e não apenas gravá-los diretamente no banco de dados? Os bancos de dados nem sempre funcionam bem ao lidar com um grande número de inserções (Recomendações do Postgres). Para fazer isso, o Logger grava os eventos recebidos em um arquivo e em uma goroutine (thread) separada, o leitor de arquivos lê o arquivo e, em seguida, os dados são convertidos e determinados. Depois que o gerenciador de tabelas se certificar de que o esquema da tabela está atualizado, os dados serão gravados no banco de dados em um lote. Posteriormente, adicionamos a capacidade de gravar dados diretamente no banco de dados, mas usamos esse modo para eventos que não são numerosos - por exemplo, conversões.

Código aberto e planos para o futuro

Em algum momento, o serviço começou a parecer um produto completo e decidimos lançá-lo em código aberto. Atualmente, foram implementadas integrações com Postgres, ClickHouse, BigQuery, Redshift, S3, Snowflake. Todas as integrações suportam modos de carregamento de dados em lote e streaming. Adicionado suporte para solicitações via API.

O esquema de integração atual é assim:

A história do nosso código aberto: como criamos um serviço de análise em Go e o disponibilizamos ao público

Embora o serviço possa ser usado de forma independente (por exemplo, usando Docker), também temos versão hospedada, onde você pode configurar a integração com um data warehouse, adicionar um CNAME ao seu domínio e visualizar estatísticas sobre o número de eventos. Nossos planos imediatos são adicionar a capacidade de agregar não apenas estatísticas de um recurso da web, mas também dados de fontes externas e salvá-los em qualquer armazenamento de sua escolha!

→ GitHub
→ Documentação
→ Slack

Ficaremos felizes se a EventNative ajudar a resolver seus problemas!

Apenas usuários registrados podem participar da pesquisa. Entrarpor favor

Qual sistema de coleta de estatísticas é utilizado na sua empresa?

  • 48,0%Google Analytics12

  • 4,0%Segmento1

  • 16,0%Outro (escreva nos comentários)4

  • 32,0%Implementou seu serviço8

25 usuários votaram. 6 usuários se abstiveram.

Fonte: habr.com

Adicionar um comentário