Testy A/B z CloudWatch Evidently

Testy A/B z CloudWatch Evidently

W dzisiejszym wpisie poruszę temat testów A/B nazywanych też bucket testing lub split-run testing. Opiszę krótko na czym polegają testy A/B i dlaczego warto ich używać oraz za pomocą CloudWatch Evidently pokażę jak takie testy przeprowadzać.

Testy A/B co to takiego?

Testy A/B pozwalają nam na porównanie zachowania użytkowników, którzy mają styczność z naszą aplikacją np. stroną internetową lub programem. Zasada działania jest dość prosta. Jednej grupie użytkowników udostępniamy naszą wersję referencyjną aplikacji „A”, a drugiej grupie użytkowników udostępniamy wersję, którą chcemy sprawdzić czyli wersję „B”. Zazwyczaj takie testy obejmują więcej niż dwie wersje oprogramowania. Amazon chwali się, że na jego głównych stronach przez cały czas działają dziesiątki testów A/B. Warto zaznaczyć, że użytkownicy nie są świadomi istnienia innych wersji naszej strony czy produktu. Ważne jest, że w przypadku testów A/B do wersji aplikacji, którą sprawdzamy będziemy kierować raczej małą część całego ruchu np. do 5%.

W procesie przydzielania wersji do konkretnych użytkowników bierze udział technika nazywana Feature Flag / Feature Tonggles. W dalszej części artykułu zamiast pełnej nazwy będę używał skrótu flaga. Wspomniana wcześniej technika polega na tym, że w momencie w którym, użytkownik korzysta z naszej aplikacji otrzymuje on flagę, na podstawie której przydzielana jest mu wybrana wersja strony. Flagi otrzymuje się na podstawie wytycznych, które określają przypisanie flag do danych grup użytkowników. Mogą to być np. lokalizacje użytkowników, numery IP, urządzenia z jakich korzystają, nowy klient kontra stały klient lub inne dane, które pozwolą nam na filtrację użytkowników.

Cykl testów A/B

By lepiej zrozumieć po co nam tego typu testy i jakie problemy pomagają rozwiązać warto przyjrzeć się metodologii działania testów A/B. Najłatwiej będzie spojrzeć na poszczególne fazy, które występują w ich procesie.

Testy A/B mają za zadanie odpowiedzieć na pytanie czy nowa funkcjonalność, którą wprowadzamy spełni pewne nasze oczekiwania. W pierwszej fazie zakładamy hipotezę, na postawie której chcemy ulepszyć naszą aplikację. Następnie na podstawie hipotezy tworzymy wariację naszej aplikacji i ukrywamy ją za flagą. Odpalamy eksperyment, gromadzimy dane i po zakończeniu eksperymentu sprawdzamy otrzymane metryki. Na podstawie porównania danych jesteśmy w stanie powiedzieć czy nasza hipoteza była słuszna czy też nie. Mamy informację czy eksperyment jest udany i czy możemy powiększyć grupę eksperymentalną, a może od razu wdrożyć nową wersję dla całej produkcji. Możemy też porzucić wersję jeśli eksperyment okazał się porażką.

Cała trudność tkwi w metrykach

Przejdę teraz do najbardziej wymagającej części związanej z testami A/B, a są to interpretacje otrzymanych danych. Wyobraźmy sobie taki przypadek: chcę zwiększyć średnią długość sesji użytkowania na blogu. Hipoteza jest następująca: poprawa layoutu strony sprawi, że blog będzie bardziej czytelny co przełoży się na pozytywny user experience i wydłuży czas sesji użytkownika. Brzmi dość racjonalnie. Drugi przykład: mamy serwis podobny do Wikipedii. Chcemy zwiększyć tak jak ostatnio średni czas sesji użytkownika. Zakładamy tak jak wcześniej, że zmiana interfejsu graficznego strony sprawi iż użytkownik spędzi na niej więcej czasu. Tu pojawia się problem. Przyjmujemy, że zwiększy się czas sesji ponieważ user experience będzie lepsze po zmianie layoutu strony. Co się jednak wydarzy w przypadku gdy przez naszą zmianę pogorszyliśmy UI. Doprowadzi to do pogorszenia intuicyjności naszej strony i klient spędza średnio więcej czasu na niej bo każda jego akcja jest dużo wolniejsza. Dlatego tak ważne jest dobranie prawidłowych metryk do danej sytuacji biznesowej i poprawna ich interpretacja.

Wybranie prawidłowych celów pomiarowych nie jest rzeczą prostą. Będą nam pomocne odpowiedzi na poniższe pytania:

  • Jaki biznes prowadzimy?
  • Jakich użytkowników obsługuje strona?
  • Co chcemy osiągnąć zmianą?
  • Jakie mamy priorytety?

Inne przykłady hipotez i ich efektów:

  • Zwiększenie ilości prezentowanych produktów na stronie – zwiększy ilość wyświetleń
  • Ulepszenie intuicyjności strony sprzedażowej – zmniejszy czas potrzeby na zamówienie produktu
  • Poprawa algorytmu rekomendacji sklepu internetowego – polepszy stosunek odwiedzin proponowanych produktów
  • Dodanie miniaturek zdjęć przy nazwach produktów – zwiększy liczbę kliknięć produktu

Przykład w CloudWatch Evidently

Do przeprowadzenia testów A/B użyję serwisu AWS, który miał premierę w listopadzie 2021 roku, a jest to CloudWatch Evidently. Posłużę się w tym celu przygotowanym przez AWS przykładem, który możecie znaleźć na stronie Tutorial: A/B testing with the Evidently sample application. W proponowanym przykładzie będziemy mieli do czynienia z internetowym sklepem z warzywami. Porównamy ze sobą dwie wersje aplikacji. Wersja pierwsza (referencyjna) różni się od wersji drugiej tym, że wersja druga będzie dodatkowo wyświetlać rabat na każdy produkt w wysokości 20%. Następnie będziemy porównywali ze sobą czas ładowania strony.

Przykład ten nie jest porywający. Bez przeprowadzania analizy możemy się domyślić, że czas wyświetlania strony, która będzie pokazywała dokładnie to samo plus rabat w wysokości 20% powinna załadować się odrobinę później. Na potrzeby pokazania mechaniki działania testów jest to wystarczające. Testy A/B mają za zadanie sprawdzenie zachowania użytkowników dlatego trudno jest pokazać wiarygodny przykład przy pomocy symulowanego środowiska.

Postępując zgodnie z poradnikiem ściągamy kod aplikacji, następnie ustawiamy w config.js region i endpoint dla naszego serwisu evidently, który chcemy użyć. Tworzymy nowego użytkownika z uprawnieniami AmazonCloudWatchEvidentlyFullAccess i dodajemy jego credentiale do wyżej wspomnianego pliku. Następnie poradnik wyjaśnia w punktach trzy i cztery co należy dodać do kodu i w jaki sposób evidently wysyła flagi do aplikacji. Kod, który ściągnęliśmy w punkcie pierwszym jest już o to uzupełniony i jeśli nie zmieniamy nazwy projektu to nie potrzebujemy w nim nic modyfikować. Nie chcę wchodzić w techniczne szczegóły, ale muszę zwrócić uwagę na dane, które są wysyłane z aplikacji do CloudWatch Evidently. Jest to payload czyli zwykły JSON, który jest wysyłany do evidently jak w poniższym kodzie.

[ {"timestamp": 1637368646.468, "type": "aws.evidently.custom", "data": "{\"details\":{\"pageLoadTime\":2058.002058},\"userDetails\":{\"userId\":\"1637368644430\",\"sessionId\":\"1637368644430\"}}" } ]

Widzimy w nim, że poza timestamp w sekcji data przesyłana jest wartość metryki, którą mierzymy oraz userDetails czyli parametr, po którym evidently identyfikuje użytkowników.

Warto dodać, że w trakcie prawdziwych testów A/B warianty są przypisane do użytkowników w podobny sposób jak działają sticky session, więc użytkownik korzysta cały czas tylko z jednej wersji aplikacji. Jest to jak najbardziej prawidłowe zachowanie ponieważ cały czas zmieniające się np. UI doprowadziłoby użytkownika delikatnie mówiąc do negatywnych odczuć. W naszym przykładzie ten mechanizm wyłączamy, inaczej musielibyśmy po każdej wysłanej metryce ustawiać opcje Overrides w naszym feature na przeciwny wariant co było by dość karkołomne w trakcie trwania eksperymentu.

Przechodzimy do kroku piątego, tworzymy projekt. Tak naprawdę sprowadza się to tylko do wybrania nazwy projektu, która musi zgadzać się z tą zapisaną w naszej aplikacji i wskazania gdzie chcemy przechowywać dane. Do wyboru mamy S3, CloudWatch Logs lub nie zapisywanie danych czyli w ostatniej opcji dane zostaną użyte tylko do wygenerowania rezultatu eksperymentu i zaprezentowania ich na wykresie.

Tworzymy teraz feature, określamy jakiego typu ma być np. boolean, long, double lub string. Dodajemy ile wariacji ma mieć dany feature, w naszym wypadku dwie. Ustawiamy nazwę featura na showDiscount – domyślna wartość false, a dla drugiej wariacji true. W tym miejscu na moment się zatrzymam gdyż muszę wspomnieć o bardzo istotnej rzeczy. Jesteśmy w stanie rozrzucać ruch na różne warianty, które będziemy testować np. 5% nowa wersja i 95% stara wersja. Przypomina to canary release, ale dzięki opcji Overrides jesteśmy w stanie wymusić, by dana grupa użytkowników dostała dokładnie taką wersję aplikacji jaką chcemy. W naszym przykładzie jednak tą opcję pomijamy.

Po przygotowaniu feature możemy stworzyć eksperyment. Dodajemy nazwę eksperymentu, wybieramy feature, który chcemy sprawdzać czyli showDiscount i w Audience ustawiamy by ruch był podzielony 50% na 50%.

Kolejna ważna sekcja Metric. W tej sekcji wybieramy źródło danych jako Custom metrics, nazwę ustawiamy na pageLoadTime i jako Goal zaznaczamy Decrease ponieważ chcemy się dowiedzieć, w którym z tych dwóch wariantów czas ładowania strony będzie krótszy. Poniżej mamy Metric rule, w której ustawiamy z jakich parametrów naszego payloada, który przychodzi do evidently będziemy pobierali entityIdKey czyli użytkownika oraz metryki. Opcjonalnie możemy ustawić też jednostkę dla naszej metryki w tym wypadku milisekundę.

I to wszystko, eksperyment jest gotowy do użycia.

Zaczynamy eksperyment

Zaznaczamy nasz eksperyment i z panelu Actions wybieramy „Start experminet”. Ustawiamy czas trwania eksperymentu na trzy godziny. Eksperyment rozpoczyna się natychmiast więc możemy odpalić naszą aplikację i zobaczyć czy flagi wysyłane są prawidłowo.

Widok naszego internetowego warzywnika w wersji bez prezentowanej zniżki wygląda następująco.

Flagi są wysyłane losowo, a ruch między wersjami jest dzielony równo więc po kilku refreschach powinniśmy zobaczyć stronę z prezentowanymi zniżkami.

Teraz musimy dostarczyć evidently odpowiednią ilość danych więc zautomatyzujmy odświeżanie strony. W moim przypadku najprostszą i najbardziej skuteczną metodą było zainstalowanie wtyczki do przeglądarki chrome o nazwie „Easy Auto Refresh” i ustawienie jej na odświeżanie co sekundę. Próbowałem też z innymi czasami odświeżania jak 0.1s, 0.25s, 0.5s, za pomocą komendy, którą możecie znaleźć poniżej. Wystarczy wrzucić ją do konsoli przeglądarki i wcisnąć enter.

document.getElementsByTagName("body")[0].innerHTML= `<iframe id="testFrame" src=""+window.location.toString()+"" style="position: absolute; top:0; left:0; right:0; bottom:0; width:100%; height:100%;"> </iframe>`; setInterval(()=>{document.getElementById("testFrame").src=document.getElementById("testFrame").src},10000);

Ale po pierwsze, odświeżanie strony częściej niż co sekundę miało negatywny wpływ na metryki wysyłane do evidently, a tego nie chcemy. Po drugie skrypt nieraz się zacinał. W przypadku wtyczki nie było z tym problemów.

Omówmy teraz panel naszego eksperymentu i podgląd na żywo spływających danych.

Do wyboru mamy kilka dostępnych wykresów. Domyślny jaki zobaczmy to „Events count over time” pokazuje ilość otrzymanych zdarzeń dla każdego wariantu w czasie. Poniżej wykresu mamy bardzo ciekawą tabelę. Opisując kolumny po kolei, są to wariacje fechera wraz z ilością kierowanego ruchu, ilość zdarzeń od rozpoczęcia eksperymentu, sumę wartość metryk dla danego wariantu, następnie średnią z wszystkich zdarzeń.

Kolumna Improvment (95% CI) to przedział ufności. Na zdjęciu w nawiasach kwadratowych widzimy liczby od -30% do +65,44% oraz wartość pomiędzy czyli +17,72%. Przedział ufności liczony jest następująco. 95% metryk najbardziej skrajnych jest odrzucanych, a 5% wyników najbardziej zbliżonych jest branych pod uwagę jako te nie obciążone zmiennymi losowymi, na podstawie których obliczany jest dolny i górny przedział ufności. W tym momencie 5% metryk otrzymanych dla wariantu z włączoną flagą ma rozstrzał bardzo duży. Dolna wartość przedziału -30% oznacza, że w tych 5% przypadków, które są brane pod uwagę występują przypadki, w których strona z zniżką ładuje się 30% szybciej niż strona bez zniżki oraz występują też metryki, w których strona ze zniżkami ładuje się aż 65,44% dłużej niż referencyjna. Naturalnie ten przedział na początku eksperymentu jest bardzo duży i szybko zacznie się zmniejszać dążąc do wartości pośredniej czyli +17,72%.

Następnie mamy „Statistical significance”, który mówi nam o tym jak bardzo pewne jest, że nasza wariacja ma rzeczywisty wpływ na daną metrykę, obecnie jest 0% ponieważ nasz przedział ufności jest cały czas między wartościami dodatnimi i ujemnymi, więc nie jesteśmy w stanie powiedzieć czy nasza wariacja sprawi, że metryka będzie wyższa czy niższa od referencyjnej. Z tego samego powodu rezultat eksperymentu jest zaznaczony jako niejednoznaczny „Inconclusive”.

Uprzedzam, że następne zdjęcia pochodzą z kolejnego mojego podejścia do tego eksperymentu który wykonałem innego dnia, ale ustawienia i sposób przeprowadzenia testu był dokładnie taki sam. Po wysłaniu prawie 11 000 zdarzeń do ewidentny i zakończeniu eksperymentu powinniśmy otrzymać następujący wynik.

Widzimy, że rezultat eksperymentu dla naszej włączonej flagi jest gorszy od strony referencyjnej. Przypominam, że gdy ustawialiśmy eksperyment naszym celem było obniżenie czasu ładowania strony, a to się nie udało, dlatego dla tej wariacji naszej aplikacji z aktywną 20% zniżką otrzymaliśmy rezultat „Worse„. W tym wypadku wynik testu był łatwy do przewidzenia ponieważ w czasie trwania eksperymentu nie działały na niego prawie rządne czynniki losowe. Jedne co należało zrobić to dostarczyć odpowiednią ilość danych tak by dolny przedział ufności znalazł się powyżej wartości 0%.

Mimo to, że eksperyment wydawał się banalny i tak udało mi się go podczas jednej z prób „zepsuć”. Poniżej wrzucam wykres Improvement ( 95% CI) dla powyższego eksperymentu.

Widzimy na nim, że na początku rozrzut przedziału ufności jest dość szeroki, ale po około 15 minutach na tyle się zmniejszył, że dolna wartość znalazła się powyżej 0% i utrzymywała się do końca eksperymentu między +3 do +5%.

W jednym z moich poprzednich podejść, ustawiłem czas trwania eksperymentu tylko na godzinę, co sprawiło, że ilość dostarczonych danych była dość mała. Niemniej udało mi się uzyskać mniej więcej po 15 minutach wystarczającą ilość danych by przedział ufności znalazł się po dodatniej stronie. Niestety przy ostatnim kwadransie eksperymentu zacząłem mocniej korzystać z tej samej przeglądarki, w której odświeżałem stronę z internetowym warzywniakiem co spowodowało anomalie. Czasy ładowania stron z włączoną flagą i bez niej zaczęły się do siebie zbliżać, a czasami nawet dane się odwracały. Dodatkowo przez małą liczbę zdarzeń niewiele trzeba było dostarczyć takich metryk z anomalią by dolną wartość przedziału ufności zrzucić poniżej poziomu 0% co spowodowało niejednoznaczny wynik eksperymentu. Podkreśla to znaczenie dobrania odpowiedniego czasu trwania eksperymentu i unikania testowania w warunkach odbiegających od normy.

Dodatkowo w warunkach produkcyjnych testy A/B powinny trwać przynajmniej cały tydzień lub inną jednostkę charakterystyczną dla danego cyklu produktu. Dzięki temu możliwe będzie dobranie prawidłowej próby co pozwoli na zaobserwowanie zachowania klientów w czasie trwania testu oraz zwiększy ich rzetelność.

Podsumowując

  • Testy A/B to efektywny i skuteczny sposób oceny reakcji odbiorców
  • Stosując ten rodzaj testów, w żaden sposób nie ingerujesz w zachowanie klimatów
  • Zaleca się regularne, powtarzalne stosowanie testów A/B
  • Testy A/B pozwalają na sprawdzenie wielu wariantów naraz
  • Długość testów i wielkość próby powinna być zawsze dostosowana do danego problemu, hipotezy, wielkości ruchu oraz okresu (nie należy ich wykonywać w okresie dużych anomalii)

Materiały dodatkowe

Poniżej dodaję kilka ciekawych linków, polecam gorąco zwłaszcza prezentację Pana Sebastiana Gębskiego z pierwszego linka, który w dużych szczegółach opowiada o CloudWach Evidentli i ma też bardziej zawansowany przykład.

Comments are closed.