[{"data":1,"prerenderedAt":2444},["ShallowReactive",2],{"article-alternates":3,"article-\u002Fit\u002Fdata\u002Farchitettura-tabella-cohort-scalare-retention-analysis-in-produzione":13},{"i18nKey":4,"paths":5},"data-007-2026-08",{"de":6,"en":7,"es":8,"fr":9,"it":10,"ru":11,"tr":12},"\u002Fde\u002Fdata\u002Fcohort-table-architecture-scaling-retention-analysis-production","\u002Fen\u002Fdata\u002Fcohort-table-architecture-scaling-retention-analysis-production","\u002Fes\u002Fdata\u002Farquitectura-de-tabla-cohort-escalando-analisis-de-retencion-en-produccion","\u002Ffr\u002Fdata\u002Farchitecture-table-cohort-retention-scaling-production","\u002Fit\u002Fdata\u002Farchitettura-tabella-cohort-scalare-retention-analysis-in-produzione","\u002Fru\u002Fdata\u002Farkhitektura-tablitsy-kohorty-masshtabirovanie-analiza-drzhaniia-v-proizvodstve","\u002Ftr\u002Fdata\u002Fcohort-tablo-mimarisi-retention-analizinin-productionda-olceklenmesi",{"_path":10,"_dir":14,"_draft":15,"_partial":15,"_locale":16,"title":17,"description":18,"publishedAt":19,"modifiedAt":19,"category":14,"i18nKey":4,"tags":20,"readingTime":26,"author":27,"body":28,"_type":2438,"_id":2439,"_source":2440,"_file":2441,"_stem":2442,"_extension":2443},"data",false,"","Architettura della Tabella Cohort: Scalare l'Analisi della Retention in Produzione","Materialized views, strategie di partitioning e ottimizzazione dei costi delle query: come scalare le analisi cohort di retention in produzione, ridurre i costi e accelerare le decisioni.","2026-08-14",[21,22,23,24,25],"analisi-cohort","bigquery","materialized-views","data-engineering","retention",8,"Roibase",{"type":29,"children":30,"toc":2430},"root",[31,39,46,60,65,70,76,81,94,346,359,372,554,559,1007,1012,1018,1037,1055,1068,1081,1086,1092,1097,1102,1484,1497,1509,1821,1826,1847,1853,1872,1877,2292,2297,2343,2348,2354,2359,2424],{"type":32,"tag":33,"props":34,"children":35},"element","p",{},[36],{"type":37,"value":38},"text","L'analisi della retention è il fulcro del processo decisionale negli e-commerce e nei modelli SaaS. Tuttavia, quando le query cohort classiche vengono eseguite in ambienti di produzione, ogni analisi esegue un full-scan delle tabelle di eventi da terabyte, richiede minuti e porta i costi delle query a centinaia di dollari al giorno. Quando il calcolo della cohort avviene on-demand, il ciclo decisionale rallenta, il team di analyst si concentra sull'ottimizzazione delle query, e i dashboard non si aggiornano. La soluzione: salvare le tabelle cohort come un data asset pre-computato, partitionato e aggiornato in modo incrementale. In questo articolo ti mostriamo come implementare materialized view, partitioning e strategie di build incrementali su BigQuery, riducendo i costi delle query del 90% mentre riduci i tempi di analisi a pochi secondi e rendi le decisioni sulla retention quasi real-time.",{"type":32,"tag":40,"props":41,"children":43},"h2",{"id":42},"perché-la-query-cohort-classica-non-scala",[44],{"type":37,"value":45},"Perché la Query Cohort Classica Non Scala",{"type":32,"tag":33,"props":47,"children":48},{},[49,51,58],{"type":37,"value":50},"L'analisi cohort standard funziona così: raggruppa gli utenti in base alla data della loro prima transazione, poi calcola quale percentuale ritorna nei giorni successivi. La query SQL effettua un join della tabella ",{"type":32,"tag":52,"props":53,"children":55},"code",{"className":54},[],[56],{"type":37,"value":57},"events",{"type":37,"value":59}," due volte — una volta per trovare la data della cohort, una volta per contare il comportamento di retention. Su BigQuery, una tabella di eventi con 500 milioni di righe, questa query richiede 10-15 secondi ed è una spesa di circa $0.50. La query viene ripetuta ad ogni refresh della dashboard, ad ogni iterazione dell'analyst, ad ogni report di test A\u002FB.",{"type":32,"tag":33,"props":61,"children":62},{},[63],{"type":37,"value":64},"Il problema non è tanto il costo quanto la velocità e la flessibilità. Quando il team di analyst vuole cambiare la definizione della cohort (ad esempio, provare \"secondo add-to-cart\" anziché \"primo acquisto\"), scrivere di nuovo la query, testarla e validarla richiede ore. I dashboard rimangono stantii. Quando il team di marketing chiede \"qual era la retention della cohort della scorsa settimana\", non ci sono dati live; l'analyst esegue manualmente la query. Questo ciclo rallenta il processo decisionale di giorni.",{"type":32,"tag":33,"props":66,"children":67},{},[68],{"type":37,"value":69},"I calcoli delle cohort inoltre richiedono uno strato di aggregazione, è un data asset. La metrica di retention non è solo \"numero di utenti\", ma \"utenti attivi \u002F dimensione della cohort\". Questo rapporto deve essere aggiornato ogni giorno, il comportamento delle cohort passate nei nuovi giorni deve essere aggiunto. La query classica non supporta questa logica incrementale, ricalcola tutto da zero.",{"type":32,"tag":40,"props":71,"children":73},{"id":72},"da-query-a-tabella-materialized-view-per-cohort",[74],{"type":37,"value":75},"Da Query a Tabella: Materialized View per Cohort",{"type":32,"tag":33,"props":77,"children":78},{},[79],{"type":37,"value":80},"Il primo passo della soluzione è fissare la definizione della cohort come una materialized view. Su BigQuery, una materialized view salva il risultato della query in forma fisica e esegue un refresh incrementale quando cambiano le tabelle di base. Tuttavia, per l'analisi cohort la MV standard non è sufficiente perché la definizione della cohort e la finestra di retention sono parametri dinamici. Per questo costruiamo una struttura ibrida: una tabella di assegnazione cohort + una tabella di aggregazione degli eventi di retention.",{"type":32,"tag":33,"props":82,"children":83},{},[84,86,92],{"type":37,"value":85},"La prima tabella, ",{"type":32,"tag":52,"props":87,"children":89},{"className":88},[],[90],{"type":37,"value":91},"cohort_assignments",{"type":37,"value":93},", salva la data in cui l'utente entra per la prima volta nella cohort:",{"type":32,"tag":95,"props":96,"children":100},"pre",{"className":97,"code":98,"language":99,"meta":16,"style":16},"language-sql shiki shiki-themes github-dark","CREATE TABLE `project.dataset.cohort_assignments`\nPARTITION BY DATE(cohort_date)\nCLUSTER BY user_id\nAS\nSELECT\n  user_id,\n  MIN(DATE(event_timestamp)) AS cohort_date,\n  COUNTIF(event_name = 'purchase') AS total_purchases\nFROM `project.dataset.events`\nWHERE event_name IN ('first_visit', 'purchase', 'signup')\nGROUP BY user_id;\n","sql",[101],{"type":32,"tag":52,"props":102,"children":103},{"__ignoreMap":16},[104,127,152,171,180,189,198,233,265,279,332],{"type":32,"tag":105,"props":106,"children":109},"span",{"class":107,"line":108},"line",1,[110,116,121],{"type":32,"tag":105,"props":111,"children":113},{"style":112},"--shiki-default:#F97583",[114],{"type":37,"value":115},"CREATE",{"type":32,"tag":105,"props":117,"children":118},{"style":112},[119],{"type":37,"value":120}," TABLE",{"type":32,"tag":105,"props":122,"children":124},{"style":123},"--shiki-default:#9ECBFF",[125],{"type":37,"value":126}," `project.dataset.cohort_assignments`\n",{"type":32,"tag":105,"props":128,"children":130},{"class":107,"line":129},2,[131,136,141,146],{"type":32,"tag":105,"props":132,"children":133},{"style":112},[134],{"type":37,"value":135},"PARTITION",{"type":32,"tag":105,"props":137,"children":138},{"style":112},[139],{"type":37,"value":140}," BY",{"type":32,"tag":105,"props":142,"children":143},{"style":112},[144],{"type":37,"value":145}," DATE",{"type":32,"tag":105,"props":147,"children":149},{"style":148},"--shiki-default:#E1E4E8",[150],{"type":37,"value":151},"(cohort_date)\n",{"type":32,"tag":105,"props":153,"children":155},{"class":107,"line":154},3,[156,161,166],{"type":32,"tag":105,"props":157,"children":158},{"style":148},[159],{"type":37,"value":160},"CLUSTER ",{"type":32,"tag":105,"props":162,"children":163},{"style":112},[164],{"type":37,"value":165},"BY",{"type":32,"tag":105,"props":167,"children":168},{"style":148},[169],{"type":37,"value":170}," user_id\n",{"type":32,"tag":105,"props":172,"children":174},{"class":107,"line":173},4,[175],{"type":32,"tag":105,"props":176,"children":177},{"style":112},[178],{"type":37,"value":179},"AS\n",{"type":32,"tag":105,"props":181,"children":183},{"class":107,"line":182},5,[184],{"type":32,"tag":105,"props":185,"children":186},{"style":112},[187],{"type":37,"value":188},"SELECT\n",{"type":32,"tag":105,"props":190,"children":192},{"class":107,"line":191},6,[193],{"type":32,"tag":105,"props":194,"children":195},{"style":148},[196],{"type":37,"value":197},"  user_id,\n",{"type":32,"tag":105,"props":199,"children":201},{"class":107,"line":200},7,[202,208,213,218,223,228],{"type":32,"tag":105,"props":203,"children":205},{"style":204},"--shiki-default:#79B8FF",[206],{"type":37,"value":207},"  MIN",{"type":32,"tag":105,"props":209,"children":210},{"style":148},[211],{"type":37,"value":212},"(",{"type":32,"tag":105,"props":214,"children":215},{"style":112},[216],{"type":37,"value":217},"DATE",{"type":32,"tag":105,"props":219,"children":220},{"style":148},[221],{"type":37,"value":222},"(event_timestamp)) ",{"type":32,"tag":105,"props":224,"children":225},{"style":112},[226],{"type":37,"value":227},"AS",{"type":32,"tag":105,"props":229,"children":230},{"style":148},[231],{"type":37,"value":232}," cohort_date,\n",{"type":32,"tag":105,"props":234,"children":235},{"class":107,"line":26},[236,241,246,251,256,260],{"type":32,"tag":105,"props":237,"children":238},{"style":148},[239],{"type":37,"value":240},"  COUNTIF(event_name ",{"type":32,"tag":105,"props":242,"children":243},{"style":112},[244],{"type":37,"value":245},"=",{"type":32,"tag":105,"props":247,"children":248},{"style":123},[249],{"type":37,"value":250}," 'purchase'",{"type":32,"tag":105,"props":252,"children":253},{"style":148},[254],{"type":37,"value":255},") ",{"type":32,"tag":105,"props":257,"children":258},{"style":112},[259],{"type":37,"value":227},{"type":32,"tag":105,"props":261,"children":262},{"style":148},[263],{"type":37,"value":264}," total_purchases\n",{"type":32,"tag":105,"props":266,"children":268},{"class":107,"line":267},9,[269,274],{"type":32,"tag":105,"props":270,"children":271},{"style":112},[272],{"type":37,"value":273},"FROM",{"type":32,"tag":105,"props":275,"children":276},{"style":123},[277],{"type":37,"value":278}," `project.dataset.events`\n",{"type":32,"tag":105,"props":280,"children":282},{"class":107,"line":281},10,[283,288,293,298,303,308,313,318,322,327],{"type":32,"tag":105,"props":284,"children":285},{"style":112},[286],{"type":37,"value":287},"WHERE",{"type":32,"tag":105,"props":289,"children":290},{"style":148},[291],{"type":37,"value":292}," event_name ",{"type":32,"tag":105,"props":294,"children":295},{"style":112},[296],{"type":37,"value":297},"IN",{"type":32,"tag":105,"props":299,"children":300},{"style":148},[301],{"type":37,"value":302}," (",{"type":32,"tag":105,"props":304,"children":305},{"style":123},[306],{"type":37,"value":307},"'first_visit'",{"type":32,"tag":105,"props":309,"children":310},{"style":148},[311],{"type":37,"value":312},", ",{"type":32,"tag":105,"props":314,"children":315},{"style":123},[316],{"type":37,"value":317},"'purchase'",{"type":32,"tag":105,"props":319,"children":320},{"style":148},[321],{"type":37,"value":312},{"type":32,"tag":105,"props":323,"children":324},{"style":123},[325],{"type":37,"value":326},"'signup'",{"type":32,"tag":105,"props":328,"children":329},{"style":148},[330],{"type":37,"value":331},")\n",{"type":32,"tag":105,"props":333,"children":335},{"class":107,"line":334},11,[336,341],{"type":32,"tag":105,"props":337,"children":338},{"style":112},[339],{"type":37,"value":340},"GROUP BY",{"type":32,"tag":105,"props":342,"children":343},{"style":148},[344],{"type":37,"value":345}," user_id;\n",{"type":32,"tag":33,"props":347,"children":348},{},[349,351,357],{"type":37,"value":350},"Questa tabella contiene ogni utente una sola volta, ",{"type":32,"tag":52,"props":352,"children":354},{"className":353},[],[355],{"type":37,"value":356},"cohort_date",{"type":37,"value":358}," è la partition key. Quando arriva un nuovo utente, viene aggiunto solo alla partition pertinente. La dimensione della tabella scala con il numero di utenti (non con il numero di eventi), per 10 milioni di utenti è circa ~500 MB.",{"type":32,"tag":33,"props":360,"children":361},{},[362,364,370],{"type":37,"value":363},"La seconda tabella, ",{"type":32,"tag":52,"props":365,"children":367},{"className":366},[],[368],{"type":37,"value":369},"daily_user_activity",{"type":37,"value":371},", salva se ogni utente è attivo in ogni giorno come un boolean flag:",{"type":32,"tag":95,"props":373,"children":375},{"className":97,"code":374,"language":99,"meta":16,"style":16},"CREATE TABLE `project.dataset.daily_user_activity`\nPARTITION BY activity_date\nCLUSTER BY user_id\nAS\nSELECT\n  user_id,\n  DATE(event_timestamp) AS activity_date,\n  TRUE AS is_active\nFROM `project.dataset.events`\nWHERE event_name IN ('pageview', 'purchase', 'session_start')\nGROUP BY user_id, activity_date;\n",[376],{"type":32,"tag":52,"props":377,"children":378},{"__ignoreMap":16},[379,395,411,426,433,440,447,469,486,497,542],{"type":32,"tag":105,"props":380,"children":381},{"class":107,"line":108},[382,386,390],{"type":32,"tag":105,"props":383,"children":384},{"style":112},[385],{"type":37,"value":115},{"type":32,"tag":105,"props":387,"children":388},{"style":112},[389],{"type":37,"value":120},{"type":32,"tag":105,"props":391,"children":392},{"style":123},[393],{"type":37,"value":394}," `project.dataset.daily_user_activity`\n",{"type":32,"tag":105,"props":396,"children":397},{"class":107,"line":129},[398,402,406],{"type":32,"tag":105,"props":399,"children":400},{"style":112},[401],{"type":37,"value":135},{"type":32,"tag":105,"props":403,"children":404},{"style":112},[405],{"type":37,"value":140},{"type":32,"tag":105,"props":407,"children":408},{"style":148},[409],{"type":37,"value":410}," activity_date\n",{"type":32,"tag":105,"props":412,"children":413},{"class":107,"line":154},[414,418,422],{"type":32,"tag":105,"props":415,"children":416},{"style":148},[417],{"type":37,"value":160},{"type":32,"tag":105,"props":419,"children":420},{"style":112},[421],{"type":37,"value":165},{"type":32,"tag":105,"props":423,"children":424},{"style":148},[425],{"type":37,"value":170},{"type":32,"tag":105,"props":427,"children":428},{"class":107,"line":173},[429],{"type":32,"tag":105,"props":430,"children":431},{"style":112},[432],{"type":37,"value":179},{"type":32,"tag":105,"props":434,"children":435},{"class":107,"line":182},[436],{"type":32,"tag":105,"props":437,"children":438},{"style":112},[439],{"type":37,"value":188},{"type":32,"tag":105,"props":441,"children":442},{"class":107,"line":191},[443],{"type":32,"tag":105,"props":444,"children":445},{"style":148},[446],{"type":37,"value":197},{"type":32,"tag":105,"props":448,"children":449},{"class":107,"line":200},[450,455,460,464],{"type":32,"tag":105,"props":451,"children":452},{"style":112},[453],{"type":37,"value":454},"  DATE",{"type":32,"tag":105,"props":456,"children":457},{"style":148},[458],{"type":37,"value":459},"(event_timestamp) ",{"type":32,"tag":105,"props":461,"children":462},{"style":112},[463],{"type":37,"value":227},{"type":32,"tag":105,"props":465,"children":466},{"style":148},[467],{"type":37,"value":468}," activity_date,\n",{"type":32,"tag":105,"props":470,"children":471},{"class":107,"line":26},[472,477,481],{"type":32,"tag":105,"props":473,"children":474},{"style":148},[475],{"type":37,"value":476},"  TRUE ",{"type":32,"tag":105,"props":478,"children":479},{"style":112},[480],{"type":37,"value":227},{"type":32,"tag":105,"props":482,"children":483},{"style":148},[484],{"type":37,"value":485}," is_active\n",{"type":32,"tag":105,"props":487,"children":488},{"class":107,"line":267},[489,493],{"type":32,"tag":105,"props":490,"children":491},{"style":112},[492],{"type":37,"value":273},{"type":32,"tag":105,"props":494,"children":495},{"style":123},[496],{"type":37,"value":278},{"type":32,"tag":105,"props":498,"children":499},{"class":107,"line":281},[500,504,508,512,516,521,525,529,533,538],{"type":32,"tag":105,"props":501,"children":502},{"style":112},[503],{"type":37,"value":287},{"type":32,"tag":105,"props":505,"children":506},{"style":148},[507],{"type":37,"value":292},{"type":32,"tag":105,"props":509,"children":510},{"style":112},[511],{"type":37,"value":297},{"type":32,"tag":105,"props":513,"children":514},{"style":148},[515],{"type":37,"value":302},{"type":32,"tag":105,"props":517,"children":518},{"style":123},[519],{"type":37,"value":520},"'pageview'",{"type":32,"tag":105,"props":522,"children":523},{"style":148},[524],{"type":37,"value":312},{"type":32,"tag":105,"props":526,"children":527},{"style":123},[528],{"type":37,"value":317},{"type":32,"tag":105,"props":530,"children":531},{"style":148},[532],{"type":37,"value":312},{"type":32,"tag":105,"props":534,"children":535},{"style":123},[536],{"type":37,"value":537},"'session_start'",{"type":32,"tag":105,"props":539,"children":540},{"style":148},[541],{"type":37,"value":331},{"type":32,"tag":105,"props":543,"children":544},{"class":107,"line":334},[545,549],{"type":32,"tag":105,"props":546,"children":547},{"style":112},[548],{"type":37,"value":340},{"type":32,"tag":105,"props":550,"children":551},{"style":148},[552],{"type":37,"value":553}," user_id, activity_date;\n",{"type":32,"tag":33,"props":555,"children":556},{},[557],{"type":37,"value":558},"La query di retention la costruiamo facendo il join di queste due tabelle:",{"type":32,"tag":95,"props":560,"children":562},{"className":97,"code":561,"language":99,"meta":16,"style":16},"SELECT\n  c.cohort_date,\n  DATE_DIFF(a.activity_date, c.cohort_date, DAY) AS days_since_cohort,\n  COUNT(DISTINCT c.user_id) AS cohort_size,\n  COUNT(DISTINCT a.user_id) AS active_users,\n  SAFE_DIVIDE(COUNT(DISTINCT a.user_id), COUNT(DISTINCT c.user_id)) AS retention_rate\nFROM `project.dataset.cohort_assignments` c\nLEFT JOIN `project.dataset.daily_user_activity` a\n  ON c.user_id = a.user_id\n  AND a.activity_date >= c.cohort_date\nWHERE c.cohort_date >= '2026-01-01'\nGROUP BY c.cohort_date, days_since_cohort\nORDER BY c.cohort_date, days_since_cohort;\n",[563],{"type":32,"tag":52,"props":564,"children":565},{"__ignoreMap":16},[566,573,595,656,700,741,817,834,852,890,928,956,981],{"type":32,"tag":105,"props":567,"children":568},{"class":107,"line":108},[569],{"type":32,"tag":105,"props":570,"children":571},{"style":112},[572],{"type":37,"value":188},{"type":32,"tag":105,"props":574,"children":575},{"class":107,"line":129},[576,581,586,590],{"type":32,"tag":105,"props":577,"children":578},{"style":204},[579],{"type":37,"value":580},"  c",{"type":32,"tag":105,"props":582,"children":583},{"style":148},[584],{"type":37,"value":585},".",{"type":32,"tag":105,"props":587,"children":588},{"style":204},[589],{"type":37,"value":356},{"type":32,"tag":105,"props":591,"children":592},{"style":148},[593],{"type":37,"value":594},",\n",{"type":32,"tag":105,"props":596,"children":597},{"class":107,"line":154},[598,603,608,612,617,621,626,630,634,638,643,647,651],{"type":32,"tag":105,"props":599,"children":600},{"style":148},[601],{"type":37,"value":602},"  DATE_DIFF(",{"type":32,"tag":105,"props":604,"children":605},{"style":204},[606],{"type":37,"value":607},"a",{"type":32,"tag":105,"props":609,"children":610},{"style":148},[611],{"type":37,"value":585},{"type":32,"tag":105,"props":613,"children":614},{"style":204},[615],{"type":37,"value":616},"activity_date",{"type":32,"tag":105,"props":618,"children":619},{"style":148},[620],{"type":37,"value":312},{"type":32,"tag":105,"props":622,"children":623},{"style":204},[624],{"type":37,"value":625},"c",{"type":32,"tag":105,"props":627,"children":628},{"style":148},[629],{"type":37,"value":585},{"type":32,"tag":105,"props":631,"children":632},{"style":204},[633],{"type":37,"value":356},{"type":32,"tag":105,"props":635,"children":636},{"style":148},[637],{"type":37,"value":312},{"type":32,"tag":105,"props":639,"children":640},{"style":112},[641],{"type":37,"value":642},"DAY",{"type":32,"tag":105,"props":644,"children":645},{"style":148},[646],{"type":37,"value":255},{"type":32,"tag":105,"props":648,"children":649},{"style":112},[650],{"type":37,"value":227},{"type":32,"tag":105,"props":652,"children":653},{"style":148},[654],{"type":37,"value":655}," days_since_cohort,\n",{"type":32,"tag":105,"props":657,"children":658},{"class":107,"line":173},[659,664,668,673,678,682,687,691,695],{"type":32,"tag":105,"props":660,"children":661},{"style":204},[662],{"type":37,"value":663},"  COUNT",{"type":32,"tag":105,"props":665,"children":666},{"style":148},[667],{"type":37,"value":212},{"type":32,"tag":105,"props":669,"children":670},{"style":112},[671],{"type":37,"value":672},"DISTINCT",{"type":32,"tag":105,"props":674,"children":675},{"style":204},[676],{"type":37,"value":677}," c",{"type":32,"tag":105,"props":679,"children":680},{"style":148},[681],{"type":37,"value":585},{"type":32,"tag":105,"props":683,"children":684},{"style":204},[685],{"type":37,"value":686},"user_id",{"type":32,"tag":105,"props":688,"children":689},{"style":148},[690],{"type":37,"value":255},{"type":32,"tag":105,"props":692,"children":693},{"style":112},[694],{"type":37,"value":227},{"type":32,"tag":105,"props":696,"children":697},{"style":148},[698],{"type":37,"value":699}," cohort_size,\n",{"type":32,"tag":105,"props":701,"children":702},{"class":107,"line":182},[703,707,711,715,720,724,728,732,736],{"type":32,"tag":105,"props":704,"children":705},{"style":204},[706],{"type":37,"value":663},{"type":32,"tag":105,"props":708,"children":709},{"style":148},[710],{"type":37,"value":212},{"type":32,"tag":105,"props":712,"children":713},{"style":112},[714],{"type":37,"value":672},{"type":32,"tag":105,"props":716,"children":717},{"style":204},[718],{"type":37,"value":719}," a",{"type":32,"tag":105,"props":721,"children":722},{"style":148},[723],{"type":37,"value":585},{"type":32,"tag":105,"props":725,"children":726},{"style":204},[727],{"type":37,"value":686},{"type":32,"tag":105,"props":729,"children":730},{"style":148},[731],{"type":37,"value":255},{"type":32,"tag":105,"props":733,"children":734},{"style":112},[735],{"type":37,"value":227},{"type":32,"tag":105,"props":737,"children":738},{"style":148},[739],{"type":37,"value":740}," active_users,\n",{"type":32,"tag":105,"props":742,"children":743},{"class":107,"line":191},[744,749,754,758,762,766,770,774,779,783,787,791,795,799,803,808,812],{"type":32,"tag":105,"props":745,"children":746},{"style":148},[747],{"type":37,"value":748},"  SAFE_DIVIDE(",{"type":32,"tag":105,"props":750,"children":751},{"style":204},[752],{"type":37,"value":753},"COUNT",{"type":32,"tag":105,"props":755,"children":756},{"style":148},[757],{"type":37,"value":212},{"type":32,"tag":105,"props":759,"children":760},{"style":112},[761],{"type":37,"value":672},{"type":32,"tag":105,"props":763,"children":764},{"style":204},[765],{"type":37,"value":719},{"type":32,"tag":105,"props":767,"children":768},{"style":148},[769],{"type":37,"value":585},{"type":32,"tag":105,"props":771,"children":772},{"style":204},[773],{"type":37,"value":686},{"type":32,"tag":105,"props":775,"children":776},{"style":148},[777],{"type":37,"value":778},"), ",{"type":32,"tag":105,"props":780,"children":781},{"style":204},[782],{"type":37,"value":753},{"type":32,"tag":105,"props":784,"children":785},{"style":148},[786],{"type":37,"value":212},{"type":32,"tag":105,"props":788,"children":789},{"style":112},[790],{"type":37,"value":672},{"type":32,"tag":105,"props":792,"children":793},{"style":204},[794],{"type":37,"value":677},{"type":32,"tag":105,"props":796,"children":797},{"style":148},[798],{"type":37,"value":585},{"type":32,"tag":105,"props":800,"children":801},{"style":204},[802],{"type":37,"value":686},{"type":32,"tag":105,"props":804,"children":805},{"style":148},[806],{"type":37,"value":807},")) ",{"type":32,"tag":105,"props":809,"children":810},{"style":112},[811],{"type":37,"value":227},{"type":32,"tag":105,"props":813,"children":814},{"style":148},[815],{"type":37,"value":816}," retention_rate\n",{"type":32,"tag":105,"props":818,"children":819},{"class":107,"line":200},[820,824,829],{"type":32,"tag":105,"props":821,"children":822},{"style":112},[823],{"type":37,"value":273},{"type":32,"tag":105,"props":825,"children":826},{"style":123},[827],{"type":37,"value":828}," `project.dataset.cohort_assignments`",{"type":32,"tag":105,"props":830,"children":831},{"style":148},[832],{"type":37,"value":833}," c\n",{"type":32,"tag":105,"props":835,"children":836},{"class":107,"line":26},[837,842,847],{"type":32,"tag":105,"props":838,"children":839},{"style":112},[840],{"type":37,"value":841},"LEFT JOIN",{"type":32,"tag":105,"props":843,"children":844},{"style":123},[845],{"type":37,"value":846}," `project.dataset.daily_user_activity`",{"type":32,"tag":105,"props":848,"children":849},{"style":148},[850],{"type":37,"value":851}," a\n",{"type":32,"tag":105,"props":853,"children":854},{"class":107,"line":267},[855,860,864,868,872,877,881,885],{"type":32,"tag":105,"props":856,"children":857},{"style":112},[858],{"type":37,"value":859},"  ON",{"type":32,"tag":105,"props":861,"children":862},{"style":204},[863],{"type":37,"value":677},{"type":32,"tag":105,"props":865,"children":866},{"style":148},[867],{"type":37,"value":585},{"type":32,"tag":105,"props":869,"children":870},{"style":204},[871],{"type":37,"value":686},{"type":32,"tag":105,"props":873,"children":874},{"style":112},[875],{"type":37,"value":876}," =",{"type":32,"tag":105,"props":878,"children":879},{"style":204},[880],{"type":37,"value":719},{"type":32,"tag":105,"props":882,"children":883},{"style":148},[884],{"type":37,"value":585},{"type":32,"tag":105,"props":886,"children":887},{"style":204},[888],{"type":37,"value":889},"user_id\n",{"type":32,"tag":105,"props":891,"children":892},{"class":107,"line":281},[893,898,902,906,910,915,919,923],{"type":32,"tag":105,"props":894,"children":895},{"style":112},[896],{"type":37,"value":897},"  AND",{"type":32,"tag":105,"props":899,"children":900},{"style":204},[901],{"type":37,"value":719},{"type":32,"tag":105,"props":903,"children":904},{"style":148},[905],{"type":37,"value":585},{"type":32,"tag":105,"props":907,"children":908},{"style":204},[909],{"type":37,"value":616},{"type":32,"tag":105,"props":911,"children":912},{"style":112},[913],{"type":37,"value":914}," >=",{"type":32,"tag":105,"props":916,"children":917},{"style":204},[918],{"type":37,"value":677},{"type":32,"tag":105,"props":920,"children":921},{"style":148},[922],{"type":37,"value":585},{"type":32,"tag":105,"props":924,"children":925},{"style":204},[926],{"type":37,"value":927},"cohort_date\n",{"type":32,"tag":105,"props":929,"children":930},{"class":107,"line":334},[931,935,939,943,947,951],{"type":32,"tag":105,"props":932,"children":933},{"style":112},[934],{"type":37,"value":287},{"type":32,"tag":105,"props":936,"children":937},{"style":204},[938],{"type":37,"value":677},{"type":32,"tag":105,"props":940,"children":941},{"style":148},[942],{"type":37,"value":585},{"type":32,"tag":105,"props":944,"children":945},{"style":204},[946],{"type":37,"value":356},{"type":32,"tag":105,"props":948,"children":949},{"style":112},[950],{"type":37,"value":914},{"type":32,"tag":105,"props":952,"children":953},{"style":123},[954],{"type":37,"value":955}," '2026-01-01'\n",{"type":32,"tag":105,"props":957,"children":959},{"class":107,"line":958},12,[960,964,968,972,976],{"type":32,"tag":105,"props":961,"children":962},{"style":112},[963],{"type":37,"value":340},{"type":32,"tag":105,"props":965,"children":966},{"style":204},[967],{"type":37,"value":677},{"type":32,"tag":105,"props":969,"children":970},{"style":148},[971],{"type":37,"value":585},{"type":32,"tag":105,"props":973,"children":974},{"style":204},[975],{"type":37,"value":356},{"type":32,"tag":105,"props":977,"children":978},{"style":148},[979],{"type":37,"value":980},", days_since_cohort\n",{"type":32,"tag":105,"props":982,"children":984},{"class":107,"line":983},13,[985,990,994,998,1002],{"type":32,"tag":105,"props":986,"children":987},{"style":112},[988],{"type":37,"value":989},"ORDER BY",{"type":32,"tag":105,"props":991,"children":992},{"style":204},[993],{"type":37,"value":677},{"type":32,"tag":105,"props":995,"children":996},{"style":148},[997],{"type":37,"value":585},{"type":32,"tag":105,"props":999,"children":1000},{"style":204},[1001],{"type":37,"value":356},{"type":32,"tag":105,"props":1003,"children":1004},{"style":148},[1005],{"type":37,"value":1006},", days_since_cohort;\n",{"type":32,"tag":33,"props":1008,"children":1009},{},[1010],{"type":37,"value":1011},"Questa query non effettua più il scan della tabella di eventi da terabyte, fa solo due join piccoli. Su BigQuery, per 10 milioni di utenti richiede ~2 secondi ed è una spesa di $0.02 — una riduzione di costo del 96%.",{"type":32,"tag":40,"props":1013,"children":1015},{"id":1014},"strategia-di-partitioning-quale-data-in-quale-partition",[1016],{"type":37,"value":1017},"Strategia di Partitioning: Quale Data in Quale Partition",{"type":32,"tag":33,"props":1019,"children":1020},{},[1021,1023,1028,1030,1035],{"type":37,"value":1022},"Nella tabella cohort, la strategia di partitioning è critica perché ci sono due dimensioni temporali: la data della cohort e la data dell'attività. La tabella ",{"type":32,"tag":52,"props":1024,"children":1026},{"className":1025},[],[1027],{"type":37,"value":91},{"type":37,"value":1029}," è partizionata per ",{"type":32,"tag":52,"props":1031,"children":1033},{"className":1032},[],[1034],{"type":37,"value":356},{"type":37,"value":1036}," perché salva il primo evento dell'utente e la definizione della cohort è fissa. Quando arriva un nuovo utente, viene aggiunto solo alla partition di oggi, le partition passate rimangono immutabili.",{"type":32,"tag":33,"props":1038,"children":1039},{},[1040,1042,1047,1048,1053],{"type":37,"value":1041},"La tabella ",{"type":32,"tag":52,"props":1043,"children":1045},{"className":1044},[],[1046],{"type":37,"value":369},{"type":37,"value":1029},{"type":32,"tag":52,"props":1049,"children":1051},{"className":1050},[],[1052],{"type":37,"value":616},{"type":37,"value":1054}," perché ogni giorno arrivano nuovi dati di attività e i giorni passati non cambiano. Questa struttura è perfetta per il refresh incrementale: un job dbt o Airflow scrive ogni giorno solo la partition di oggi, non tocca le partition passate.",{"type":32,"tag":33,"props":1056,"children":1057},{},[1058,1060,1066],{"type":37,"value":1059},"Tuttavia, l'analisi di retention richiede un join tra due date: cohort_date e activity_date. Per ottimizzare le prestazioni del join, usiamo cluster key. Su BigQuery, ",{"type":32,"tag":52,"props":1061,"children":1063},{"className":1062},[],[1064],{"type":37,"value":1065},"CLUSTER BY user_id",{"type":37,"value":1067}," salva fisicamente le righe con lo stesso user_id una accanto all'altra, il join fa il pruning a livello di blocco e riduce l'I\u002FO su disco. Per 10 milioni di utenti, il join senza cluster key richiede ~8 secondi, con cluster key scende a ~2 secondi.",{"type":32,"tag":33,"props":1069,"children":1070},{},[1071,1073,1079],{"type":37,"value":1072},"Anche il partition pruning è importante. L'analisi di retention di solito analizza le cohort degli ultimi 90 giorni. Il filtro ",{"type":32,"tag":52,"props":1074,"children":1076},{"className":1075},[],[1077],{"type":37,"value":1078},"WHERE c.cohort_date >= '2026-05-01'",{"type":37,"value":1080}," attiva il partition pruning, BigQuery legge solo le partition pertinenti. Per 2 anni di dati, senza partition pruning il costo della query è ~$0.50, con partition pruning è $0.02 — perché i dati scansionati si riducono di 24 volte.",{"type":32,"tag":33,"props":1082,"children":1083},{},[1084],{"type":37,"value":1085},"Nel partitioning strategy c'è un trade-off: le partition giornaliere rendono facile il refresh incrementale ma troppi partition su BigQuery aumentano l'overhead di planning delle query. Una tabella con oltre 1000 partition aumenta il tempo di caricamento dei metadati del query planner. Per questo, i dati delle cohort più vecchi di 2 anni devono essere archiviati o consolidati in partition mensili.",{"type":32,"tag":40,"props":1087,"children":1089},{"id":1088},"refresh-incrementale-calcola-solo-i-nuovi-dati",[1090],{"type":37,"value":1091},"Refresh Incrementale: Calcola Solo i Nuovi Dati",{"type":32,"tag":33,"props":1093,"children":1094},{},[1095],{"type":37,"value":1096},"Le tabelle cohort devono essere aggiornate quotidianamente perché nuovi utenti entrano nella cohort e il comportamento di retention dei cohort esistenti si aggiorna. Tuttavia, fare un full refresh — ricalcolare l'intera tabella — è uno spreco di risorse. La soluzione: il pattern di build incrementale.",{"type":32,"tag":33,"props":1098,"children":1099},{},[1100],{"type":37,"value":1101},"Su dbt, un modello incrementale è definito così:",{"type":32,"tag":95,"props":1103,"children":1105},{"className":97,"code":1104,"language":99,"meta":16,"style":16},"{{\n  config(\n    materialized='incremental',\n    partition_by={'field': 'cohort_date', 'data_type': 'date'},\n    cluster_by=['user_id'],\n    incremental_strategy='insert_overwrite'\n  )\n}}\n\nSELECT\n  user_id,\n  MIN(DATE(event_timestamp)) AS cohort_date,\n  COUNTIF(event_name = 'purchase') AS total_purchases\nFROM {{ source('raw', 'events') }}\nWHERE DATE(event_timestamp) = CURRENT_DATE() - 1\n{% if is_incremental() %}\n  AND DATE(event_timestamp) > (SELECT MAX(cohort_date) FROM {{ this }})\n{% endif %}\nGROUP BY user_id\n",[1106],{"type":32,"tag":52,"props":1107,"children":1108},{"__ignoreMap":16},[1109,1117,1125,1146,1201,1218,1235,1243,1251,1260,1267,1274,1301,1328,1360,1395,1414,1463,1472],{"type":32,"tag":105,"props":1110,"children":1111},{"class":107,"line":108},[1112],{"type":32,"tag":105,"props":1113,"children":1114},{"style":148},[1115],{"type":37,"value":1116},"{{\n",{"type":32,"tag":105,"props":1118,"children":1119},{"class":107,"line":129},[1120],{"type":32,"tag":105,"props":1121,"children":1122},{"style":148},[1123],{"type":37,"value":1124},"  config(\n",{"type":32,"tag":105,"props":1126,"children":1127},{"class":107,"line":154},[1128,1133,1137,1142],{"type":32,"tag":105,"props":1129,"children":1130},{"style":148},[1131],{"type":37,"value":1132},"    materialized",{"type":32,"tag":105,"props":1134,"children":1135},{"style":112},[1136],{"type":37,"value":245},{"type":32,"tag":105,"props":1138,"children":1139},{"style":123},[1140],{"type":37,"value":1141},"'incremental'",{"type":32,"tag":105,"props":1143,"children":1144},{"style":148},[1145],{"type":37,"value":594},{"type":32,"tag":105,"props":1147,"children":1148},{"class":107,"line":173},[1149,1154,1158,1163,1168,1173,1178,1182,1187,1191,1196],{"type":32,"tag":105,"props":1150,"children":1151},{"style":148},[1152],{"type":37,"value":1153},"    partition_by",{"type":32,"tag":105,"props":1155,"children":1156},{"style":112},[1157],{"type":37,"value":245},{"type":32,"tag":105,"props":1159,"children":1160},{"style":148},[1161],{"type":37,"value":1162},"{",{"type":32,"tag":105,"props":1164,"children":1165},{"style":123},[1166],{"type":37,"value":1167},"'field'",{"type":32,"tag":105,"props":1169,"children":1170},{"style":148},[1171],{"type":37,"value":1172},": ",{"type":32,"tag":105,"props":1174,"children":1175},{"style":123},[1176],{"type":37,"value":1177},"'cohort_date'",{"type":32,"tag":105,"props":1179,"children":1180},{"style":148},[1181],{"type":37,"value":312},{"type":32,"tag":105,"props":1183,"children":1184},{"style":123},[1185],{"type":37,"value":1186},"'data_type'",{"type":32,"tag":105,"props":1188,"children":1189},{"style":148},[1190],{"type":37,"value":1172},{"type":32,"tag":105,"props":1192,"children":1193},{"style":123},[1194],{"type":37,"value":1195},"'date'",{"type":32,"tag":105,"props":1197,"children":1198},{"style":148},[1199],{"type":37,"value":1200},"},\n",{"type":32,"tag":105,"props":1202,"children":1203},{"class":107,"line":182},[1204,1209,1213],{"type":32,"tag":105,"props":1205,"children":1206},{"style":148},[1207],{"type":37,"value":1208},"    cluster_by",{"type":32,"tag":105,"props":1210,"children":1211},{"style":112},[1212],{"type":37,"value":245},{"type":32,"tag":105,"props":1214,"children":1215},{"style":148},[1216],{"type":37,"value":1217},"['user_id'],\n",{"type":32,"tag":105,"props":1219,"children":1220},{"class":107,"line":191},[1221,1226,1230],{"type":32,"tag":105,"props":1222,"children":1223},{"style":148},[1224],{"type":37,"value":1225},"    incremental_strategy",{"type":32,"tag":105,"props":1227,"children":1228},{"style":112},[1229],{"type":37,"value":245},{"type":32,"tag":105,"props":1231,"children":1232},{"style":123},[1233],{"type":37,"value":1234},"'insert_overwrite'\n",{"type":32,"tag":105,"props":1236,"children":1237},{"class":107,"line":200},[1238],{"type":32,"tag":105,"props":1239,"children":1240},{"style":148},[1241],{"type":37,"value":1242},"  )\n",{"type":32,"tag":105,"props":1244,"children":1245},{"class":107,"line":26},[1246],{"type":32,"tag":105,"props":1247,"children":1248},{"style":148},[1249],{"type":37,"value":1250},"}}\n",{"type":32,"tag":105,"props":1252,"children":1253},{"class":107,"line":267},[1254],{"type":32,"tag":105,"props":1255,"children":1257},{"emptyLinePlaceholder":1256},true,[1258],{"type":37,"value":1259},"\n",{"type":32,"tag":105,"props":1261,"children":1262},{"class":107,"line":281},[1263],{"type":32,"tag":105,"props":1264,"children":1265},{"style":112},[1266],{"type":37,"value":188},{"type":32,"tag":105,"props":1268,"children":1269},{"class":107,"line":334},[1270],{"type":32,"tag":105,"props":1271,"children":1272},{"style":148},[1273],{"type":37,"value":197},{"type":32,"tag":105,"props":1275,"children":1276},{"class":107,"line":958},[1277,1281,1285,1289,1293,1297],{"type":32,"tag":105,"props":1278,"children":1279},{"style":204},[1280],{"type":37,"value":207},{"type":32,"tag":105,"props":1282,"children":1283},{"style":148},[1284],{"type":37,"value":212},{"type":32,"tag":105,"props":1286,"children":1287},{"style":112},[1288],{"type":37,"value":217},{"type":32,"tag":105,"props":1290,"children":1291},{"style":148},[1292],{"type":37,"value":222},{"type":32,"tag":105,"props":1294,"children":1295},{"style":112},[1296],{"type":37,"value":227},{"type":32,"tag":105,"props":1298,"children":1299},{"style":148},[1300],{"type":37,"value":232},{"type":32,"tag":105,"props":1302,"children":1303},{"class":107,"line":983},[1304,1308,1312,1316,1320,1324],{"type":32,"tag":105,"props":1305,"children":1306},{"style":148},[1307],{"type":37,"value":240},{"type":32,"tag":105,"props":1309,"children":1310},{"style":112},[1311],{"type":37,"value":245},{"type":32,"tag":105,"props":1313,"children":1314},{"style":123},[1315],{"type":37,"value":250},{"type":32,"tag":105,"props":1317,"children":1318},{"style":148},[1319],{"type":37,"value":255},{"type":32,"tag":105,"props":1321,"children":1322},{"style":112},[1323],{"type":37,"value":227},{"type":32,"tag":105,"props":1325,"children":1326},{"style":148},[1327],{"type":37,"value":264},{"type":32,"tag":105,"props":1329,"children":1331},{"class":107,"line":1330},14,[1332,1336,1341,1346,1350,1355],{"type":32,"tag":105,"props":1333,"children":1334},{"style":112},[1335],{"type":37,"value":273},{"type":32,"tag":105,"props":1337,"children":1338},{"style":148},[1339],{"type":37,"value":1340}," {{ source(",{"type":32,"tag":105,"props":1342,"children":1343},{"style":123},[1344],{"type":37,"value":1345},"'raw'",{"type":32,"tag":105,"props":1347,"children":1348},{"style":148},[1349],{"type":37,"value":312},{"type":32,"tag":105,"props":1351,"children":1352},{"style":123},[1353],{"type":37,"value":1354},"'events'",{"type":32,"tag":105,"props":1356,"children":1357},{"style":148},[1358],{"type":37,"value":1359},") }}\n",{"type":32,"tag":105,"props":1361,"children":1363},{"class":107,"line":1362},15,[1364,1368,1372,1376,1380,1385,1390],{"type":32,"tag":105,"props":1365,"children":1366},{"style":112},[1367],{"type":37,"value":287},{"type":32,"tag":105,"props":1369,"children":1370},{"style":112},[1371],{"type":37,"value":145},{"type":32,"tag":105,"props":1373,"children":1374},{"style":148},[1375],{"type":37,"value":459},{"type":32,"tag":105,"props":1377,"children":1378},{"style":112},[1379],{"type":37,"value":245},{"type":32,"tag":105,"props":1381,"children":1382},{"style":148},[1383],{"type":37,"value":1384}," CURRENT_DATE() ",{"type":32,"tag":105,"props":1386,"children":1387},{"style":112},[1388],{"type":37,"value":1389},"-",{"type":32,"tag":105,"props":1391,"children":1392},{"style":204},[1393],{"type":37,"value":1394}," 1\n",{"type":32,"tag":105,"props":1396,"children":1398},{"class":107,"line":1397},16,[1399,1404,1409],{"type":32,"tag":105,"props":1400,"children":1401},{"style":148},[1402],{"type":37,"value":1403},"{% ",{"type":32,"tag":105,"props":1405,"children":1406},{"style":112},[1407],{"type":37,"value":1408},"if",{"type":32,"tag":105,"props":1410,"children":1411},{"style":148},[1412],{"type":37,"value":1413}," is_incremental() %}\n",{"type":32,"tag":105,"props":1415,"children":1417},{"class":107,"line":1416},17,[1418,1422,1426,1430,1435,1439,1444,1449,1454,1458],{"type":32,"tag":105,"props":1419,"children":1420},{"style":112},[1421],{"type":37,"value":897},{"type":32,"tag":105,"props":1423,"children":1424},{"style":112},[1425],{"type":37,"value":145},{"type":32,"tag":105,"props":1427,"children":1428},{"style":148},[1429],{"type":37,"value":459},{"type":32,"tag":105,"props":1431,"children":1432},{"style":112},[1433],{"type":37,"value":1434},">",{"type":32,"tag":105,"props":1436,"children":1437},{"style":148},[1438],{"type":37,"value":302},{"type":32,"tag":105,"props":1440,"children":1441},{"style":112},[1442],{"type":37,"value":1443},"SELECT",{"type":32,"tag":105,"props":1445,"children":1446},{"style":204},[1447],{"type":37,"value":1448}," MAX",{"type":32,"tag":105,"props":1450,"children":1451},{"style":148},[1452],{"type":37,"value":1453},"(cohort_date) ",{"type":32,"tag":105,"props":1455,"children":1456},{"style":112},[1457],{"type":37,"value":273},{"type":32,"tag":105,"props":1459,"children":1460},{"style":148},[1461],{"type":37,"value":1462}," {{ this }})\n",{"type":32,"tag":105,"props":1464,"children":1466},{"class":107,"line":1465},18,[1467],{"type":32,"tag":105,"props":1468,"children":1469},{"style":148},[1470],{"type":37,"value":1471},"{% endif %}\n",{"type":32,"tag":105,"props":1473,"children":1475},{"class":107,"line":1474},19,[1476,1480],{"type":32,"tag":105,"props":1477,"children":1478},{"style":112},[1479],{"type":37,"value":340},{"type":32,"tag":105,"props":1481,"children":1482},{"style":148},[1483],{"type":37,"value":170},{"type":32,"tag":33,"props":1485,"children":1486},{},[1487,1489,1495],{"type":37,"value":1488},"Questo modello ogni giorno calcola solo la partition di ieri. La strategia ",{"type":32,"tag":52,"props":1490,"children":1492},{"className":1491},[],[1493],{"type":37,"value":1494},"insert_overwrite",{"type":37,"value":1496}," cancella la partition esistente e scrive la nuova. Su BigQuery, il replace a livello di partition è atomico, le query downstream non leggono mai dati incompleti.",{"type":32,"tag":33,"props":1498,"children":1499},{},[1500,1502,1507],{"type":37,"value":1501},"Per la tabella ",{"type":32,"tag":52,"props":1503,"children":1505},{"className":1504},[],[1506],{"type":37,"value":369},{"type":37,"value":1508}," la logica incrementale è ancora più semplice perché ogni giorno si aggiunge una nuova partition, le partition passate non cambiano mai:",{"type":32,"tag":95,"props":1510,"children":1512},{"className":97,"code":1511,"language":99,"meta":16,"style":16},"{{\n  config(\n    materialized='incremental',\n    partition_by={'field': 'activity_date', 'data_type': 'date'},\n    cluster_by=['user_id']\n  )\n}}\n\nSELECT\n  user_id,\n  DATE(event_timestamp) AS activity_date,\n  TRUE AS is_active\nFROM {{ source('raw', 'events') }}\nWHERE DATE(event_timestamp) = CURRENT_DATE() - 1\n{% if is_incremental() %}\n  AND DATE(event_timestamp) NOT IN (SELECT DISTINCT activity_date FROM {{ this }})\n{% endif %}\nGROUP BY user_id, activity_date\n",[1513],{"type":32,"tag":52,"props":1514,"children":1515},{"__ignoreMap":16},[1516,1523,1530,1549,1597,1613,1620,1627,1634,1641,1648,1667,1682,1709,1740,1755,1802,1809],{"type":32,"tag":105,"props":1517,"children":1518},{"class":107,"line":108},[1519],{"type":32,"tag":105,"props":1520,"children":1521},{"style":148},[1522],{"type":37,"value":1116},{"type":32,"tag":105,"props":1524,"children":1525},{"class":107,"line":129},[1526],{"type":32,"tag":105,"props":1527,"children":1528},{"style":148},[1529],{"type":37,"value":1124},{"type":32,"tag":105,"props":1531,"children":1532},{"class":107,"line":154},[1533,1537,1541,1545],{"type":32,"tag":105,"props":1534,"children":1535},{"style":148},[1536],{"type":37,"value":1132},{"type":32,"tag":105,"props":1538,"children":1539},{"style":112},[1540],{"type":37,"value":245},{"type":32,"tag":105,"props":1542,"children":1543},{"style":123},[1544],{"type":37,"value":1141},{"type":32,"tag":105,"props":1546,"children":1547},{"style":148},[1548],{"type":37,"value":594},{"type":32,"tag":105,"props":1550,"children":1551},{"class":107,"line":173},[1552,1556,1560,1564,1568,1572,1577,1581,1585,1589,1593],{"type":32,"tag":105,"props":1553,"children":1554},{"style":148},[1555],{"type":37,"value":1153},{"type":32,"tag":105,"props":1557,"children":1558},{"style":112},[1559],{"type":37,"value":245},{"type":32,"tag":105,"props":1561,"children":1562},{"style":148},[1563],{"type":37,"value":1162},{"type":32,"tag":105,"props":1565,"children":1566},{"style":123},[1567],{"type":37,"value":1167},{"type":32,"tag":105,"props":1569,"children":1570},{"style":148},[1571],{"type":37,"value":1172},{"type":32,"tag":105,"props":1573,"children":1574},{"style":123},[1575],{"type":37,"value":1576},"'activity_date'",{"type":32,"tag":105,"props":1578,"children":1579},{"style":148},[1580],{"type":37,"value":312},{"type":32,"tag":105,"props":1582,"children":1583},{"style":123},[1584],{"type":37,"value":1186},{"type":32,"tag":105,"props":1586,"children":1587},{"style":148},[1588],{"type":37,"value":1172},{"type":32,"tag":105,"props":1590,"children":1591},{"style":123},[1592],{"type":37,"value":1195},{"type":32,"tag":105,"props":1594,"children":1595},{"style":148},[1596],{"type":37,"value":1200},{"type":32,"tag":105,"props":1598,"children":1599},{"class":107,"line":182},[1600,1604,1608],{"type":32,"tag":105,"props":1601,"children":1602},{"style":148},[1603],{"type":37,"value":1208},{"type":32,"tag":105,"props":1605,"children":1606},{"style":112},[1607],{"type":37,"value":245},{"type":32,"tag":105,"props":1609,"children":1610},{"style":148},[1611],{"type":37,"value":1612},"['user_id']\n",{"type":32,"tag":105,"props":1614,"children":1615},{"class":107,"line":191},[1616],{"type":32,"tag":105,"props":1617,"children":1618},{"style":148},[1619],{"type":37,"value":1242},{"type":32,"tag":105,"props":1621,"children":1622},{"class":107,"line":200},[1623],{"type":32,"tag":105,"props":1624,"children":1625},{"style":148},[1626],{"type":37,"value":1250},{"type":32,"tag":105,"props":1628,"children":1629},{"class":107,"line":26},[1630],{"type":32,"tag":105,"props":1631,"children":1632},{"emptyLinePlaceholder":1256},[1633],{"type":37,"value":1259},{"type":32,"tag":105,"props":1635,"children":1636},{"class":107,"line":267},[1637],{"type":32,"tag":105,"props":1638,"children":1639},{"style":112},[1640],{"type":37,"value":188},{"type":32,"tag":105,"props":1642,"children":1643},{"class":107,"line":281},[1644],{"type":32,"tag":105,"props":1645,"children":1646},{"style":148},[1647],{"type":37,"value":197},{"type":32,"tag":105,"props":1649,"children":1650},{"class":107,"line":334},[1651,1655,1659,1663],{"type":32,"tag":105,"props":1652,"children":1653},{"style":112},[1654],{"type":37,"value":454},{"type":32,"tag":105,"props":1656,"children":1657},{"style":148},[1658],{"type":37,"value":459},{"type":32,"tag":105,"props":1660,"children":1661},{"style":112},[1662],{"type":37,"value":227},{"type":32,"tag":105,"props":1664,"children":1665},{"style":148},[1666],{"type":37,"value":468},{"type":32,"tag":105,"props":1668,"children":1669},{"class":107,"line":958},[1670,1674,1678],{"type":32,"tag":105,"props":1671,"children":1672},{"style":148},[1673],{"type":37,"value":476},{"type":32,"tag":105,"props":1675,"children":1676},{"style":112},[1677],{"type":37,"value":227},{"type":32,"tag":105,"props":1679,"children":1680},{"style":148},[1681],{"type":37,"value":485},{"type":32,"tag":105,"props":1683,"children":1684},{"class":107,"line":983},[1685,1689,1693,1697,1701,1705],{"type":32,"tag":105,"props":1686,"children":1687},{"style":112},[1688],{"type":37,"value":273},{"type":32,"tag":105,"props":1690,"children":1691},{"style":148},[1692],{"type":37,"value":1340},{"type":32,"tag":105,"props":1694,"children":1695},{"style":123},[1696],{"type":37,"value":1345},{"type":32,"tag":105,"props":1698,"children":1699},{"style":148},[1700],{"type":37,"value":312},{"type":32,"tag":105,"props":1702,"children":1703},{"style":123},[1704],{"type":37,"value":1354},{"type":32,"tag":105,"props":1706,"children":1707},{"style":148},[1708],{"type":37,"value":1359},{"type":32,"tag":105,"props":1710,"children":1711},{"class":107,"line":1330},[1712,1716,1720,1724,1728,1732,1736],{"type":32,"tag":105,"props":1713,"children":1714},{"style":112},[1715],{"type":37,"value":287},{"type":32,"tag":105,"props":1717,"children":1718},{"style":112},[1719],{"type":37,"value":145},{"type":32,"tag":105,"props":1721,"children":1722},{"style":148},[1723],{"type":37,"value":459},{"type":32,"tag":105,"props":1725,"children":1726},{"style":112},[1727],{"type":37,"value":245},{"type":32,"tag":105,"props":1729,"children":1730},{"style":148},[1731],{"type":37,"value":1384},{"type":32,"tag":105,"props":1733,"children":1734},{"style":112},[1735],{"type":37,"value":1389},{"type":32,"tag":105,"props":1737,"children":1738},{"style":204},[1739],{"type":37,"value":1394},{"type":32,"tag":105,"props":1741,"children":1742},{"class":107,"line":1362},[1743,1747,1751],{"type":32,"tag":105,"props":1744,"children":1745},{"style":148},[1746],{"type":37,"value":1403},{"type":32,"tag":105,"props":1748,"children":1749},{"style":112},[1750],{"type":37,"value":1408},{"type":32,"tag":105,"props":1752,"children":1753},{"style":148},[1754],{"type":37,"value":1413},{"type":32,"tag":105,"props":1756,"children":1757},{"class":107,"line":1397},[1758,1762,1766,1770,1775,1780,1784,1789,1794,1798],{"type":32,"tag":105,"props":1759,"children":1760},{"style":112},[1761],{"type":37,"value":897},{"type":32,"tag":105,"props":1763,"children":1764},{"style":112},[1765],{"type":37,"value":145},{"type":32,"tag":105,"props":1767,"children":1768},{"style":148},[1769],{"type":37,"value":459},{"type":32,"tag":105,"props":1771,"children":1772},{"style":112},[1773],{"type":37,"value":1774},"NOT",{"type":32,"tag":105,"props":1776,"children":1777},{"style":112},[1778],{"type":37,"value":1779}," IN",{"type":32,"tag":105,"props":1781,"children":1782},{"style":148},[1783],{"type":37,"value":302},{"type":32,"tag":105,"props":1785,"children":1786},{"style":112},[1787],{"type":37,"value":1788},"SELECT DISTINCT",{"type":32,"tag":105,"props":1790,"children":1791},{"style":148},[1792],{"type":37,"value":1793}," activity_date ",{"type":32,"tag":105,"props":1795,"children":1796},{"style":112},[1797],{"type":37,"value":273},{"type":32,"tag":105,"props":1799,"children":1800},{"style":148},[1801],{"type":37,"value":1462},{"type":32,"tag":105,"props":1803,"children":1804},{"class":107,"line":1416},[1805],{"type":32,"tag":105,"props":1806,"children":1807},{"style":148},[1808],{"type":37,"value":1471},{"type":32,"tag":105,"props":1810,"children":1811},{"class":107,"line":1465},[1812,1816],{"type":32,"tag":105,"props":1813,"children":1814},{"style":112},[1815],{"type":37,"value":340},{"type":32,"tag":105,"props":1817,"children":1818},{"style":148},[1819],{"type":37,"value":1820}," user_id, activity_date\n",{"type":32,"tag":33,"props":1822,"children":1823},{},[1824],{"type":37,"value":1825},"Con il refresh incrementale il tempo del job giornaliero scende da 5 minuti a 30 secondi. L'utilizzo di slot BigQuery cala dell'80%, l'attesa in coda delle query scompare. Quando il team di analyst apre la dashboard alle 9 di mattina, i dati di retention di ieri sono pronti.",{"type":32,"tag":33,"props":1827,"children":1828},{},[1829,1831,1837,1839,1845],{"type":37,"value":1830},"Tuttavia, nel build incrementale c'è un rischio: late-arriving data. Se la pipeline di eventi ha un ritardo di 2-3 ore, la partition di ieri contiene dati incompleti. Per risolvere questo problema si usano due approcci: (1) su dbt, il parametro ",{"type":32,"tag":52,"props":1832,"children":1834},{"className":1833},[],[1835],{"type":37,"value":1836},"lookback_window",{"type":37,"value":1838}," — ricalcola gli ultimi 3 giorni ogni volta; (2) su BigQuery, usare i metadati ",{"type":32,"tag":52,"props":1840,"children":1842},{"className":1841},[],[1843],{"type":37,"value":1844},"_PARTITIONTIME",{"type":37,"value":1846}," per filtrare in base all'ora di inserimento della partition. Il secondo metodo è più efficace perché re-processa solo gli eventi che arrivano tardi.",{"type":32,"tag":40,"props":1848,"children":1850},{"id":1849},"ottimizzazione-dei-costi-delle-query-dimensione-della-tabella-e-pattern-di-scan",[1851],{"type":37,"value":1852},"Ottimizzazione dei Costi delle Query: Dimensione della Tabella e Pattern di Scan",{"type":32,"tag":33,"props":1854,"children":1855},{},[1856,1858,1863,1865,1870],{"type":37,"value":1857},"Il costo delle tabelle cohort dipende da due fattori: la dimensione della tabella (GB) e il pattern di scan della query. La tabella ",{"type":32,"tag":52,"props":1859,"children":1861},{"className":1860},[],[1862],{"type":37,"value":91},{"type":37,"value":1864}," per 10 milioni di utenti è ~500 MB, la tabella ",{"type":32,"tag":52,"props":1866,"children":1868},{"className":1867},[],[1869],{"type":37,"value":369},{"type":37,"value":1871}," per una finestra di 90 giorni è ~5 GB. Quando le due tabelle vengono sottoposte a join, BigQuery scansiona ~6 GB, il costo è ~$0.03. Ma la stessa analisi sulla tabella di eventi raw avrebbe scansionato 500 GB, il costo sarebbe ~$2.50 — una differenza di 80 volte.",{"type":32,"tag":33,"props":1873,"children":1874},{},[1875],{"type":37,"value":1876},"Per ridurre ulteriormente i costi usiamo una tabella di sintesi delle cohort pre-aggregata:",{"type":32,"tag":95,"props":1878,"children":1880},{"className":97,"code":1879,"language":99,"meta":16,"style":16},"CREATE TABLE `project.dataset.cohort_retention_summary`\nPARTITION BY cohort_date\nCLUSTER BY days_since_cohort\nAS\nSELECT\n  c.cohort_date,\n  DATE_DIFF(a.activity_date, c.cohort_date, DAY) AS days_since_cohort,\n  COUNT(DISTINCT c.user_id) AS cohort_size,\n  COUNT(DISTINCT a.user_id) AS active_users,\n  SAFE_DIVIDE(COUNT(DISTINCT a.user_id), COUNT(DISTINCT c.user_id)) AS retention_rate\nFROM `project.dataset.cohort_assignments` c\nLEFT JOIN `project.dataset.daily_user_activity` a\n  ON c.user_id = a.user_id\n  AND a.activity_date >= c.cohort_date\nGROUP BY c.cohort_date, days_since_cohort;\n",[1881],{"type":32,"tag":52,"props":1882,"children":1883},{"__ignoreMap":16},[1884,1900,1916,1932,1939,1946,1965,2020,2059,2098,2169,2184,2199,2234,2269],{"type":32,"tag":105,"props":1885,"children":1886},{"class":107,"line":108},[1887,1891,1895],{"type":32,"tag":105,"props":1888,"children":1889},{"style":112},[1890],{"type":37,"value":115},{"type":32,"tag":105,"props":1892,"children":1893},{"style":112},[1894],{"type":37,"value":120},{"type":32,"tag":105,"props":1896,"children":1897},{"style":123},[1898],{"type":37,"value":1899}," `project.dataset.cohort_retention_summary`\n",{"type":32,"tag":105,"props":1901,"children":1902},{"class":107,"line":129},[1903,1907,1911],{"type":32,"tag":105,"props":1904,"children":1905},{"style":112},[1906],{"type":37,"value":135},{"type":32,"tag":105,"props":1908,"children":1909},{"style":112},[1910],{"type":37,"value":140},{"type":32,"tag":105,"props":1912,"children":1913},{"style":148},[1914],{"type":37,"value":1915}," cohort_date\n",{"type":32,"tag":105,"props":1917,"children":1918},{"class":107,"line":154},[1919,1923,1927],{"type":32,"tag":105,"props":1920,"children":1921},{"style":148},[1922],{"type":37,"value":160},{"type":32,"tag":105,"props":1924,"children":1925},{"style":112},[1926],{"type":37,"value":165},{"type":32,"tag":105,"props":1928,"children":1929},{"style":148},[1930],{"type":37,"value":1931}," days_since_cohort\n",{"type":32,"tag":105,"props":1933,"children":1934},{"class":107,"line":173},[1935],{"type":32,"tag":105,"props":1936,"children":1937},{"style":112},[1938],{"type":37,"value":179},{"type":32,"tag":105,"props":1940,"children":1941},{"class":107,"line":182},[1942],{"type":32,"tag":105,"props":1943,"children":1944},{"style":112},[1945],{"type":37,"value":188},{"type":32,"tag":105,"props":1947,"children":1948},{"class":107,"line":191},[1949,1953,1957,1961],{"type":32,"tag":105,"props":1950,"children":1951},{"style":204},[1952],{"type":37,"value":580},{"type":32,"tag":105,"props":1954,"children":1955},{"style":148},[1956],{"type":37,"value":585},{"type":32,"tag":105,"props":1958,"children":1959},{"style":204},[1960],{"type":37,"value":356},{"type":32,"tag":105,"props":1962,"children":1963},{"style":148},[1964],{"type":37,"value":594},{"type":32,"tag":105,"props":1966,"children":1967},{"class":107,"line":200},[1968,1972,1976,1980,1984,1988,1992,1996,2000,2004,2008,2012,2016],{"type":32,"tag":105,"props":1969,"children":1970},{"style":148},[1971],{"type":37,"value":602},{"type":32,"tag":105,"props":1973,"children":1974},{"style":204},[1975],{"type":37,"value":607},{"type":32,"tag":105,"props":1977,"children":1978},{"style":148},[1979],{"type":37,"value":585},{"type":32,"tag":105,"props":1981,"children":1982},{"style":204},[1983],{"type":37,"value":616},{"type":32,"tag":105,"props":1985,"children":1986},{"style":148},[1987],{"type":37,"value":312},{"type":32,"tag":105,"props":1989,"children":1990},{"style":204},[1991],{"type":37,"value":625},{"type":32,"tag":105,"props":1993,"children":1994},{"style":148},[1995],{"type":37,"value":585},{"type":32,"tag":105,"props":1997,"children":1998},{"style":204},[1999],{"type":37,"value":356},{"type":32,"tag":105,"props":2001,"children":2002},{"style":148},[2003],{"type":37,"value":312},{"type":32,"tag":105,"props":2005,"children":2006},{"style":112},[2007],{"type":37,"value":642},{"type":32,"tag":105,"props":2009,"children":2010},{"style":148},[2011],{"type":37,"value":255},{"type":32,"tag":105,"props":2013,"children":2014},{"style":112},[2015],{"type":37,"value":227},{"type":32,"tag":105,"props":2017,"children":2018},{"style":148},[2019],{"type":37,"value":655},{"type":32,"tag":105,"props":2021,"children":2022},{"class":107,"line":26},[2023,2027,2031,2035,2039,2043,2047,2051,2055],{"type":32,"tag":105,"props":2024,"children":2025},{"style":204},[2026],{"type":37,"value":663},{"type":32,"tag":105,"props":2028,"children":2029},{"style":148},[2030],{"type":37,"value":212},{"type":32,"tag":105,"props":2032,"children":2033},{"style":112},[2034],{"type":37,"value":672},{"type":32,"tag":105,"props":2036,"children":2037},{"style":204},[2038],{"type":37,"value":677},{"type":32,"tag":105,"props":2040,"children":2041},{"style":148},[2042],{"type":37,"value":585},{"type":32,"tag":105,"props":2044,"children":2045},{"style":204},[2046],{"type":37,"value":686},{"type":32,"tag":105,"props":2048,"children":2049},{"style":148},[2050],{"type":37,"value":255},{"type":32,"tag":105,"props":2052,"children":2053},{"style":112},[2054],{"type":37,"value":227},{"type":32,"tag":105,"props":2056,"children":2057},{"style":148},[2058],{"type":37,"value":699},{"type":32,"tag":105,"props":2060,"children":2061},{"class":107,"line":267},[2062,2066,2070,2074,2078,2082,2086,2090,2094],{"type":32,"tag":105,"props":2063,"children":2064},{"style":204},[2065],{"type":37,"value":663},{"type":32,"tag":105,"props":2067,"children":2068},{"style":148},[2069],{"type":37,"value":212},{"type":32,"tag":105,"props":2071,"children":2072},{"style":112},[2073],{"type":37,"value":672},{"type":32,"tag":105,"props":2075,"children":2076},{"style":204},[2077],{"type":37,"value":719},{"type":32,"tag":105,"props":2079,"children":2080},{"style":148},[2081],{"type":37,"value":585},{"type":32,"tag":105,"props":2083,"children":2084},{"style":204},[2085],{"type":37,"value":686},{"type":32,"tag":105,"props":2087,"children":2088},{"style":148},[2089],{"type":37,"value":255},{"type":32,"tag":105,"props":2091,"children":2092},{"style":112},[2093],{"type":37,"value":227},{"type":32,"tag":105,"props":2095,"children":2096},{"style":148},[2097],{"type":37,"value":740},{"type":32,"tag":105,"props":2099,"children":2100},{"class":107,"line":281},[2101,2105,2109,2113,2117,2121,2125,2129,2133,2137,2141,2145,2149,2153,2157,2161,2165],{"type":32,"tag":105,"props":2102,"children":2103},{"style":148},[2104],{"type":37,"value":748},{"type":32,"tag":105,"props":2106,"children":2107},{"style":204},[2108],{"type":37,"value":753},{"type":32,"tag":105,"props":2110,"children":2111},{"style":148},[2112],{"type":37,"value":212},{"type":32,"tag":105,"props":2114,"children":2115},{"style":112},[2116],{"type":37,"value":672},{"type":32,"tag":105,"props":2118,"children":2119},{"style":204},[2120],{"type":37,"value":719},{"type":32,"tag":105,"props":2122,"children":2123},{"style":148},[2124],{"type":37,"value":585},{"type":32,"tag":105,"props":2126,"children":2127},{"style":204},[2128],{"type":37,"value":686},{"type":32,"tag":105,"props":2130,"children":2131},{"style":148},[2132],{"type":37,"value":778},{"type":32,"tag":105,"props":2134,"children":2135},{"style":204},[2136],{"type":37,"value":753},{"type":32,"tag":105,"props":2138,"children":2139},{"style":148},[2140],{"type":37,"value":212},{"type":32,"tag":105,"props":2142,"children":2143},{"style":112},[2144],{"type":37,"value":672},{"type":32,"tag":105,"props":2146,"children":2147},{"style":204},[2148],{"type":37,"value":677},{"type":32,"tag":105,"props":2150,"children":2151},{"style":148},[2152],{"type":37,"value":585},{"type":32,"tag":105,"props":2154,"children":2155},{"style":204},[2156],{"type":37,"value":686},{"type":32,"tag":105,"props":2158,"children":2159},{"style":148},[2160],{"type":37,"value":807},{"type":32,"tag":105,"props":2162,"children":2163},{"style":112},[2164],{"type":37,"value":227},{"type":32,"tag":105,"props":2166,"children":2167},{"style":148},[2168],{"type":37,"value":816},{"type":32,"tag":105,"props":2170,"children":2171},{"class":107,"line":334},[2172,2176,2180],{"type":32,"tag":105,"props":2173,"children":2174},{"style":112},[2175],{"type":37,"value":273},{"type":32,"tag":105,"props":2177,"children":2178},{"style":123},[2179],{"type":37,"value":828},{"type":32,"tag":105,"props":2181,"children":2182},{"style":148},[2183],{"type":37,"value":833},{"type":32,"tag":105,"props":2185,"children":2186},{"class":107,"line":958},[2187,2191,2195],{"type":32,"tag":105,"props":2188,"children":2189},{"style":112},[2190],{"type":37,"value":841},{"type":32,"tag":105,"props":2192,"children":2193},{"style":123},[2194],{"type":37,"value":846},{"type":32,"tag":105,"props":2196,"children":2197},{"style":148},[2198],{"type":37,"value":851},{"type":32,"tag":105,"props":2200,"children":2201},{"class":107,"line":983},[2202,2206,2210,2214,2218,2222,2226,2230],{"type":32,"tag":105,"props":2203,"children":2204},{"style":112},[2205],{"type":37,"value":859},{"type":32,"tag":105,"props":2207,"children":2208},{"style":204},[2209],{"type":37,"value":677},{"type":32,"tag":105,"props":2211,"children":2212},{"style":148},[2213],{"type":37,"value":585},{"type":32,"tag":105,"props":2215,"children":2216},{"style":204},[2217],{"type":37,"value":686},{"type":32,"tag":105,"props":2219,"children":2220},{"style":112},[2221],{"type":37,"value":876},{"type":32,"tag":105,"props":2223,"children":2224},{"style":204},[2225],{"type":37,"value":719},{"type":32,"tag":105,"props":2227,"children":2228},{"style":148},[2229],{"type":37,"value":585},{"type":32,"tag":105,"props":2231,"children":2232},{"style":204},[2233],{"type":37,"value":889},{"type":32,"tag":105,"props":2235,"children":2236},{"class":107,"line":1330},[2237,2241,2245,2249,2253,2257,2261,2265],{"type":32,"tag":105,"props":2238,"children":2239},{"style":112},[2240],{"type":37,"value":897},{"type":32,"tag":105,"props":2242,"children":2243},{"style":204},[2244],{"type":37,"value":719},{"type":32,"tag":105,"props":2246,"children":2247},{"style":148},[2248],{"type":37,"value":585},{"type":32,"tag":105,"props":2250,"children":2251},{"style":204},[2252],{"type":37,"value":616},{"type":32,"tag":105,"props":2254,"children":2255},{"style":112},[2256],{"type":37,"value":914},{"type":32,"tag":105,"props":2258,"children":2259},{"style":204},[2260],{"type":37,"value":677},{"type":32,"tag":105,"props":2262,"children":2263},{"style":148},[2264],{"type":37,"value":585},{"type":32,"tag":105,"props":2266,"children":2267},{"style":204},[2268],{"type":37,"value":927},{"type":32,"tag":105,"props":2270,"children":2271},{"class":107,"line":1362},[2272,2276,2280,2284,2288],{"type":32,"tag":105,"props":2273,"children":2274},{"style":112},[2275],{"type":37,"value":340},{"type":32,"tag":105,"props":2277,"children":2278},{"style":204},[2279],{"type":37,"value":677},{"type":32,"tag":105,"props":2281,"children":2282},{"style":148},[2283],{"type":37,"value":585},{"type":32,"tag":105,"props":2285,"children":2286},{"style":204},[2287],{"type":37,"value":356},{"type":32,"tag":105,"props":2289,"children":2290},{"style":148},[2291],{"type":37,"value":1006},{"type":32,"tag":33,"props":2293,"children":2294},{},[2295],{"type":37,"value":2296},"Questa tabella salva il tasso di retention pre-computato per ogni combinazione di cohort-day. La dimensione della tabella è ~100 MB (10 milioni di utenti × 90 giorni = 900 milioni di righe → dopo aggregazione ~50.000 righe). La dashboard legge questa tabella, non fa join, il tempo della query è \u003C1 secondo, il costo è ~$0.001.",{"type":32,"tag":33,"props":2298,"children":2299},{},[2300,2302,2308,2310,2315,2316,2321,2322,2327,2329,2334,2336,2341],{"type":37,"value":2301},"Un altro aspetto dell'ottimizzazione dei costi della query è non usare ",{"type":32,"tag":52,"props":2303,"children":2305},{"className":2304},[],[2306],{"type":37,"value":2307},"SELECT *",{"type":37,"value":2309},". Nell'analisi cohort servono solo le colonne ",{"type":32,"tag":52,"props":2311,"children":2313},{"className":2312},[],[2314],{"type":37,"value":686},{"type":37,"value":312},{"type":32,"tag":52,"props":2317,"children":2319},{"className":2318},[],[2320],{"type":37,"value":356},{"type":37,"value":312},{"type":32,"tag":52,"props":2323,"children":2325},{"className":2324},[],[2326],{"type":37,"value":616},{"type":37,"value":2328},". Se la tabella ",{"type":32,"tag":52,"props":2330,"children":2332},{"className":2331},[],[2333],{"type":37,"value":369},{"type":37,"value":2335}," contiene colonne extra come event_name, session_id e la query usa ",{"type":32,"tag":52,"props":2337,"children":2339},{"className":2338},[],[2340],{"type":37,"value":2307},{"type":37,"value":2342},", vengono scansionati dati non necessari. BigQuery usa storage in colonne, selezionare solo le colonne necessarie riduce l'I\u002FO su disco del 40-50%.",{"type":32,"tag":33,"props":2344,"children":2345},{},[2346],{"type":37,"value":2347},"L'ultima ottimizzazione è usare BI Engine di BigQuery. BI Engine mette in cache la tabella di sintesi cohort, le query della dashboard ritornano con latenza sub-secondo. Per una tabella di 100 MB, una prenotazione di BI Engine costa ~$10\u002Fmese, ma il risparmio sui costi delle query quando si eseguono 1000 query al giorno è ~$30\u002Fmese — un guadagno netto.",{"type":32,"tag":40,"props":2349,"children":2351},{"id":2350},"retention-engineering-pipeline-dbt-airflow-alerting",[2352],{"type":37,"value":2353},"Retention Engineering Pipeline: dbt + Airflow + Alerting",{"type":32,"tag":33,"props":2355,"children":2356},{},[2357],{"type":37,"value":2358},"In un ambiente di produzione, l'architettura cohort non è solo SQL, richiede orchestration e monitoring. La pipeline di retention è composta dai seguenti componenti:",{"type":32,"tag":2360,"props":2361,"children":2362},"ol",{},[2363,2375,2404,2414],{"type":32,"tag":2364,"props":2365,"children":2366},"li",{},[2367,2373],{"type":32,"tag":2368,"props":2369,"children":2370},"strong",{},[2371],{"type":37,"value":2372},"DAG Airflow:",{"type":37,"value":2374}," Si attiva ogni mattina alle 06:00, valida a livello di partition la tabella di eventi (controllo dei dati che arrivano in ritardo).",{"type":32,"tag":2364,"props":2376,"children":2377},{},[2378,2383,2385,2390,2391,2396,2397,2403],{"type":32,"tag":2368,"props":2379,"children":2380},{},[2381],{"type":37,"value":2382},"Modelli dbt incrementali:",{"type":37,"value":2384}," Aggiorna in sequenza le tabelle ",{"type":32,"tag":52,"props":2386,"children":2388},{"className":2387},[],[2389],{"type":37,"value":91},{"type":37,"value":312},{"type":32,"tag":52,"props":2392,"children":2394},{"className":2393},[],[2395],{"type":37,"value":369},{"type":37,"value":312},{"type":32,"tag":52,"props":2398,"children":2400},{"className":2399},[],[2401],{"type":37,"value":2402},"cohort_retention_summary",{"type":37,"value":585},{"type":32,"tag":2364,"props":2405,"children":2406},{},[2407,2412],{"type":32,"tag":2368,"props":2408,"children":2409},{},[2410],{"type":37,"value":2411},"Test di data quality:",{"type":37,"value":2413}," I test dbt verificano constraint come cohort_size > 0, retention_rate BETWEEN 0 AND 1.",{"type":32,"tag":2364,"props":2415,"children":2416},{},[2417,2422],{"type":32,"tag":2368,"props":2418,"children":2419},{},[2420],{"type":37,"value":2421},"Alerting:",{"type":37,"value":2423}," Se il tasso di retention Day 1 di oggi è inferiore del 20% alla media",{"type":32,"tag":2425,"props":2426,"children":2427},"style",{},[2428],{"type":37,"value":2429},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":16,"searchDepth":154,"depth":154,"links":2431},[2432,2433,2434,2435,2436,2437],{"id":42,"depth":129,"text":45},{"id":72,"depth":129,"text":75},{"id":1014,"depth":129,"text":1017},{"id":1088,"depth":129,"text":1091},{"id":1849,"depth":129,"text":1852},{"id":2350,"depth":129,"text":2353},"markdown","content:it:data:architettura-tabella-cohort-scalare-retention-analysis-in-produzione.md","content","it\u002Fdata\u002Farchitettura-tabella-cohort-scalare-retention-analysis-in-produzione.md","it\u002Fdata\u002Farchitettura-tabella-cohort-scalare-retention-analysis-in-produzione","md",1786860288803]