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
Mas encontramos um problema que ainda não foi resolvido. Assim nasceu
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
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
//входящий 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
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
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 (
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:
Embora o serviço possa ser usado de forma independente (por exemplo, usando Docker), também temos
→
→
→
Ficaremos felizes se a EventNative ajudar a resolver seus problemas!
Apenas usuários registrados podem participar da pesquisa.
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