La historia de nuestro código abierto: cómo creamos un servicio de análisis en Go y lo pusimos a disposición del público

Actualmente, casi todas las empresas del mundo recopilan estadísticas sobre las acciones de los usuarios en un recurso web. La motivación es clara: las empresas quieren saber cómo se utiliza su producto/sitio web y comprender mejor a sus usuarios. Por supuesto, existe una gran cantidad de herramientas en el mercado para resolver este problema, desde sistemas de análisis que proporcionan datos en forma de paneles y gráficos (por ejemplo, Google Analytics) a la Plataforma de datos del cliente, que le permiten recopilar y agregar datos de diferentes fuentes en cualquier almacenamiento (por ejemplo, Segmento).

Pero encontramos un problema que aún no se ha resuelto. Así nacido EventoNativo - servicio de análisis de código abierto. Sobre por qué fuimos a desarrollar nuestro propio servicio, qué nos brindó y qué sucedió al final (con fragmentos de código), lea debajo del corte.

La historia de nuestro código abierto: cómo creamos un servicio de análisis en Go y lo pusimos a disposición del público

¿Por qué deberíamos desarrollar nuestro propio servicio?

Eran los años noventa, sobrevivimos como pudimos. 2019, desarrollamos la primera API de plataforma de datos de clientes kSentido, lo que permitió agregar datos de diferentes fuentes (anuncios de Facebook, Stripe, Salesforce, Google Play, Google Analytics, etc.) para un análisis de datos más conveniente, identificación de dependencias, etc. Hemos observado que muchos usuarios utilizan nuestra plataforma de análisis de datos, concretamente Google Analytics (en adelante, GA). Hablamos con algunos usuarios y descubrimos que necesitan los datos analíticos de sus productos, que reciben mediante GA, pero Datos de muestra de Google y para muchos la interfaz de usuario de GA no es un estándar de conveniencia. Tuvimos suficientes conversaciones con nuestros usuarios y nos dimos cuenta de que muchos también usaban la plataforma Segment (que, por cierto, fue hace apenas unos días). vendido por 3.2 millones de dólares).

Instalaron un píxel de JavaScript de segmento en su recurso web y los datos de comportamiento de sus usuarios se cargaron en una base de datos específica (por ejemplo, Postgres). Pero Segment también tiene su inconveniente: el precio. Por ejemplo, si un recurso web tiene 90,000 MTU (usuarios rastreados mensualmente), entonces deberá pagar ~ $ 1,000 por mes al cajero. También hubo un tercer problema: algunas extensiones del navegador (como AdBlock) bloquearon la recopilación de análisis. Las solicitudes http del navegador se enviaron a los dominios GA y Segment. Basándonos en el deseo de nuestros clientes, hemos creado un servicio de análisis que recopila un conjunto completo de datos (sin muestreo), de forma gratuita y puede funcionar en nuestra propia infraestructura.

Cómo funciona el servicio

El servicio consta de tres partes: un píxel de JavaScript (que luego reescribimos a mecanografiado), una parte del servidor implementada en el lenguaje GO y se planeó usar Redshift y BigQuery como base de datos interna (luego agregaron soporte para Postgres , ClickHouse y Snowflake).

La estructura de eventos GA y Segment decidieron dejarla sin cambios. Todo lo que se necesitaba era duplicar todos los eventos del recurso web donde está instalado el píxel en nuestro backend. Resulta que esto es fácil de hacer. El píxel de Javascript anuló el método de la biblioteca GA original con uno nuevo que duplicó el evento en nuestro 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 el píxel Segment todo es más sencillo, tiene métodos middleware y nosotros utilizamos uno de ellos.


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

Además de copiar eventos, agregamos la capacidad de enviar json arbitrario:


//Отправка событий с произвольным 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 continuación, hablemos del lado del servidor. El backend debe aceptar solicitudes http, completarlas con información adicional, por ejemplo, datos geográficos (gracias maxmind para ello) y escribir en la base de datos. Queríamos que el servicio fuera lo más conveniente posible para que pudiera usarse con una configuración mínima. Hemos implementado la funcionalidad de determinar el esquema de datos en función de la estructura del evento entrante json. Los tipos de datos se definen por valores. Los objetos anidados se descomponen y reducen a una estructura 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"
}

Sin embargo, actualmente las matrices simplemente se convierten en cadenas. No todas las bases de datos relacionales admiten campos repetidos. También es posible cambiar los nombres de los campos o eliminarlos utilizando reglas de asignación opcionales. Le permiten cambiar el esquema de datos, si es necesario, o convertir un tipo de datos a otro. Por ejemplo, si el campo json contiene una cadena con una marca de tiempo (campo_3_sub_campo_1_sub_sub_campo_1 del ejemplo anterior), luego, para crear un campo en la base de datos con el tipo de marca de tiempo, debe escribir una regla de mapeo en la configuración. En otras palabras, el tipo de datos del campo se determina primero por el valor json y luego se aplica la regla de conversión de tipo (si está configurada). Hemos identificado 4 tipos de datos principales: STRING, FLOAT64, INT64 y TIMESTAMP. Las reglas de mapeo y conversión se ven así:

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 el tipo de datos:

  • convertir estructura json a estructura plana
  • determinar el tipo de datos de los campos por valores
  • aplicar reglas de mapeo y conversión de tipos

Luego, desde la estructura 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"
    }
}

Se obtendrá el esquema de datos:

"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

También pensamos que el usuario debería poder configurar particiones o dividir datos en la base de datos de acuerdo con otros criterios e implementamos la capacidad de configurar el nombre de la tabla como una constante o expresión en configuración. En el siguiente ejemplo, el evento se guardará en una tabla con un nombre calculado en función de los valores de los campos product_type y _timestamp (por ejemplo suministros_2020_10):

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

Sin embargo, la estructura de los eventos entrantes puede cambiar en tiempo de ejecución. Hemos implementado un algoritmo para verificar la diferencia entre la estructura de una tabla existente y la estructura de un evento entrante. Si se encuentra una diferencia, la tabla se actualizará con nuevos campos. Para hacer esto, use la consulta SQL del parche:

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

Arquitectura

La historia de nuestro código abierto: cómo creamos un servicio de análisis en Go y lo pusimos a disposición del público

¿Por qué necesita escribir eventos en el sistema de archivos y no simplemente escribirlos directamente en la base de datos? Las bases de datos no siempre muestran un alto rendimiento con una gran cantidad de inserciones (recomendaciones de postgres). Para hacer esto, Logger escribe los eventos entrantes en un archivo y ya en una rutina (hilo) separada, el lector de archivos lee el archivo, luego se lleva a cabo la transformación y definición del esquema de datos. Después de que el administrador de la tabla se asegure de que el esquema de la tabla esté actualizado, los datos se escribirán en la base de datos en un lote. Posteriormente, agregamos la capacidad de escribir datos directamente en la base de datos, pero usamos este modo para eventos que no son muchos, por ejemplo, conversiones.

Código abierto y planes futuros

En algún momento, el servicio se convirtió en un producto completo y decidimos ponerlo en código abierto. Por el momento se han implementado integraciones con Postgres, ClickHouse, BigQuery, Redshift, S3, Snowflake. Todas las integraciones admiten modos de carga de datos por lotes y de transmisión. Se agregó soporte para solicitudes a través de API.

El esquema de integración actual se ve así:

La historia de nuestro código abierto: cómo creamos un servicio de análisis en Go y lo pusimos a disposición del público

Aunque el servicio se puede utilizar de forma independiente (por ejemplo, usando Docker), también tenemos versión alojada, donde puede configurar la integración con el almacén de datos, agregar un CNAME a su dominio y ver estadísticas sobre la cantidad de eventos. Nuestros planes inmediatos son agregar la capacidad de agregar no solo estadísticas de un recurso web, sino también datos de fuentes de datos externas y guardarlas en cualquier almacenamiento de su elección.

→ GitHub
→ Документация
→ Flojo

¡Estaremos encantados de que EventNative le ayude a resolver sus problemas!

Solo los usuarios registrados pueden participar en la encuesta. Registrarsepor favor

Qué sistema de recopilación de estadísticas se utiliza en su empresa

  • 48,0%Google Analytics12

  • 4,0%Segmento1

  • 16,0%Otro (escribe en los comentarios) 4

  • 32,0%Implementó su servicio8

25 usuarios votaron. 6 usuarios se abstuvieron.

Fuente: habr.com

Añadir un comentario