Moduł segmentacji

Moduł analityki funkcjonuje po stronie klienta dzięki wykorzystaniu języka JavaScript, natomiast moduł segmentacji jest zaimplementowany po stronie serwera. Moduły segmentacji mogą być napisane w dowolnym języku programowania, jednak tutaj wykorzystam język Ruby oraz framework Ruby on Rails. Istnieje wygodna biblioteka (gem) implementująca Ahoy.js w języku Ruby - ahoy_matey, która zapewnia podstawy do zbudowania modułu segmentacji. Moduł segmentacji wykonuje dwa główne zadania - zapisuje zarejestrowaną aktywność w bazie danych oraz, na podstawie tej bazy danych, tworzy zdefiniowane wcześniej segmenty użytkowników.

Zapisywanie w bazie danych

Załóżmy, że zupełnie nowy użytkownik odwiedza stronę główną witryny. Po wykonaniu pracy przez moduł analityki (funkcja trackAll()), serwer otrzymuje od niego dwa zapytania POST:

  • pod adres /ahoy/visits, zawierające identyfikatory wizyty i użytkownika wraz z dodatkowymi informacjami o wizycie,
  • pod adres /ahoy/events, zarejestrowaną aktywność (w przypadku odwiedzenia strony zostaje wywołana funkcja trackView(), która wysyła event z kategorii $view):
{
  "events" => [
    {
      "id" => "e85b59eb-8266-4fad-bbed-9f10eb03b4ab",
      "name" => "$view",
      "properties" => {
        "url" => "http://localhost:3000/",
        "title" => "Automation",
        "page" => "/"
      },
      "time" => 1502043123.768
    }
  ],
  "visit_token"=>"4ba9c74b-c0d3-4e2b-a4ff-8a86b07e7a80",
  "visitor_token"=>"e02c51a0-5dfb-4483-a162-f8f6aa704243", 
  "event" => {}
}

Następnie wartości wysłane przez bibliotekę Ahoy.js są zapisywane w bazie danych. Dane na temat wizyty są zapisywane w tabeli “visits” w kolumnach:

  • visit_token
  • visitor_token
  • started_at
  • ip
  • user_agent
  • landing_page
  • screen_height
  • screen_width
  • browser
  • os
  • device_type
  • country
  • latitude
  • longitude
  • user_id

Dane każdego eventu są zapisywane w tabeli “events” w kolumnach:

  • visit_id
  • user_id
  • name
  • properties
  • time

Konstrukcja tabeli events może budzić pewne wątpliwości - nie zawiera osobnej kolumny dla każdego szczegółu danego eventu, np. URL odwiedzanej podstrony lub jej tytułu. Zamiast tego, kolumna properties zawiera wszystkie zapisane informacje jako ciąg znaków w formacie JSON, np.

{ 
  "url":"http://example.com:/",
  "title":"Example site",
  "page":"/"
}

Taka struktura podyktowana jest wymogiem elastyczności systemu. Dla skomplikowanych implementacji i bardziej szczegółowej analityki ilość mierzonych wartości może osiągać bardzo duże liczby, co skutkowałoby tysiącami kolumn (często pustych) w jednej tabeli. Taka ilość kolumn negatywnie wpływa na szybkość działania bazy danych, a zarazem całego modułu segmentacji. Dzisiejszym nowoczesnym bazom danych obsługa formatu JSON nie stanowi problemu i umożliwia szybkie działania na tak dużej ilości danych oraz wygodną ich obróbkę dla programistów. W bazie danych zapisywany jest również user_id. Jest to unikalny numer identyfikacyjny aktualnie zalogowanego użytkownika. Obecność user_id pozwala na przypisanie wszystkich aktywności do danego użytkownika, a w konsekwencji możliwość późniejszego przypisania danego użytkownika do segmentu.

Definiowanie segmentów

Segmenty to grupy użytkowników, którzy spełniają wcześniej określone kryteria. Segmenty działają automatycznie, to znaczy użytkownicy zostają do nich przypisani kiedy tylko zaczną spełniać warunki lub wypisani, kiedy przestaną je spełniać. Przykładem kryterium dla segmentu może być “odwiedził podstronę /kontakt”, segment będzie wówczas zawierał wszystkich użytkowników, którzy otworzyli w swojej przeglądarce podstronę o adresie /kontakt.

Do zademonstrowania segmentów stworzyłem małą aplikację marketing-automation-rails, która posiada część opisanych wcześniej funkcjonalności. Aplikacja wyjaśni, w jaki sposób definiuje się i tworzy segmenty.

Nie sugeruj się jakością kodu w marketing-automation-rails. Aplikacja została napisana jako proof of concept i nie jest w żadnym stopniu gotowa do pracy na produkcji

Do stworzenia segmentu wymagana jest jego nazwa (segment.name) oraz zestaw kryteriów (segment.filters). Kryteria (filtry) to zestaw reguł umieszczonych w dwuwymiarowej tablicy. Każda reguła opisuje właściwości eventu, który zostanie zakwalifikowany jako spełniający daną regułę, a więc również (dzięki user_id) użytkownika, którego aktywność spełnia tą regułę.

Regułą może być “wyświetlenia podstrony /services”. Jej odwzorowanie to Hash zawierający trzy klucze: name, match i properties:

filters = [
  [
    { 
      name: "$view",
      match: "=",
      properties: {
        page: "/services"
      }
    }
  ]
]

name informuje system, które eventy brać pod uwagę przy poszukiwaniu dopasowań.

Przyjmuje wartości z listy:

  • $view,
  • $click,
  • $submit,
  • $change

match opisuje sposób dopasowania wybranej wartości do zarejestrowanych eventów. Może przyjmować wartości z listy poniżej:

  • “=” (dokładne dopasowanie),
  • “~” (zawiera),
  • “^” (rozpoczyna się od),
  • “$” (kończy się na),
  • “>” (jest większe niż - tylko w przypadku liczb),
  • “<” (jest mniejsze niż - tylko w przypadku liczb),
  • “!=” (nie równa się),
  • “!~” (nie zawiera),
  • “!^” (nie rozpoczyna się od),
  • “!$” (nie kończy się na)

properties definiuje jedną property, które system ma poszukiwać wśród eventów. W przykładzie powyżej jest to ścieżka w adresie URL: “/services”.

Cała powyższa reguła zawiera wystarczająco dużo informacji, by móc stworzyć na jej podstawie cały segment tylko tych użytkowników, którzy wyświetlili dokładnie podstronę /services. Dokładne dopasowanie eliminuje wszystkie wyświetlenia z dodatkowymi znakami w ścieżce URL, jak na przykład parametry URL. Użytkownicy odwiedzający podstronę /services?source=banner nie zostaną już zakwalifikowani do segmentu.

Definiowanie segmentu w oparciu o tylko jedną regułę często nie jest wystarczające, dlatego można je ze sobą łączyć. Załóżmy, że chcemy odnaleźć użytkowników, którzy odwiedzili podstronę /services oraz podstronę, której tytuł zawiera słowo “offer” LUB odwiedzili dowolną podstronę z parametrem “source=banner”.

Zdefiniowaliśmy dwie grupy kryteriów, a przynależność do którejkolwiek z nich kwalifikuje użytkownika do naszego nowego segmentu. Dla pierwszej grupy tworzymy dwie reguły:

{
  name: "$view",
  match: "=",
  properties: {
    page: "/services"
  }
},
{ 
  name: "$view",
  match: "~",
  properties: {
    title: "offer"
  }
}

Natomiast przynależność do drugiej grupy definiuje pojedyncza reguła:

{ 
  name: "$view",
  match: "~",
  properties: {
    url: "source=banner"
  }
}

Wszystkie reguły umieszczone w dwuwymiarowej tabeli kryteriów wyglądają jak poniżej:

filters = [
  [
    { 
      name: "$view",
      match: "=",
      properties: {
        page: "/services"
      }
    },
    { 
      name: "$view",
      match: "~",
      properties: {
        title: "offer"
      }
    }
  ],
  [
    { 
      name: "$view",
      match: "~",
      properties: {
        url: "source=banner"
      }
    }
  ]
]

Tabela pierwszego wymiaru wewnątrz zmiennej filters zawiera kolejne tabele drugiego wymiaru. Każda tabela drugiego wymiaru to kryterium dołączenia do segmentu, na które składają się pojedyncze reguły. Aby kryterium zostało spełnione, aktywność użytkownika musi spełniać wszystkie reguły w danym kryterium. Zakwalifikowanie użytkownika do segmentu nie wymaga, by spełnione zostały wszystkie kryteria - wystarczy jedno.

Zrozumienie powyższej konstrukcji ułatwia schemat działania filtrów:

Schemat działania filtrów

Segmentacja odwiedzających

Po zdefiniowaniu reguł i kryteriów dla segmentu, można przeanalizować dotychczasową aktywnośc użytkowników i przypisać ich do danego segmentu. Proces ten nazywa się budowaniem segmentu i składa się z kilkunastu następujących po sobie kroków. Na początku system odczytuje reguły i generuje listy żytkowników spełniających warunki dołączenia do segmentu. W aplikacji marketing-automation-rails jest za to odpowiedzialna klasa BuildSegment.

Zawiera ona szereg funkcji, każda z nich wyspecjalizowana w jednym zadaniu. Wywoływane jedna po drugiej budują listy użytkowników spełniające kolejno warunki pojedynczej reguły, warunki całego filtra oraz dodają lub odejmują użytkowników z listy użytkowników segmentu.

Schemat budowania segmentu

Aby rozpocząć budowanie segmentu, należy wywołać funkcję Segment.build dla wybranego segmentu. Następnie aplikacja odczytuje filtry oraz przypisane do nich reguły, a podklasa BuildSegment::FindUsersToAdd buduje zapytanie do bazy danych na podstawie odczytanej reguły. Budowanie takiego zapytania składa się z kilku kroków, zapisanych w funkcji query:

  1. Najpierw zostaje odczytany sposób dopasowania i następuje sprawdzenie, czy dopasowanie jest negatywne lub pozytywne (zawiera znak “!”),
  2. Przy pomocy wyrażenia regularnego tworzona jest część zapytania SQL do bazy danych. Odczytana cecha wizyty z reguły (np. odwiedzenie strony) jest dopasowana do symboli SQL zgodnie z Hashem @rules_hash,
  3. Wygenerowana część zapytania jest następne przekazywana do klasy Ahoy::Event i funkcji stworzonych specjalnie na potrzeby segmentacji, rozszerzających domyślną funkcjonalność biblioteki Ahoy. Funkcje find_users wykonują zapytania do bazy danych, które zwracają wszystkie eventy spełniające warunki dla danej reguły,
  4. Do osobnej tabeli są zapisywane identyfikatory wszystkich użytkowników przypisanych do eventów znalezionych w poprzednim punkcie. W ten sposób uzyskujemy listę użytkowników spełniających warunki danej reguły.

Po uzyskaniu list użytkowników dla każdej reguły z danego filtra, funkcja users_passing_all_rules tworzy kolejną listę użytkowników - tym razem tylko tych, którzy znajdują się na każdej z list użytkowników dotyczących reguł. W ten sposób otrzymujemy listę użytkowników, których aktywność spełnia warunki całego filtra (a więc również warunki dołączenia do segmentu) - users_passing_filter.

Funkcja users_meeting_requirements_for(segment) buduje ostateczną listę użytkowników spełniających wymagania dołączenia do segmentu, łącząc wszystkie listy użytkowników spełniających wymagania filtrów.

Ostatnim krokiem jest już właściwe dodanie użytkowników do segmentu - a więc zapisanie tej informacji w bazie danych aplikacji. Funkcja add_users zapisuje informację o nowych użytkownikach, którzy powinni należeć do segmentu, a funkcja remove_users usuwa z segmentu obecnych użytkowników, którzy przestali spełniać warunki przynależności do niego.

Czytaj dalej: Moduł akcji