Obecnie prawie każda firma na świecie gromadzi statystyki dotyczące działań użytkowników w zasobie internetowym. Motywacja jest jasna – firmy chcą wiedzieć, w jaki sposób używany jest ich produkt/strona internetowa i lepiej rozumieć swoich użytkowników. Oczywiście na rynku istnieje duża ilość narzędzi pozwalających rozwiązać ten problem – począwszy od systemów analitycznych dostarczających dane w postaci dashboardów i wykresów (np.
Jednak znaleźliśmy problem, który nie został jeszcze rozwiązany. Tak narodził się
Dlaczego powinniśmy rozwijać własny serwis?
Były lata dziewięćdziesiąte, przetrwaliśmy, jak mogliśmy. W 2019 roku opracowaliśmy Platformę Danych Klienta API First kSense, co umożliwiło agregację danych z różnych źródeł (reklamy na Facebooku, Stripe, Salesforce, Google Play, Google Analytics itp.) w celu wygodniejszej analizy danych, identyfikacji zależności itp. Zauważyliśmy, że wielu użytkowników wykorzystuje naszą platformę do analizy danych, a konkretnie Google Analytics (dalej GA). Rozmawialiśmy z niektórymi użytkownikami i dowiedzieliśmy się, że potrzebują danych analitycznych dla swojego produktu, które otrzymują za pomocą GA, ale
Zainstalowali piksel javascript Segment w swoim zasobie internetowym, a dane o zachowaniach ich użytkowników zostały załadowane do określonej bazy danych (na przykład Postgres). Ale Segment ma też swoją wadę – cenę. Na przykład, jeśli zasób sieciowy ma 90,000 1,000 MTU (miesięcznych śledzonych użytkowników), wówczas musisz płacić kasjerowi ~XNUMX $ miesięcznie. Był też trzeci problem – niektóre rozszerzenia przeglądarki (takie jak AdBlock) blokowały zbieranie statystyk, ponieważ... żądania http z przeglądarki zostały wysłane do domen GA i Segment. Na życzenie naszych klientów stworzyliśmy usługę analityczną, która zbiera kompletny zestaw danych (bez pobierania próbek), jest bezpłatna i może pracować na własnej infrastrukturze.
Jak działa usługa
Usługa składa się z trzech części: piksela javascript (który później przepisaliśmy na maszynie), części serwerowej zaimplementowanej w języku GO, a w planach było wykorzystanie Redshift i BigQuery jako własnej bazy danych (później dodano obsługę Postgres, ClickHouse i Snowflake).
Zdecydowano o pozostawieniu struktury zdarzeń GA i Segmentu bez zmian. Wystarczyło zduplikować wszystkie zdarzenia z zasobu sieciowego, w którym piksel jest zainstalowany, do naszego backendu. Jak się okazuje, nie jest to trudne. Piksel JavaScript nadpisał oryginalną metodę biblioteki GA nową, co zduplikowało zdarzenie w naszym systemie.
//'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);
});
});
}
Z pikselem segmentowym wszystko jest prostsze; ma metody oprogramowania pośredniego, z których jedną wykorzystaliśmy.
//'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.');
}
Oprócz kopiowania zdarzeń dodaliśmy możliwość wysyłania dowolnego jsona:
//Отправка событий с произвольным 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'
});
Następnie porozmawiajmy o części serwerowej. Backend powinien akceptować żądania http, wypełniać je dodatkowymi informacjami, na przykład danymi geograficznymi (dzięki
//входящий 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"
}
Jednak tablice są obecnie po prostu konwertowane na ciągi znaków, ponieważ Nie wszystkie relacyjne bazy danych obsługują powtarzające się pola. Możliwa jest także zmiana nazw pól lub ich usunięcie za pomocą opcjonalnych reguł mapowania. Umożliwiają one w razie potrzeby zmianę schematu danych lub konwersję jednego typu danych na inny. Na przykład, jeśli pole json zawiera ciąg znaków ze znacznikiem czasu (pole_3_sub_pole_1_sub_sub_pole_1 z powyższego przykładu), to aby utworzyć w bazie danych pole typu timestamp należy w konfiguracji napisać regułę mapowania. Innymi słowy, typ danych pola jest najpierw określany na podstawie wartości json, a następnie stosowana jest reguła rzutowania typu (jeśli jest skonfigurowana). Zidentyfikowaliśmy 4 główne typy danych: STRING, FLOAT64, INT64 i TIMESTAMP. Reguły mapowania i rzutowania typów wyglądają następująco:
rules:
- "/field_1/subfield_1 -> " #правило удаления поля
- "/field_2/subfield_1 -> /field_10/subfield_1" #правило переноса поля
- "/field_3/subfield_1/subsubfield_1 -> (timestamp) /field_20" #правило переноса поля и приведения типа
Algorytm ustalania typu danych:
- przekonwertuj strukturę json na płaską strukturę
- określanie typu danych pól według wartości
- stosowanie reguł mapowania i rzutowania typów
Następnie z przychodzącej struktury json:
{
"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"
}
}
uzyskany zostanie schemat danych:
"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
Pomyśleliśmy również, że użytkownik powinien mieć możliwość skonfigurowania partycjonowania lub dzielenia danych w bazie danych według innych kryteriów i zaimplementowaliśmy możliwość ustawienia nazwy tabeli ze stałą lub
tableName: '{{.product_type}}_{{._timestamp.Format "2006_01"}}'
Jednakże struktura przychodzących zdarzeń może ulec zmianie w czasie wykonywania. Zaimplementowaliśmy algorytm sprawdzający różnicę pomiędzy strukturą istniejącej tabeli, a strukturą przychodzącego zdarzenia. W przypadku znalezienia różnicy tabela zostanie zaktualizowana o nowe pola. Aby to zrobić, użyj zapytania SQL łatki:
#Пример для Postgres
ALTER TABLE "schema"."table" ADD COLUMN new_column character varying
Architektura
Dlaczego musisz zapisywać zdarzenia w systemie plików, a nie tylko zapisywać je bezpośrednio w bazie danych? Bazy danych nie zawsze radzą sobie dobrze z dużą liczbą wstawek (
Open Source i plany na przyszłość
W pewnym momencie usługa zaczęła wyglądać jak pełnoprawny produkt i postanowiliśmy udostępnić ją na platformie Open Source. Aktualnie zostały wdrożone integracje z Postgres, ClickHouse, BigQuery, Redshift, S3, Snowflake. Wszystkie integracje obsługują zarówno tryb wsadowy, jak i strumieniowy ładowania danych. Dodano obsługę żądań poprzez API.
Obecny schemat integracji wygląda następująco:
Choć z usługi można korzystać samodzielnie (np. za pomocą Dockera), my też tak mamy
→
→
→
Będzie nam miło, jeśli EventNative pomoże rozwiązać Twoje problemy!
W ankiecie mogą brać udział tylko zarejestrowani użytkownicy.
Jaki system gromadzenia statystyk stosowany jest w Twojej firmie?
-
48,0%Google Analytics12
-
4,0%Segment1
-
16,0%Kolejne (napisz w komentarzu) 4
-
32,0%Wdrożyłeś swoją usługę 8
Głosowało 25 użytkowników. 6 użytkowników wstrzymało się od głosu.
Źródło: www.habr.com