SQL för trattanalys i lagerbaserad produktanalys

27 nov. 2024

Att analysera och förstå funnels är en viktig färdighet för varje produktchef (PM). Det hjälper dem att förstå och optimera produkt- och funktionsanvändning.

Att analysera och förstå funnels är en viktig färdighet för varje produktchef (PM). Det hjälper dem att förstå och optimera produkt- och funktionsanvändning genom att göra det möjligt för dem att spåra och visualisera kundresan från förvärv, via aktivering, retention, remiss och intäkter; eller användarnas delresor genom vilket arbetsflöde som helst i produkten – samtidigt som konverterings- och bortfallsfrekvenser lyfts fram längs vägen. Med hjälp av funnelanalys kan PM definiera valfria framgångskriterier och spåra de steg en användare tar för att nå framgångskriterierna vid konvertering, eller den punkt i produkten där användaren avbröt arbetsflödet, vid ett bortfall.

Denna typ av analys skiljer sig helt från affärsanalys, som aggregerar mått på en uppsättning dimensionella attribut för att förstå affärsresultat. Men baserat på mina mer än 10 års erfarenhet av att bygga högpresterande databaser, tror jag att **moderna datalager är väl rustade för att effektivt utföra sådana (faktiskt all produktanalys) arbetsbelastningar i stor skala**.

I det här inlägget kommer jag att dyka djupt in i de interna delarna av beräkningen som ligger bakom beräkningen av funnels och i detalj hur moderna datalager kan utföra funnelfrågor i stor skala.

Vad är funnel analys?

Wikipedia definierar **funnel analys** som kartläggning och analys av en serie händelser som leder mot ett definierat mål, som en resa från annons till köp i onlineannonsering. Exemplet nedan följer konverteringen av användare till "Betald aktivering" efter att ha tagit utbildningskurser på en online-inlärningsplattform - 18,6 % av användarna tar "Analytics Course" efter "Getting Started"; och av dessa konverterar 31 % av användarna till "Betald aktivering", för en total konvertering på 5,8 %.

En projektledare som vill öka engagemanget kan titta på konverterings- och bortfallsfrekvenser mellan olika steg och besluta att fokusera på att förbättra konverteringen mellan "Komma igång" och "Analytics Course" för att öka "Paid Activation".

Hur man bygger en tratt

Konceptuellt följer en tratt en ordnad händelsesekvens med "event_time" för varje användare för att beräkna det längsta trattstadiet de nått. Det finns flera sätt att uttrycka denna beräkning med SQL. Vi kommer att täcka två sådana mönster och ge insikter i prestandan för båda. Men innan vi hoppar till SQL, låt oss konkret definiera datamodellen som används för denna analys.

Datamodell

Låt oss anta ett schema med en enda "Events"-tabell. Data i "Events" kan föreställas ha följande form:

evenemangsnamn evenemangstid användar-ID OS webbläsare
Komma igång 0 användar-ID0 ios safari
Analys Kurs 1 användar-id0

ios

safari
Analytics-kurs 1 användar-id1 android chrome
Betald aktivering 4 användar-id0 ios safari

Jag har gjort två förenklande antaganden i den här modellen, men som jag förklarar nedan är inget av dessa antaganden ett problemområde för moderna datalager.

  1. Strukturerade data - "Händelser" antas vara en strukturerad tabell även om händelsedata nästan alltid är semistrukturerade. Se min föregående blogg för att förstå hur datalager hanterar semistrukturerade data effektivt.
  2. Inga kopplingar - Första generationens verktyg som Amplitude och Mixpanel modellerar sina scheman som en enda händelseström. Denna modell kan vara mycket restriktiv, särskilt eftersom all verklig analys kräver affärskontext från andra källor. Till skillnad från första generationens produktanalysverktyg som Amplitude och Mixpanel, som erbjuder extremt begränsade frågefunktioner, är moderna datalager byggda för att effektivt sammanfoga flera tabeller.

Funnel Query

  • Underfrågan i steg 1 genererar unika användar-ID:n som stötte på händelsen "Komma igång"
  • Underfrågan i steg 2 ansluter till tabellen "Händelser" med utdata från steg 1 för att generera individuella användar-ID:n som nådde händelsen "Analytics Course" efter händelsen "Komma igång"
  • Underfrågan i steg 3 ansluter till tabellen "Händelser" med utdata från steg 2 för att generera unika användar-ID:n som nådde händelsen "Betald aktivering" efter händelserna "Komma igång" och "Analytics Course", i den ordningen
  • Utdata från alla tre steg aggregeras för att räkna antalet unika användare i varje steg

Moderna datalager har extremt sofistikerade frågeplanerare för att optimera relationsträd. Det finns två optimeringar i planen ovan som är värda att nämna:

  1. Beräkning för varje steg sker en gång - Optimeraren tar bort redundanta beräkningar i planen som delplaner eller fragment för att återanvända resultat i hela frågan. De bästa motorerna i sin klass schemalägger delplaner adaptivt för att använda resultat och statistik för vidare planering.
  2. Aggregering i "Slutlig utdata" skjuts nedanför kopplingen - De bästa optimerarna i sin klass bör kunna dra slutsatsen att aggregering kan skjutas nedanför vänsterkopplingen för många-till-en-koppling, som här, för att eliminera kostnaden för dyra vänsterkopplingar mellan de tre stegen. Att skapa en fråga som aggregerar före koppling är också ett gångbart alternativ för en analysmotor om optimeraren inte kan optimera för detta mönster.

Aggregerings- och kopplingsoperationer är grunden för moderna datalager, vilket gör att sådana frågor kan köras effektivt i stor skala med massiv parallellitet. Det finns dock fortfarande ett par förbättringar som ytterligare kan förbättra frågeprestanda:

  1. Koppling mellan underfrågor - Kopplingar mellan underfrågor är dyrare än bastabellkopplingar eftersom underfrågor inte har förberäknade kopplingsindex. I vårt exempel kopplas "Händelser" och varje stegs utdata vid användarkorn, vilket kan vara kostsamt för användare med hög kardinalitet. Observera att korskopplingen i den slutliga frågan är ganska billig eftersom den kopplar exakt en rad från varje indata.
  2. Flera skanningar - Även efter att redundant beräkning har tagits bort skannas tabellen "Händelser" tre gånger, en för varje underfråga. En snabbare och effektivare algoritm skulle skanna tabellen "Händelser" bara en gång.

Staplade fönsterfunktioner-fråga

Ett alternativt sätt att deklarera denna beräkning är att använda fönsterfunktioner. Fönsterfunktioner möjliggör analys av imperativ händelsesekvensstil via det deklarativa SQL-gränssnittet. I den här metoden skapar vi en stack med fönsterfunktioner (partitionering över), en för varje steg i tratten, följt av ett distinkt antal "user_ids" högst upp i stacken. Vi kallar detta mönster för mönstret Stacked Window Functions. Här är SQL:en för att skapa samma tratt med Stacked Window Functions.

Precis som tidigare gör frågeplaneraren i de flesta moderna datalager två kritiska optimeringar för förbättrad prestanda för ovanstående plan:

  • Enkel sortering över Windows - Alla fönsterfunktioner partitioneras på "user_id" och sorteras på "event_time". I ett sådant fall skapar optimeraren en enda sorteringsoperator som matar data i önskad ordning till alla fönsterfunktioner.
  • Lokal distinkt aggregering - Frågan har en distinkt aggregering på "user_id". Denna aggregering kan utföras inom en partition (lokalt) om data är förpartitionerade på "user_id" eftersom användarbaserad partitionering garanterar att användare inte behöver aggregeras över partitioner.

Båda optimeringarna som listas ovan är standard i de flesta moderna datalager. Denna formulering av trattfrågan har ingen av bristerna hos Join Sequence-stilen för trattfrågan. Det finns dock ett problem som måste åtgärdas. Sorteringsoperatorn ovanpå händelseskanningen kan verka dyr att beräkna. Lyckligtvis behöver denna sortering inte vara global; sortering lokalt inom partitionen är tillräcklig eftersom fönsteroperatorn kräver sorterad inmatning per användare och inte mellan användare.

Det finns potential att optimera denna fråga ytterligare. De flesta datalager erbjuder en knapp för att klustra tabeller med hjälp av en användardefinierad klusternyckel. Klusternyckel är en uppsättning kolumner eller kolumnuttryck som används för att placera rader i en tabell nära varandra för förbättrad prestanda. Att klustra tabellen "Händelser" efter "händelsetid" har flera fördelar:

  • Minska (kan till och med eliminera) kostnaden för sorteringsoperatorn om data nästan (eller helt) sorteras efter händelsetid
  • Förbättra skanningsprestanda på "Händelser" för frågor med tidsintervallfilter på kolumnen "händelsetid"

Slutsats

I den här bloggen presenterade vi två olika formuleringar av SQL för trattanalys och analyserade deras respektive frågeplaner för att förstå deras prestandaprofil i moderna datalager.

Första generationens verktyg, som Mixpanel och Amplitude, har en föråldrad arkitektur som kräver dataflytt, skapar datasilos och dubbelarbete, och är också oöverkomligt dyra i stor skala. Moderna datalager har mognat till en punkt där de inbyggt kan erbjuda en interaktiv upplevelse för produktanalysarbetsbelastningar utan dessa brister och flexibiliteten hos justerbara kostnader för önskvärd prestanda.

Optimizely Warehouse-Native Analytics kan ansluta till alla större molnbaserade datalagerleverantörer, inklusive Snowflake, BigQuery, Redshift och Databricks, för att ge en rik produktanalysupplevelse – utan någon dataförflyttning och till en tredjedel av kostnaden för äldre metoder.