Historia naszego open source: jak stworzyliśmy usługę analityczną w Go i udostępniliśmy ją publicznie

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. Google Analytics) do Customer Data Platform, które umożliwiają gromadzenie i agregację danych z różnych źródeł w dowolnej hurtowni (np Segment).

Jednak znaleźliśmy problem, który nie został jeszcze rozwiązany. Tak narodził się WydarzenieNative — usługa analityczna typu open source. Przeczytaj o tym, dlaczego zdecydowaliśmy się na rozwój własnej usługi, co nam dała i jaki był efekt końcowy (z fragmentami kodu).

Historia naszego open source: jak stworzyliśmy usługę analityczną w Go i udostępniliśmy ją publicznie

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 Dane próbki Google i dla wielu interfejs użytkownika GA nie jest standardem wygody. Odbyliśmy wystarczająco dużo rozmów z naszymi użytkownikami i zdaliśmy sobie sprawę, że wielu korzystało również z platformy Segment (która, nawiasem mówiąc, powstała niedawno sprzedany za 3.2 miliarda dolarów).

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 maxmind do tego) i zapisz go w bazie danych. Chcieliśmy, aby usługa była jak najbardziej wygodna, aby można było z niej korzystać przy minimalnej konfiguracji. Zaimplementowaliśmy funkcjonalność ustalania schematu danych na podstawie struktury przychodzącego zdarzenia json. Typy danych są definiowane przez wartości. Zagnieżdżone obiekty są rozkładane i redukowane do płaskiej struktury:

//входящий 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 wyrażenie w konfiguracji. W poniższym przykładzie zdarzenie zostanie zapisane do tabeli o nazwie wyliczonej na podstawie wartości pól Product_type i _timestamp (np. materiały_2020_10):

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

Historia naszego open source: jak stworzyliśmy usługę analityczną w Go i udostępniliśmy ją publicznie

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 (Zalecenia Postgresa). W tym celu Logger zapisuje przychodzące zdarzenia do pliku i w osobnej goroutine (wątku) Czytnik plików odczytuje plik, następnie dane są konwertowane i ustalane. Po upewnieniu się przez Menedżera tabel, że schemat tabeli jest aktualny, dane zostaną zapisane do bazy danych w jednej partii. Następnie dodaliśmy możliwość zapisu danych bezpośrednio do bazy, jednak tego trybu używamy w przypadku zdarzeń, które nie są liczne – np. konwersji.

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:

Historia naszego open source: jak stworzyliśmy usługę analityczną w Go i udostępniliśmy ją publicznie

Choć z usługi można korzystać samodzielnie (np. za pomocą Dockera), my też tak mamy wersja hostowana, w którym możesz skonfigurować integrację z hurtownią danych, dodać CNAME do swojej domeny i wyświetlić statystyki dotyczące liczby zdarzeń. Nasze najbliższe plany to dodanie możliwości agregowania nie tylko statystyk z zasobu internetowego, ale także danych z zewnętrznych źródeł danych i zapisywanie ich w dowolnym wybranym przez Ciebie magazynie!

→ GitHub
→ Dokumentacja
→ Slack

Będzie nam miło, jeśli EventNative pomoże rozwiązać Twoje problemy!

W ankiecie mogą brać udział tylko zarejestrowani użytkownicy. Zaloguj się, Proszę.

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

Dodaj komentarz