[{"data":1,"prerenderedAt":1958},["ShallowReactive",2],{"article-alternates":3,"article-\u002Fde\u002Fdata\u002Fcohort-table-architecture-production-retention-scaling":13},{"i18nKey":4,"paths":5},"data-007-2026-07",{"de":6,"en":7,"es":8,"fr":9,"it":10,"ru":11,"tr":12},"\u002Fde\u002Fdata\u002Fcohort-table-architecture-production-retention-scaling","\u002Fen\u002Fdata\u002Fcohort-table-architecture-scaling-retention-analysis-production","\u002Fes\u002Fdata\u002Farquitectura-tabla-cohorte-escalando-analisis-retencion","\u002Ffr\u002Fdata\u002Farchitecture-table-cohort-analyse-retention","\u002Fit\u002Fdata\u002Farchitettura-tabella-cohort-scalabilita-retention-analysis-production","\u002Fru\u002Fdata\u002Farkhitektura-tablitsy-kogort-masshtabirovanie-analiza-sokhraneniya-v-production","\u002Ftr\u002Fdata\u002Fcohort-tablo-mimarisi-retention-analizinin-productionda-olceklenmesi",{"_path":6,"_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":1952,"_id":1953,"_source":1954,"_file":1955,"_stem":1956,"_extension":1957},"data",false,"","Cohort Table Architecture: Scaling Retention Analysis in Production","How do you scale cohort analysis in production with materialized views, partitioning, and query cost optimization? Concrete table architecture on BigQuery and dbt.","2026-07-12",[21,22,23,24,25],"cohort-analysis","bigquery","materialized-views","query-optimization","retention",8,"Roibase",{"type":29,"children":30,"toc":1942},"root",[31,39,46,51,56,61,67,89,105,119,145,330,336,341,362,375,1435,1440,1447,1452,1457,1493,1506,1512,1517,1527,1537,1549,1554,1560,1565,1603,1762,1767,1866,1872,1877,1926,1931,1936],{"type":32,"tag":33,"props":34,"children":35},"element","p",{},[36],{"type":37,"value":38},"text","Retention analysis is one of the most critical metrics in marketing data. Understanding which user cohorts stay longest and which campaigns create lasting value requires cohort tables. The problem: classic cohort queries run against tens of millions of rows of event data from scratch each time, driving query costs astronomical. Building a cohort architecture in production—updated every morning, returning results in 3 seconds when an analyst queries it, yet minimizing costs through proper partitioning strategy—is an engineering problem in its own right. In this article, we walk through a concrete cohort table architecture on BigQuery and dbt, materialized view strategy, and query cost optimization step by step.",{"type":32,"tag":40,"props":41,"children":43},"h2",{"id":42},"why-cohort-deserves-its-own-table",[44],{"type":37,"value":45},"Why cohort deserves its own table",{"type":32,"tag":33,"props":47,"children":48},{},[49],{"type":37,"value":50},"You cannot calculate retention from raw event tables on each query. If an e-commerce company has 50 million daily events, answering \"What's the 30-day activity rate for users who signed up in January 2026?\" requires BigQuery to scan 1.5 billion rows. That query takes 10–15 seconds and processes 200–300 GB. If an analyst runs 20 different cohort segments per day, monthly query cost exceeds $500.",{"type":32,"tag":33,"props":52,"children":53},{},[54],{"type":37,"value":55},"A cohort table solves this: pre-aggregate event data by group, pre-calculate each cohort's metrics for every day, and store them. Now when an analyst queries, BigQuery only scans the cohort table, never touching raw events. 1,000 cohorts × 90 days × 5 metrics = 450,000 rows. A query against this table takes 200 ms and processes 5 MB.",{"type":32,"tag":33,"props":57,"children":58},{},[59],{"type":37,"value":60},"But this approach creates a new problem: how is the cohort table refreshed? When new events arrive daily, do you recalculate all history? Use incremental updates? What partitioning strategy optimizes both query performance and update cost? The answers lie in materialized view and incremental dbt model design.",{"type":32,"tag":40,"props":62,"children":64},{"id":63},"partition-strategy-cohort_date-or-observation_date",[65],{"type":37,"value":66},"Partition strategy: cohort_date or observation_date?",{"type":32,"tag":33,"props":68,"children":69},{},[70,72,79,81,87],{"type":37,"value":71},"Choosing the partition key for your cohort table is critical. Two candidates: cohort creation date (",{"type":32,"tag":73,"props":74,"children":76},"code",{"className":75},[],[77],{"type":37,"value":78},"cohort_date",{"type":37,"value":80},") and observation date (",{"type":32,"tag":73,"props":82,"children":84},{"className":83},[],[85],{"type":37,"value":86},"observation_date",{"type":37,"value":88},").",{"type":32,"tag":33,"props":90,"children":91},{},[92,103],{"type":32,"tag":93,"props":94,"children":95},"strong",{},[96,101],{"type":32,"tag":73,"props":97,"children":99},{"className":98},[],[100],{"type":37,"value":78},{"type":37,"value":102}," partition:",{"type":37,"value":104}," Partition by users' first activity date. January 2026 cohort in one partition, February in another. Advantage: when a new cohort forms, you write only to that partition, leaving old partitions untouched. Disadvantage: retrieving 90 days of retention data for the same cohort forces BigQuery to scan 90 partitions. Query performance suffers.",{"type":32,"tag":33,"props":106,"children":107},{},[108,117],{"type":32,"tag":93,"props":109,"children":110},{},[111,116],{"type":32,"tag":73,"props":112,"children":114},{"className":113},[],[115],{"type":37,"value":86},{"type":37,"value":102},{"type":37,"value":118}," One partition per day. On July 12, the July 12 partition holds all cohorts' metrics for that day. Advantage: queries like \"7-day retention trend\" scan only 7 partitions. Disadvantage: you must update all cohorts daily; incremental update cost is high.",{"type":32,"tag":33,"props":120,"children":121},{},[122,124,129,131,136,138,143],{"type":37,"value":123},"The right answer is ",{"type":32,"tag":93,"props":125,"children":126},{},[127],{"type":37,"value":128},"hybrid architecture with two tables:",{"type":37,"value":130}," a \"snapshot table\" (",{"type":32,"tag":73,"props":132,"children":134},{"className":133},[],[135],{"type":37,"value":86},{"type":37,"value":137}," partitioned) and an \"aggregated table\" (",{"type":32,"tag":73,"props":139,"children":141},{"className":140},[],[142],{"type":37,"value":78},{"type":37,"value":144}," partitioned). The snapshot table updates daily and feeds dashboards. The aggregated table updates weekly for deep cohort comparisons. This design aligns with BigQuery best practices: narrow and wide table separation.",{"type":32,"tag":146,"props":147,"children":151},"pre",{"className":148,"code":149,"language":150,"meta":16,"style":16},"language-sql shiki shiki-themes github-dark","-- Snapshot table schema (observation_date partitioned)\nCREATE TABLE `analytics.cohort_retention_snapshot`\nPARTITION BY observation_date\nCLUSTER BY cohort_date, channel, device_category\nAS\nSELECT\n  observation_date,\n  cohort_date,\n  channel,\n  device_category,\n  cohort_size,\n  day_n,\n  active_users,\n  retention_rate\nFROM ...\n","sql",[152],{"type":32,"tag":73,"props":153,"children":154},{"__ignoreMap":16},[155,167,188,208,227,236,245,254,262,271,280,289,298,307,316],{"type":32,"tag":156,"props":157,"children":160},"span",{"class":158,"line":159},"line",1,[161],{"type":32,"tag":156,"props":162,"children":164},{"style":163},"--shiki-default:#6A737D",[165],{"type":37,"value":166},"-- Snapshot table schema (observation_date partitioned)\n",{"type":32,"tag":156,"props":168,"children":170},{"class":158,"line":169},2,[171,177,182],{"type":32,"tag":156,"props":172,"children":174},{"style":173},"--shiki-default:#F97583",[175],{"type":37,"value":176},"CREATE",{"type":32,"tag":156,"props":178,"children":179},{"style":173},[180],{"type":37,"value":181}," TABLE",{"type":32,"tag":156,"props":183,"children":185},{"style":184},"--shiki-default:#9ECBFF",[186],{"type":37,"value":187}," `analytics.cohort_retention_snapshot`\n",{"type":32,"tag":156,"props":189,"children":191},{"class":158,"line":190},3,[192,197,202],{"type":32,"tag":156,"props":193,"children":194},{"style":173},[195],{"type":37,"value":196},"PARTITION",{"type":32,"tag":156,"props":198,"children":199},{"style":173},[200],{"type":37,"value":201}," BY",{"type":32,"tag":156,"props":203,"children":205},{"style":204},"--shiki-default:#E1E4E8",[206],{"type":37,"value":207}," observation_date\n",{"type":32,"tag":156,"props":209,"children":211},{"class":158,"line":210},4,[212,217,222],{"type":32,"tag":156,"props":213,"children":214},{"style":204},[215],{"type":37,"value":216},"CLUSTER ",{"type":32,"tag":156,"props":218,"children":219},{"style":173},[220],{"type":37,"value":221},"BY",{"type":32,"tag":156,"props":223,"children":224},{"style":204},[225],{"type":37,"value":226}," cohort_date, channel, device_category\n",{"type":32,"tag":156,"props":228,"children":230},{"class":158,"line":229},5,[231],{"type":32,"tag":156,"props":232,"children":233},{"style":173},[234],{"type":37,"value":235},"AS\n",{"type":32,"tag":156,"props":237,"children":239},{"class":158,"line":238},6,[240],{"type":32,"tag":156,"props":241,"children":242},{"style":173},[243],{"type":37,"value":244},"SELECT\n",{"type":32,"tag":156,"props":246,"children":248},{"class":158,"line":247},7,[249],{"type":32,"tag":156,"props":250,"children":251},{"style":204},[252],{"type":37,"value":253},"  observation_date,\n",{"type":32,"tag":156,"props":255,"children":256},{"class":158,"line":26},[257],{"type":32,"tag":156,"props":258,"children":259},{"style":204},[260],{"type":37,"value":261},"  cohort_date,\n",{"type":32,"tag":156,"props":263,"children":265},{"class":158,"line":264},9,[266],{"type":32,"tag":156,"props":267,"children":268},{"style":204},[269],{"type":37,"value":270},"  channel,\n",{"type":32,"tag":156,"props":272,"children":274},{"class":158,"line":273},10,[275],{"type":32,"tag":156,"props":276,"children":277},{"style":204},[278],{"type":37,"value":279},"  device_category,\n",{"type":32,"tag":156,"props":281,"children":283},{"class":158,"line":282},11,[284],{"type":32,"tag":156,"props":285,"children":286},{"style":204},[287],{"type":37,"value":288},"  cohort_size,\n",{"type":32,"tag":156,"props":290,"children":292},{"class":158,"line":291},12,[293],{"type":32,"tag":156,"props":294,"children":295},{"style":204},[296],{"type":37,"value":297},"  day_n,\n",{"type":32,"tag":156,"props":299,"children":301},{"class":158,"line":300},13,[302],{"type":32,"tag":156,"props":303,"children":304},{"style":204},[305],{"type":37,"value":306},"  active_users,\n",{"type":32,"tag":156,"props":308,"children":310},{"class":158,"line":309},14,[311],{"type":32,"tag":156,"props":312,"children":313},{"style":204},[314],{"type":37,"value":315},"  retention_rate\n",{"type":32,"tag":156,"props":317,"children":319},{"class":158,"line":318},15,[320,325],{"type":32,"tag":156,"props":321,"children":322},{"style":173},[323],{"type":37,"value":324},"FROM",{"type":32,"tag":156,"props":326,"children":327},{"style":204},[328],{"type":37,"value":329}," ...\n",{"type":32,"tag":40,"props":331,"children":333},{"id":332},"materialized-view-vs-incremental-model-tradeoff",[334],{"type":37,"value":335},"Materialized view vs incremental model tradeoff",{"type":32,"tag":33,"props":337,"children":338},{},[339],{"type":37,"value":340},"BigQuery materialized views (MV) auto-refresh incrementally—when new events arrive, the base query reruns and results cache. But MVs have three constraints: join limit (max 5), no window functions, and no manual partition control.",{"type":32,"tag":33,"props":342,"children":343},{},[344,346,352,354,360],{"type":37,"value":345},"Cohort calculation usually involves 3+ joins (users, events, subscriptions tables) and needs window functions like ",{"type":32,"tag":73,"props":347,"children":349},{"className":348},[],[350],{"type":37,"value":351},"LAG()",{"type":37,"value":353}," and ",{"type":32,"tag":73,"props":355,"children":357},{"className":356},[],[358],{"type":37,"value":359},"FIRST_VALUE()",{"type":37,"value":361},". MVs won't work here. Alternative: dbt incremental model.",{"type":32,"tag":33,"props":363,"children":364},{},[365,367,373],{"type":37,"value":366},"A dbt incremental model lets you define custom merge strategy. Each day, you update only the last 7 days' partitions (",{"type":32,"tag":73,"props":368,"children":370},{"className":369},[],[371],{"type":37,"value":372},"WHERE observation_date >= CURRENT_DATE() - 7",{"type":37,"value":374},"). This approach cuts query cost by 85%. Example dbt model:",{"type":32,"tag":146,"props":376,"children":378},{"className":148,"code":377,"language":150,"meta":16,"style":16},"{{ config(\n    materialized='incremental',\n    partition_by={\n      \"field\": \"observation_date\",\n      \"data_type\": \"date\"\n    },\n    cluster_by=['cohort_date', 'channel'],\n    incremental_strategy='insert_overwrite'\n) }}\n\nWITH daily_cohorts AS (\n  SELECT\n    DATE(first_seen_at) AS cohort_date,\n    user_id,\n    acquisition_channel AS channel\n  FROM {{ ref('users') }}\n  WHERE first_seen_at IS NOT NULL\n),\n\ndaily_activity AS (\n  SELECT\n    DATE(event_timestamp) AS activity_date,\n    user_id,\n    COUNT(*) AS event_count\n  FROM {{ ref('events') }}\n  WHERE event_name IN ('page_view', 'purchase')\n  {% if is_incremental() %}\n    AND DATE(event_timestamp) >= CURRENT_DATE() - 7\n  {% endif %}\n  GROUP BY 1, 2\n)\n\nSELECT\n  a.activity_date AS observation_date,\n  c.cohort_date,\n  c.channel,\n  DATE_DIFF(a.activity_date, c.cohort_date, DAY) AS day_n,\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 daily_cohorts c\nLEFT JOIN daily_activity a\n  ON c.user_id = a.user_id\nWHERE a.activity_date >= c.cohort_date\n{% if is_incremental() %}\n  AND a.activity_date >= CURRENT_DATE() - 7\n{% endif %}\nGROUP BY 1, 2, 3, 4\n",[379],{"type":32,"tag":73,"props":380,"children":381},{"__ignoreMap":16},[382,390,413,430,452,469,477,494,511,519,528,551,559,581,589,606,629,648,657,665,682,690,712,720,754,775,818,837,875,884,907,915,923,931,960,981,1002,1063,1108,1150,1227,1240,1254,1293,1332,1349,1386,1395],{"type":32,"tag":156,"props":383,"children":384},{"class":158,"line":159},[385],{"type":32,"tag":156,"props":386,"children":387},{"style":204},[388],{"type":37,"value":389},"{{ config(\n",{"type":32,"tag":156,"props":391,"children":392},{"class":158,"line":169},[393,398,403,408],{"type":32,"tag":156,"props":394,"children":395},{"style":204},[396],{"type":37,"value":397},"    materialized",{"type":32,"tag":156,"props":399,"children":400},{"style":173},[401],{"type":37,"value":402},"=",{"type":32,"tag":156,"props":404,"children":405},{"style":184},[406],{"type":37,"value":407},"'incremental'",{"type":32,"tag":156,"props":409,"children":410},{"style":204},[411],{"type":37,"value":412},",\n",{"type":32,"tag":156,"props":414,"children":415},{"class":158,"line":190},[416,421,425],{"type":32,"tag":156,"props":417,"children":418},{"style":204},[419],{"type":37,"value":420},"    partition_by",{"type":32,"tag":156,"props":422,"children":423},{"style":173},[424],{"type":37,"value":402},{"type":32,"tag":156,"props":426,"children":427},{"style":204},[428],{"type":37,"value":429},"{\n",{"type":32,"tag":156,"props":431,"children":432},{"class":158,"line":210},[433,438,443,448],{"type":32,"tag":156,"props":434,"children":435},{"style":184},[436],{"type":37,"value":437},"      \"field\"",{"type":32,"tag":156,"props":439,"children":440},{"style":204},[441],{"type":37,"value":442},": ",{"type":32,"tag":156,"props":444,"children":445},{"style":184},[446],{"type":37,"value":447},"\"observation_date\"",{"type":32,"tag":156,"props":449,"children":450},{"style":204},[451],{"type":37,"value":412},{"type":32,"tag":156,"props":453,"children":454},{"class":158,"line":229},[455,460,464],{"type":32,"tag":156,"props":456,"children":457},{"style":184},[458],{"type":37,"value":459},"      \"data_type\"",{"type":32,"tag":156,"props":461,"children":462},{"style":204},[463],{"type":37,"value":442},{"type":32,"tag":156,"props":465,"children":466},{"style":184},[467],{"type":37,"value":468},"\"date\"\n",{"type":32,"tag":156,"props":470,"children":471},{"class":158,"line":238},[472],{"type":32,"tag":156,"props":473,"children":474},{"style":204},[475],{"type":37,"value":476},"    },\n",{"type":32,"tag":156,"props":478,"children":479},{"class":158,"line":247},[480,485,489],{"type":32,"tag":156,"props":481,"children":482},{"style":204},[483],{"type":37,"value":484},"    cluster_by",{"type":32,"tag":156,"props":486,"children":487},{"style":173},[488],{"type":37,"value":402},{"type":32,"tag":156,"props":490,"children":491},{"style":204},[492],{"type":37,"value":493},"['cohort_date', 'channel'],\n",{"type":32,"tag":156,"props":495,"children":496},{"class":158,"line":26},[497,502,506],{"type":32,"tag":156,"props":498,"children":499},{"style":204},[500],{"type":37,"value":501},"    incremental_strategy",{"type":32,"tag":156,"props":503,"children":504},{"style":173},[505],{"type":37,"value":402},{"type":32,"tag":156,"props":507,"children":508},{"style":184},[509],{"type":37,"value":510},"'insert_overwrite'\n",{"type":32,"tag":156,"props":512,"children":513},{"class":158,"line":264},[514],{"type":32,"tag":156,"props":515,"children":516},{"style":204},[517],{"type":37,"value":518},") }}\n",{"type":32,"tag":156,"props":520,"children":521},{"class":158,"line":273},[522],{"type":32,"tag":156,"props":523,"children":525},{"emptyLinePlaceholder":524},true,[526],{"type":37,"value":527},"\n",{"type":32,"tag":156,"props":529,"children":530},{"class":158,"line":282},[531,536,541,546],{"type":32,"tag":156,"props":532,"children":533},{"style":173},[534],{"type":37,"value":535},"WITH",{"type":32,"tag":156,"props":537,"children":538},{"style":204},[539],{"type":37,"value":540}," daily_cohorts ",{"type":32,"tag":156,"props":542,"children":543},{"style":173},[544],{"type":37,"value":545},"AS",{"type":32,"tag":156,"props":547,"children":548},{"style":204},[549],{"type":37,"value":550}," (\n",{"type":32,"tag":156,"props":552,"children":553},{"class":158,"line":291},[554],{"type":32,"tag":156,"props":555,"children":556},{"style":173},[557],{"type":37,"value":558},"  SELECT\n",{"type":32,"tag":156,"props":560,"children":561},{"class":158,"line":300},[562,567,572,576],{"type":32,"tag":156,"props":563,"children":564},{"style":173},[565],{"type":37,"value":566},"    DATE",{"type":32,"tag":156,"props":568,"children":569},{"style":204},[570],{"type":37,"value":571},"(first_seen_at) ",{"type":32,"tag":156,"props":573,"children":574},{"style":173},[575],{"type":37,"value":545},{"type":32,"tag":156,"props":577,"children":578},{"style":204},[579],{"type":37,"value":580}," cohort_date,\n",{"type":32,"tag":156,"props":582,"children":583},{"class":158,"line":309},[584],{"type":32,"tag":156,"props":585,"children":586},{"style":204},[587],{"type":37,"value":588},"    user_id,\n",{"type":32,"tag":156,"props":590,"children":591},{"class":158,"line":318},[592,597,601],{"type":32,"tag":156,"props":593,"children":594},{"style":204},[595],{"type":37,"value":596},"    acquisition_channel ",{"type":32,"tag":156,"props":598,"children":599},{"style":173},[600],{"type":37,"value":545},{"type":32,"tag":156,"props":602,"children":603},{"style":204},[604],{"type":37,"value":605}," channel\n",{"type":32,"tag":156,"props":607,"children":609},{"class":158,"line":608},16,[610,615,620,625],{"type":32,"tag":156,"props":611,"children":612},{"style":173},[613],{"type":37,"value":614},"  FROM",{"type":32,"tag":156,"props":616,"children":617},{"style":204},[618],{"type":37,"value":619}," {{ ref(",{"type":32,"tag":156,"props":621,"children":622},{"style":184},[623],{"type":37,"value":624},"'users'",{"type":32,"tag":156,"props":626,"children":627},{"style":204},[628],{"type":37,"value":518},{"type":32,"tag":156,"props":630,"children":632},{"class":158,"line":631},17,[633,638,643],{"type":32,"tag":156,"props":634,"children":635},{"style":173},[636],{"type":37,"value":637},"  WHERE",{"type":32,"tag":156,"props":639,"children":640},{"style":204},[641],{"type":37,"value":642}," first_seen_at ",{"type":32,"tag":156,"props":644,"children":645},{"style":173},[646],{"type":37,"value":647},"IS NOT NULL\n",{"type":32,"tag":156,"props":649,"children":651},{"class":158,"line":650},18,[652],{"type":32,"tag":156,"props":653,"children":654},{"style":204},[655],{"type":37,"value":656},"),\n",{"type":32,"tag":156,"props":658,"children":660},{"class":158,"line":659},19,[661],{"type":32,"tag":156,"props":662,"children":663},{"emptyLinePlaceholder":524},[664],{"type":37,"value":527},{"type":32,"tag":156,"props":666,"children":668},{"class":158,"line":667},20,[669,674,678],{"type":32,"tag":156,"props":670,"children":671},{"style":204},[672],{"type":37,"value":673},"daily_activity ",{"type":32,"tag":156,"props":675,"children":676},{"style":173},[677],{"type":37,"value":545},{"type":32,"tag":156,"props":679,"children":680},{"style":204},[681],{"type":37,"value":550},{"type":32,"tag":156,"props":683,"children":685},{"class":158,"line":684},21,[686],{"type":32,"tag":156,"props":687,"children":688},{"style":173},[689],{"type":37,"value":558},{"type":32,"tag":156,"props":691,"children":693},{"class":158,"line":692},22,[694,698,703,707],{"type":32,"tag":156,"props":695,"children":696},{"style":173},[697],{"type":37,"value":566},{"type":32,"tag":156,"props":699,"children":700},{"style":204},[701],{"type":37,"value":702},"(event_timestamp) ",{"type":32,"tag":156,"props":704,"children":705},{"style":173},[706],{"type":37,"value":545},{"type":32,"tag":156,"props":708,"children":709},{"style":204},[710],{"type":37,"value":711}," activity_date,\n",{"type":32,"tag":156,"props":713,"children":715},{"class":158,"line":714},23,[716],{"type":32,"tag":156,"props":717,"children":718},{"style":204},[719],{"type":37,"value":588},{"type":32,"tag":156,"props":721,"children":723},{"class":158,"line":722},24,[724,730,735,740,745,749],{"type":32,"tag":156,"props":725,"children":727},{"style":726},"--shiki-default:#79B8FF",[728],{"type":37,"value":729},"    COUNT",{"type":32,"tag":156,"props":731,"children":732},{"style":204},[733],{"type":37,"value":734},"(",{"type":32,"tag":156,"props":736,"children":737},{"style":173},[738],{"type":37,"value":739},"*",{"type":32,"tag":156,"props":741,"children":742},{"style":204},[743],{"type":37,"value":744},") ",{"type":32,"tag":156,"props":746,"children":747},{"style":173},[748],{"type":37,"value":545},{"type":32,"tag":156,"props":750,"children":751},{"style":204},[752],{"type":37,"value":753}," event_count\n",{"type":32,"tag":156,"props":755,"children":757},{"class":158,"line":756},25,[758,762,766,771],{"type":32,"tag":156,"props":759,"children":760},{"style":173},[761],{"type":37,"value":614},{"type":32,"tag":156,"props":763,"children":764},{"style":204},[765],{"type":37,"value":619},{"type":32,"tag":156,"props":767,"children":768},{"style":184},[769],{"type":37,"value":770},"'events'",{"type":32,"tag":156,"props":772,"children":773},{"style":204},[774],{"type":37,"value":518},{"type":32,"tag":156,"props":776,"children":778},{"class":158,"line":777},26,[779,783,788,793,798,803,808,813],{"type":32,"tag":156,"props":780,"children":781},{"style":173},[782],{"type":37,"value":637},{"type":32,"tag":156,"props":784,"children":785},{"style":204},[786],{"type":37,"value":787}," event_name ",{"type":32,"tag":156,"props":789,"children":790},{"style":173},[791],{"type":37,"value":792},"IN",{"type":32,"tag":156,"props":794,"children":795},{"style":204},[796],{"type":37,"value":797}," (",{"type":32,"tag":156,"props":799,"children":800},{"style":184},[801],{"type":37,"value":802},"'page_view'",{"type":32,"tag":156,"props":804,"children":805},{"style":204},[806],{"type":37,"value":807},", ",{"type":32,"tag":156,"props":809,"children":810},{"style":184},[811],{"type":37,"value":812},"'purchase'",{"type":32,"tag":156,"props":814,"children":815},{"style":204},[816],{"type":37,"value":817},")\n",{"type":32,"tag":156,"props":819,"children":821},{"class":158,"line":820},27,[822,827,832],{"type":32,"tag":156,"props":823,"children":824},{"style":204},[825],{"type":37,"value":826},"  {% ",{"type":32,"tag":156,"props":828,"children":829},{"style":173},[830],{"type":37,"value":831},"if",{"type":32,"tag":156,"props":833,"children":834},{"style":204},[835],{"type":37,"value":836}," is_incremental() %}\n",{"type":32,"tag":156,"props":838,"children":840},{"class":158,"line":839},28,[841,846,851,855,860,865,870],{"type":32,"tag":156,"props":842,"children":843},{"style":173},[844],{"type":37,"value":845},"    AND",{"type":32,"tag":156,"props":847,"children":848},{"style":173},[849],{"type":37,"value":850}," DATE",{"type":32,"tag":156,"props":852,"children":853},{"style":204},[854],{"type":37,"value":702},{"type":32,"tag":156,"props":856,"children":857},{"style":173},[858],{"type":37,"value":859},">=",{"type":32,"tag":156,"props":861,"children":862},{"style":204},[863],{"type":37,"value":864}," CURRENT_DATE() ",{"type":32,"tag":156,"props":866,"children":867},{"style":173},[868],{"type":37,"value":869},"-",{"type":32,"tag":156,"props":871,"children":872},{"style":726},[873],{"type":37,"value":874}," 7\n",{"type":32,"tag":156,"props":876,"children":878},{"class":158,"line":877},29,[879],{"type":32,"tag":156,"props":880,"children":881},{"style":204},[882],{"type":37,"value":883},"  {% endif %}\n",{"type":32,"tag":156,"props":885,"children":887},{"class":158,"line":886},30,[888,893,898,902],{"type":32,"tag":156,"props":889,"children":890},{"style":173},[891],{"type":37,"value":892},"  GROUP BY",{"type":32,"tag":156,"props":894,"children":895},{"style":726},[896],{"type":37,"value":897}," 1",{"type":32,"tag":156,"props":899,"children":900},{"style":204},[901],{"type":37,"value":807},{"type":32,"tag":156,"props":903,"children":904},{"style":726},[905],{"type":37,"value":906},"2\n",{"type":32,"tag":156,"props":908,"children":910},{"class":158,"line":909},31,[911],{"type":32,"tag":156,"props":912,"children":913},{"style":204},[914],{"type":37,"value":817},{"type":32,"tag":156,"props":916,"children":918},{"class":158,"line":917},32,[919],{"type":32,"tag":156,"props":920,"children":921},{"emptyLinePlaceholder":524},[922],{"type":37,"value":527},{"type":32,"tag":156,"props":924,"children":926},{"class":158,"line":925},33,[927],{"type":32,"tag":156,"props":928,"children":929},{"style":173},[930],{"type":37,"value":244},{"type":32,"tag":156,"props":932,"children":934},{"class":158,"line":933},34,[935,940,945,950,955],{"type":32,"tag":156,"props":936,"children":937},{"style":726},[938],{"type":37,"value":939},"  a",{"type":32,"tag":156,"props":941,"children":942},{"style":204},[943],{"type":37,"value":944},".",{"type":32,"tag":156,"props":946,"children":947},{"style":726},[948],{"type":37,"value":949},"activity_date",{"type":32,"tag":156,"props":951,"children":952},{"style":173},[953],{"type":37,"value":954}," AS",{"type":32,"tag":156,"props":956,"children":957},{"style":204},[958],{"type":37,"value":959}," observation_date,\n",{"type":32,"tag":156,"props":961,"children":963},{"class":158,"line":962},35,[964,969,973,977],{"type":32,"tag":156,"props":965,"children":966},{"style":726},[967],{"type":37,"value":968},"  c",{"type":32,"tag":156,"props":970,"children":971},{"style":204},[972],{"type":37,"value":944},{"type":32,"tag":156,"props":974,"children":975},{"style":726},[976],{"type":37,"value":78},{"type":32,"tag":156,"props":978,"children":979},{"style":204},[980],{"type":37,"value":412},{"type":32,"tag":156,"props":982,"children":984},{"class":158,"line":983},36,[985,989,993,998],{"type":32,"tag":156,"props":986,"children":987},{"style":726},[988],{"type":37,"value":968},{"type":32,"tag":156,"props":990,"children":991},{"style":204},[992],{"type":37,"value":944},{"type":32,"tag":156,"props":994,"children":995},{"style":726},[996],{"type":37,"value":997},"channel",{"type":32,"tag":156,"props":999,"children":1000},{"style":204},[1001],{"type":37,"value":412},{"type":32,"tag":156,"props":1003,"children":1005},{"class":158,"line":1004},37,[1006,1011,1016,1020,1024,1028,1033,1037,1041,1045,1050,1054,1058],{"type":32,"tag":156,"props":1007,"children":1008},{"style":204},[1009],{"type":37,"value":1010},"  DATE_DIFF(",{"type":32,"tag":156,"props":1012,"children":1013},{"style":726},[1014],{"type":37,"value":1015},"a",{"type":32,"tag":156,"props":1017,"children":1018},{"style":204},[1019],{"type":37,"value":944},{"type":32,"tag":156,"props":1021,"children":1022},{"style":726},[1023],{"type":37,"value":949},{"type":32,"tag":156,"props":1025,"children":1026},{"style":204},[1027],{"type":37,"value":807},{"type":32,"tag":156,"props":1029,"children":1030},{"style":726},[1031],{"type":37,"value":1032},"c",{"type":32,"tag":156,"props":1034,"children":1035},{"style":204},[1036],{"type":37,"value":944},{"type":32,"tag":156,"props":1038,"children":1039},{"style":726},[1040],{"type":37,"value":78},{"type":32,"tag":156,"props":1042,"children":1043},{"style":204},[1044],{"type":37,"value":807},{"type":32,"tag":156,"props":1046,"children":1047},{"style":173},[1048],{"type":37,"value":1049},"DAY",{"type":32,"tag":156,"props":1051,"children":1052},{"style":204},[1053],{"type":37,"value":744},{"type":32,"tag":156,"props":1055,"children":1056},{"style":173},[1057],{"type":37,"value":545},{"type":32,"tag":156,"props":1059,"children":1060},{"style":204},[1061],{"type":37,"value":1062}," day_n,\n",{"type":32,"tag":156,"props":1064,"children":1066},{"class":158,"line":1065},38,[1067,1072,1076,1081,1086,1090,1095,1099,1103],{"type":32,"tag":156,"props":1068,"children":1069},{"style":726},[1070],{"type":37,"value":1071},"  COUNT",{"type":32,"tag":156,"props":1073,"children":1074},{"style":204},[1075],{"type":37,"value":734},{"type":32,"tag":156,"props":1077,"children":1078},{"style":173},[1079],{"type":37,"value":1080},"DISTINCT",{"type":32,"tag":156,"props":1082,"children":1083},{"style":726},[1084],{"type":37,"value":1085}," c",{"type":32,"tag":156,"props":1087,"children":1088},{"style":204},[1089],{"type":37,"value":944},{"type":32,"tag":156,"props":1091,"children":1092},{"style":726},[1093],{"type":37,"value":1094},"user_id",{"type":32,"tag":156,"props":1096,"children":1097},{"style":204},[1098],{"type":37,"value":744},{"type":32,"tag":156,"props":1100,"children":1101},{"style":173},[1102],{"type":37,"value":545},{"type":32,"tag":156,"props":1104,"children":1105},{"style":204},[1106],{"type":37,"value":1107}," cohort_size,\n",{"type":32,"tag":156,"props":1109,"children":1111},{"class":158,"line":1110},39,[1112,1116,1120,1124,1129,1133,1137,1141,1145],{"type":32,"tag":156,"props":1113,"children":1114},{"style":726},[1115],{"type":37,"value":1071},{"type":32,"tag":156,"props":1117,"children":1118},{"style":204},[1119],{"type":37,"value":734},{"type":32,"tag":156,"props":1121,"children":1122},{"style":173},[1123],{"type":37,"value":1080},{"type":32,"tag":156,"props":1125,"children":1126},{"style":726},[1127],{"type":37,"value":1128}," a",{"type":32,"tag":156,"props":1130,"children":1131},{"style":204},[1132],{"type":37,"value":944},{"type":32,"tag":156,"props":1134,"children":1135},{"style":726},[1136],{"type":37,"value":1094},{"type":32,"tag":156,"props":1138,"children":1139},{"style":204},[1140],{"type":37,"value":744},{"type":32,"tag":156,"props":1142,"children":1143},{"style":173},[1144],{"type":37,"value":545},{"type":32,"tag":156,"props":1146,"children":1147},{"style":204},[1148],{"type":37,"value":1149}," active_users,\n",{"type":32,"tag":156,"props":1151,"children":1153},{"class":158,"line":1152},40,[1154,1159,1164,1168,1172,1176,1180,1184,1189,1193,1197,1201,1205,1209,1213,1218,1222],{"type":32,"tag":156,"props":1155,"children":1156},{"style":204},[1157],{"type":37,"value":1158},"  SAFE_DIVIDE(",{"type":32,"tag":156,"props":1160,"children":1161},{"style":726},[1162],{"type":37,"value":1163},"COUNT",{"type":32,"tag":156,"props":1165,"children":1166},{"style":204},[1167],{"type":37,"value":734},{"type":32,"tag":156,"props":1169,"children":1170},{"style":173},[1171],{"type":37,"value":1080},{"type":32,"tag":156,"props":1173,"children":1174},{"style":726},[1175],{"type":37,"value":1128},{"type":32,"tag":156,"props":1177,"children":1178},{"style":204},[1179],{"type":37,"value":944},{"type":32,"tag":156,"props":1181,"children":1182},{"style":726},[1183],{"type":37,"value":1094},{"type":32,"tag":156,"props":1185,"children":1186},{"style":204},[1187],{"type":37,"value":1188},"), ",{"type":32,"tag":156,"props":1190,"children":1191},{"style":726},[1192],{"type":37,"value":1163},{"type":32,"tag":156,"props":1194,"children":1195},{"style":204},[1196],{"type":37,"value":734},{"type":32,"tag":156,"props":1198,"children":1199},{"style":173},[1200],{"type":37,"value":1080},{"type":32,"tag":156,"props":1202,"children":1203},{"style":726},[1204],{"type":37,"value":1085},{"type":32,"tag":156,"props":1206,"children":1207},{"style":204},[1208],{"type":37,"value":944},{"type":32,"tag":156,"props":1210,"children":1211},{"style":726},[1212],{"type":37,"value":1094},{"type":32,"tag":156,"props":1214,"children":1215},{"style":204},[1216],{"type":37,"value":1217},")) ",{"type":32,"tag":156,"props":1219,"children":1220},{"style":173},[1221],{"type":37,"value":545},{"type":32,"tag":156,"props":1223,"children":1224},{"style":204},[1225],{"type":37,"value":1226}," retention_rate\n",{"type":32,"tag":156,"props":1228,"children":1230},{"class":158,"line":1229},41,[1231,1235],{"type":32,"tag":156,"props":1232,"children":1233},{"style":173},[1234],{"type":37,"value":324},{"type":32,"tag":156,"props":1236,"children":1237},{"style":204},[1238],{"type":37,"value":1239}," daily_cohorts c\n",{"type":32,"tag":156,"props":1241,"children":1243},{"class":158,"line":1242},42,[1244,1249],{"type":32,"tag":156,"props":1245,"children":1246},{"style":173},[1247],{"type":37,"value":1248},"LEFT JOIN",{"type":32,"tag":156,"props":1250,"children":1251},{"style":204},[1252],{"type":37,"value":1253}," daily_activity a\n",{"type":32,"tag":156,"props":1255,"children":1257},{"class":158,"line":1256},43,[1258,1263,1267,1271,1275,1280,1284,1288],{"type":32,"tag":156,"props":1259,"children":1260},{"style":173},[1261],{"type":37,"value":1262},"  ON",{"type":32,"tag":156,"props":1264,"children":1265},{"style":726},[1266],{"type":37,"value":1085},{"type":32,"tag":156,"props":1268,"children":1269},{"style":204},[1270],{"type":37,"value":944},{"type":32,"tag":156,"props":1272,"children":1273},{"style":726},[1274],{"type":37,"value":1094},{"type":32,"tag":156,"props":1276,"children":1277},{"style":173},[1278],{"type":37,"value":1279}," =",{"type":32,"tag":156,"props":1281,"children":1282},{"style":726},[1283],{"type":37,"value":1128},{"type":32,"tag":156,"props":1285,"children":1286},{"style":204},[1287],{"type":37,"value":944},{"type":32,"tag":156,"props":1289,"children":1290},{"style":726},[1291],{"type":37,"value":1292},"user_id\n",{"type":32,"tag":156,"props":1294,"children":1296},{"class":158,"line":1295},44,[1297,1302,1306,1310,1314,1319,1323,1327],{"type":32,"tag":156,"props":1298,"children":1299},{"style":173},[1300],{"type":37,"value":1301},"WHERE",{"type":32,"tag":156,"props":1303,"children":1304},{"style":726},[1305],{"type":37,"value":1128},{"type":32,"tag":156,"props":1307,"children":1308},{"style":204},[1309],{"type":37,"value":944},{"type":32,"tag":156,"props":1311,"children":1312},{"style":726},[1313],{"type":37,"value":949},{"type":32,"tag":156,"props":1315,"children":1316},{"style":173},[1317],{"type":37,"value":1318}," >=",{"type":32,"tag":156,"props":1320,"children":1321},{"style":726},[1322],{"type":37,"value":1085},{"type":32,"tag":156,"props":1324,"children":1325},{"style":204},[1326],{"type":37,"value":944},{"type":32,"tag":156,"props":1328,"children":1329},{"style":726},[1330],{"type":37,"value":1331},"cohort_date\n",{"type":32,"tag":156,"props":1333,"children":1335},{"class":158,"line":1334},45,[1336,1341,1345],{"type":32,"tag":156,"props":1337,"children":1338},{"style":204},[1339],{"type":37,"value":1340},"{% ",{"type":32,"tag":156,"props":1342,"children":1343},{"style":173},[1344],{"type":37,"value":831},{"type":32,"tag":156,"props":1346,"children":1347},{"style":204},[1348],{"type":37,"value":836},{"type":32,"tag":156,"props":1350,"children":1352},{"class":158,"line":1351},46,[1353,1358,1362,1366,1370,1374,1378,1382],{"type":32,"tag":156,"props":1354,"children":1355},{"style":173},[1356],{"type":37,"value":1357},"  AND",{"type":32,"tag":156,"props":1359,"children":1360},{"style":726},[1361],{"type":37,"value":1128},{"type":32,"tag":156,"props":1363,"children":1364},{"style":204},[1365],{"type":37,"value":944},{"type":32,"tag":156,"props":1367,"children":1368},{"style":726},[1369],{"type":37,"value":949},{"type":32,"tag":156,"props":1371,"children":1372},{"style":173},[1373],{"type":37,"value":1318},{"type":32,"tag":156,"props":1375,"children":1376},{"style":204},[1377],{"type":37,"value":864},{"type":32,"tag":156,"props":1379,"children":1380},{"style":173},[1381],{"type":37,"value":869},{"type":32,"tag":156,"props":1383,"children":1384},{"style":726},[1385],{"type":37,"value":874},{"type":32,"tag":156,"props":1387,"children":1389},{"class":158,"line":1388},47,[1390],{"type":32,"tag":156,"props":1391,"children":1392},{"style":204},[1393],{"type":37,"value":1394},"{% endif %}\n",{"type":32,"tag":156,"props":1396,"children":1398},{"class":158,"line":1397},48,[1399,1404,1408,1412,1417,1421,1426,1430],{"type":32,"tag":156,"props":1400,"children":1401},{"style":173},[1402],{"type":37,"value":1403},"GROUP BY",{"type":32,"tag":156,"props":1405,"children":1406},{"style":726},[1407],{"type":37,"value":897},{"type":32,"tag":156,"props":1409,"children":1410},{"style":204},[1411],{"type":37,"value":807},{"type":32,"tag":156,"props":1413,"children":1414},{"style":726},[1415],{"type":37,"value":1416},"2",{"type":32,"tag":156,"props":1418,"children":1419},{"style":204},[1420],{"type":37,"value":807},{"type":32,"tag":156,"props":1422,"children":1423},{"style":726},[1424],{"type":37,"value":1425},"3",{"type":32,"tag":156,"props":1427,"children":1428},{"style":204},[1429],{"type":37,"value":807},{"type":32,"tag":156,"props":1431,"children":1432},{"style":726},[1433],{"type":37,"value":1434},"4\n",{"type":32,"tag":33,"props":1436,"children":1437},{},[1438],{"type":37,"value":1439},"When this model runs daily, it overwrites only the last 7 days' partitions. BigQuery processing cost drops from 20 GB daily to 2 GB. Annual query cost savings: $2,400.",{"type":32,"tag":1441,"props":1442,"children":1444},"h3",{"id":1443},"clustering-key-selection",[1445],{"type":37,"value":1446},"Clustering key selection",{"type":32,"tag":33,"props":1448,"children":1449},{},[1450],{"type":37,"value":1451},"Partitioning alone isn't enough; clustering matters too. Your cohort table filters on three dimensions: cohort_date (time), channel (source), device_category (device). In BigQuery, clustering key order matters: put the highest-cardinality field first.",{"type":32,"tag":33,"props":1453,"children":1454},{},[1455],{"type":37,"value":1456},"Cardinality analysis:",{"type":32,"tag":1458,"props":1459,"children":1460},"ul",{},[1461,1472,1482],{"type":32,"tag":1462,"props":1463,"children":1464},"li",{},[1465,1470],{"type":32,"tag":73,"props":1466,"children":1468},{"className":1467},[],[1469],{"type":37,"value":78},{"type":37,"value":1471},": 365 values (1 year)",{"type":32,"tag":1462,"props":1473,"children":1474},{},[1475,1480],{"type":32,"tag":73,"props":1476,"children":1478},{"className":1477},[],[1479],{"type":37,"value":997},{"type":37,"value":1481},": 15–20 values (organic, paid_search, social, email…)",{"type":32,"tag":1462,"props":1483,"children":1484},{},[1485,1491],{"type":32,"tag":73,"props":1486,"children":1488},{"className":1487},[],[1489],{"type":37,"value":1490},"device_category",{"type":37,"value":1492},": 3–4 values (desktop, mobile, tablet)",{"type":32,"tag":33,"props":1494,"children":1495},{},[1496,1498,1504],{"type":37,"value":1497},"Correct order: ",{"type":32,"tag":73,"props":1499,"children":1501},{"className":1500},[],[1502],{"type":37,"value":1503},"CLUSTER BY cohort_date, channel, device_category",{"type":37,"value":1505},". This ordering accelerates queries like \"30-day retention for mobile users from Instagram in Q4 2025\" by 10x.",{"type":32,"tag":40,"props":1507,"children":1509},{"id":1508},"query-cost-optimization-pre-aggregation-depth-level",[1510],{"type":37,"value":1511},"Query cost optimization: pre-aggregation depth level",{"type":32,"tag":33,"props":1513,"children":1514},{},[1515],{"type":37,"value":1516},"The granularity level of your cohort table determines the cost–performance balance. Do you store separate rows for each cohort × channel × device combination, or just overall totals?",{"type":32,"tag":33,"props":1518,"children":1519},{},[1520,1525],{"type":32,"tag":93,"props":1521,"children":1522},{},[1523],{"type":37,"value":1524},"Option 1: Granular table",{"type":37,"value":1526},"—every cohort × channel × device × day_n combination as a separate row. Total rows: 365 cohorts × 20 channels × 4 devices × 90 days = 2.6 million rows. Advantage: analysts can pivot on any segment. Disadvantage: higher storage cost ($50\u002FTB → ~$0.15\u002Fmonth).",{"type":32,"tag":33,"props":1528,"children":1529},{},[1530,1535],{"type":32,"tag":93,"props":1531,"children":1532},{},[1533],{"type":37,"value":1534},"Option 2: Aggregated table",{"type":37,"value":1536},"—only cohort × day_n, no channel or device breakdown. Total rows: 365 × 90 = 32,850. Advantage: minimal storage and query cost. Disadvantage: no channel breakdown possible.",{"type":32,"tag":33,"props":1538,"children":1539},{},[1540,1542,1547],{"type":37,"value":1541},"The right approach is ",{"type":32,"tag":93,"props":1543,"children":1544},{},[1545],{"type":37,"value":1546},"two-level tables:",{"type":37,"value":1548}," core metrics granular (with channel and device breakdown) for dashboards, extended metrics aggregated (only cohort_date × day_n) for ad-hoc analysis. This optimizes storage while preserving analytical flexibility.",{"type":32,"tag":33,"props":1550,"children":1551},{},[1552],{"type":37,"value":1553},"Also define BigQuery partition expiration policies: automatically delete partitions older than 90 days. Retention analysis rarely looks beyond 90 days; this policy cuts annual storage cost by 60%.",{"type":32,"tag":40,"props":1555,"children":1557},{"id":1556},"solving-identity-resolution-at-the-cohort-level",[1558],{"type":37,"value":1559},"Solving identity resolution at the cohort level",{"type":32,"tag":33,"props":1561,"children":1562},{},[1563],{"type":37,"value":1564},"The darkest corner of cohort analysis: user_id collisions and identity resolution. If a user signs up on desktop but transacts on mobile, two user_ids form. If the cohort table doesn't unify them, retention calculates 20% lower.",{"type":32,"tag":33,"props":1566,"children":1567},{},[1568,1570,1576,1578,1586,1588,1594,1596,1602],{"type":37,"value":1569},"Solution: before building the cohort table, merge in your identity graph. The ",{"type":32,"tag":73,"props":1571,"children":1573},{"className":1572},[],[1574],{"type":37,"value":1575},"canonical_user_id",{"type":37,"value":1577}," column from your ",{"type":32,"tag":1015,"props":1579,"children":1583},{"href":1580,"rel":1581},"https:\u002F\u002Fwww.roibase.com.tr\u002Fde\u002Ffirstparty",[1582],"nofollow",[1584],{"type":37,"value":1585},"First-Party Data & Measurement Architecture",{"type":37,"value":1587}," process enters here. In your dbt model, use a ",{"type":32,"tag":73,"props":1589,"children":1591},{"className":1590},[],[1592],{"type":37,"value":1593},"users_unified",{"type":37,"value":1595}," view instead of raw ",{"type":32,"tag":73,"props":1597,"children":1599},{"className":1598},[],[1600],{"type":37,"value":1601},"users",{"type":37,"value":944},{"type":32,"tag":146,"props":1604,"children":1606},{"className":148,"code":1605,"language":150,"meta":16,"style":16},"WITH unified_users AS (\n  SELECT\n    canonical_user_id,\n    MIN(first_seen_at) AS cohort_date,\n    ARRAY_AGG(DISTINCT acquisition_channel IGNORE NULLS ORDER BY first_seen_at LIMIT 1)[OFFSET(0)] AS channel\n  FROM {{ ref('users_unified') }}\n  GROUP BY 1\n)\n",[1607],{"type":32,"tag":73,"props":1608,"children":1609},{"__ignoreMap":16},[1610,1630,1637,1645,1665,1723,1743,1755],{"type":32,"tag":156,"props":1611,"children":1612},{"class":158,"line":159},[1613,1617,1622,1626],{"type":32,"tag":156,"props":1614,"children":1615},{"style":173},[1616],{"type":37,"value":535},{"type":32,"tag":156,"props":1618,"children":1619},{"style":204},[1620],{"type":37,"value":1621}," unified_users ",{"type":32,"tag":156,"props":1623,"children":1624},{"style":173},[1625],{"type":37,"value":545},{"type":32,"tag":156,"props":1627,"children":1628},{"style":204},[1629],{"type":37,"value":550},{"type":32,"tag":156,"props":1631,"children":1632},{"class":158,"line":169},[1633],{"type":32,"tag":156,"props":1634,"children":1635},{"style":173},[1636],{"type":37,"value":558},{"type":32,"tag":156,"props":1638,"children":1639},{"class":158,"line":190},[1640],{"type":32,"tag":156,"props":1641,"children":1642},{"style":204},[1643],{"type":37,"value":1644},"    canonical_user_id,\n",{"type":32,"tag":156,"props":1646,"children":1647},{"class":158,"line":210},[1648,1653,1657,1661],{"type":32,"tag":156,"props":1649,"children":1650},{"style":726},[1651],{"type":37,"value":1652},"    MIN",{"type":32,"tag":156,"props":1654,"children":1655},{"style":204},[1656],{"type":37,"value":571},{"type":32,"tag":156,"props":1658,"children":1659},{"style":173},[1660],{"type":37,"value":545},{"type":32,"tag":156,"props":1662,"children":1663},{"style":204},[1664],{"type":37,"value":580},{"type":32,"tag":156,"props":1666,"children":1667},{"class":158,"line":229},[1668,1673,1677,1682,1687,1692,1697,1701,1706,1710,1715,1719],{"type":32,"tag":156,"props":1669,"children":1670},{"style":204},[1671],{"type":37,"value":1672},"    ARRAY_AGG(",{"type":32,"tag":156,"props":1674,"children":1675},{"style":173},[1676],{"type":37,"value":1080},{"type":32,"tag":156,"props":1678,"children":1679},{"style":204},[1680],{"type":37,"value":1681}," acquisition_channel ",{"type":32,"tag":156,"props":1683,"children":1684},{"style":173},[1685],{"type":37,"value":1686},"IGNORE",{"type":32,"tag":156,"props":1688,"children":1689},{"style":173},[1690],{"type":37,"value":1691}," NULLS",{"type":32,"tag":156,"props":1693,"children":1694},{"style":173},[1695],{"type":37,"value":1696}," ORDER BY",{"type":32,"tag":156,"props":1698,"children":1699},{"style":204},[1700],{"type":37,"value":642},{"type":32,"tag":156,"props":1702,"children":1703},{"style":173},[1704],{"type":37,"value":1705},"LIMIT",{"type":32,"tag":156,"props":1707,"children":1708},{"style":726},[1709],{"type":37,"value":897},{"type":32,"tag":156,"props":1711,"children":1712},{"style":204},[1713],{"type":37,"value":1714},")[OFFSET(0)] ",{"type":32,"tag":156,"props":1716,"children":1717},{"style":173},[1718],{"type":37,"value":545},{"type":32,"tag":156,"props":1720,"children":1721},{"style":204},[1722],{"type":37,"value":605},{"type":32,"tag":156,"props":1724,"children":1725},{"class":158,"line":238},[1726,1730,1734,1739],{"type":32,"tag":156,"props":1727,"children":1728},{"style":173},[1729],{"type":37,"value":614},{"type":32,"tag":156,"props":1731,"children":1732},{"style":204},[1733],{"type":37,"value":619},{"type":32,"tag":156,"props":1735,"children":1736},{"style":184},[1737],{"type":37,"value":1738},"'users_unified'",{"type":32,"tag":156,"props":1740,"children":1741},{"style":204},[1742],{"type":37,"value":518},{"type":32,"tag":156,"props":1744,"children":1745},{"class":158,"line":247},[1746,1750],{"type":32,"tag":156,"props":1747,"children":1748},{"style":173},[1749],{"type":37,"value":892},{"type":32,"tag":156,"props":1751,"children":1752},{"style":726},[1753],{"type":37,"value":1754}," 1\n",{"type":32,"tag":156,"props":1756,"children":1757},{"class":158,"line":26},[1758],{"type":32,"tag":156,"props":1759,"children":1760},{"style":204},[1761],{"type":37,"value":817},{"type":32,"tag":33,"props":1763,"children":1764},{},[1765],{"type":37,"value":1766},"This approach calculates cross-device retention correctly. In production, it creates a 15–25% retention difference. When your identity resolution table updates, the cohort table must rematerialize—so declare the dependency in your dbt DAG:",{"type":32,"tag":146,"props":1768,"children":1772},{"className":1769,"code":1770,"language":1771,"meta":16,"style":16},"language-yaml shiki shiki-themes github-dark","models:\n  - name: cohort_retention_snapshot\n    config:\n      materialized: incremental\n    depends_on:\n      - ref('users_unified')\n","yaml",[1773],{"type":32,"tag":73,"props":1774,"children":1775},{"__ignoreMap":16},[1776,1790,1812,1824,1841,1853],{"type":32,"tag":156,"props":1777,"children":1778},{"class":158,"line":159},[1779,1785],{"type":32,"tag":156,"props":1780,"children":1782},{"style":1781},"--shiki-default:#85E89D",[1783],{"type":37,"value":1784},"models",{"type":32,"tag":156,"props":1786,"children":1787},{"style":204},[1788],{"type":37,"value":1789},":\n",{"type":32,"tag":156,"props":1791,"children":1792},{"class":158,"line":169},[1793,1798,1803,1807],{"type":32,"tag":156,"props":1794,"children":1795},{"style":204},[1796],{"type":37,"value":1797},"  - ",{"type":32,"tag":156,"props":1799,"children":1800},{"style":1781},[1801],{"type":37,"value":1802},"name",{"type":32,"tag":156,"props":1804,"children":1805},{"style":204},[1806],{"type":37,"value":442},{"type":32,"tag":156,"props":1808,"children":1809},{"style":184},[1810],{"type":37,"value":1811},"cohort_retention_snapshot\n",{"type":32,"tag":156,"props":1813,"children":1814},{"class":158,"line":190},[1815,1820],{"type":32,"tag":156,"props":1816,"children":1817},{"style":1781},[1818],{"type":37,"value":1819},"    config",{"type":32,"tag":156,"props":1821,"children":1822},{"style":204},[1823],{"type":37,"value":1789},{"type":32,"tag":156,"props":1825,"children":1826},{"class":158,"line":210},[1827,1832,1836],{"type":32,"tag":156,"props":1828,"children":1829},{"style":1781},[1830],{"type":37,"value":1831},"      materialized",{"type":32,"tag":156,"props":1833,"children":1834},{"style":204},[1835],{"type":37,"value":442},{"type":32,"tag":156,"props":1837,"children":1838},{"style":184},[1839],{"type":37,"value":1840},"incremental\n",{"type":32,"tag":156,"props":1842,"children":1843},{"class":158,"line":229},[1844,1849],{"type":32,"tag":156,"props":1845,"children":1846},{"style":1781},[1847],{"type":37,"value":1848},"    depends_on",{"type":32,"tag":156,"props":1850,"children":1851},{"style":204},[1852],{"type":37,"value":1789},{"type":32,"tag":156,"props":1854,"children":1855},{"class":158,"line":238},[1856,1861],{"type":32,"tag":156,"props":1857,"children":1858},{"style":204},[1859],{"type":37,"value":1860},"      - ",{"type":32,"tag":156,"props":1862,"children":1863},{"style":184},[1864],{"type":37,"value":1865},"ref('users_unified')\n",{"type":32,"tag":40,"props":1867,"children":1869},{"id":1868},"production-checklist-monitoring-and-alerting",[1870],{"type":37,"value":1871},"Production checklist: monitoring and alerting",{"type":32,"tag":33,"props":1873,"children":1874},{},[1875],{"type":37,"value":1876},"When your cohort table goes to production, continuously monitor three metrics:",{"type":32,"tag":1878,"props":1879,"children":1880},"ol",{},[1881,1899,1916],{"type":32,"tag":1462,"props":1882,"children":1883},{},[1884,1889,1891,1897],{"type":32,"tag":93,"props":1885,"children":1886},{},[1887],{"type":37,"value":1888},"Freshness:",{"type":37,"value":1890}," When was the last partition updated? Define a ",{"type":32,"tag":73,"props":1892,"children":1894},{"className":1893},[],[1895],{"type":37,"value":1896},"freshness",{"type":37,"value":1898}," test in dbt-core; if a partition is older than 24 hours, send a Slack alert.",{"type":32,"tag":1462,"props":1900,"children":1901},{},[1902,1907,1909,1915],{"type":32,"tag":93,"props":1903,"children":1904},{},[1905],{"type":37,"value":1906},"Row count drift:",{"type":37,"value":1908}," If today's cohort_size differs 30% from yesterday's, your data pipeline has a problem. Use a BigQuery scheduled query to check ",{"type":32,"tag":73,"props":1910,"children":1912},{"className":1911},[],[1913],{"type":37,"value":1914},"STDDEV()",{"type":37,"value":944},{"type":32,"tag":1462,"props":1917,"children":1918},{},[1919,1924],{"type":32,"tag":93,"props":1920,"children":1921},{},[1922],{"type":37,"value":1923},"Query cost spike:",{"type":37,"value":1925}," If average cost for queries against the cohort table jumps from $0.01 to $0.10, partition pruning isn't working. Check the INFORMATION_SCHEMA.JOBS table.",{"type":32,"tag":33,"props":1927,"children":1928},{},[1929],{"type":37,"value":1930},"Build a Google Cloud Monitoring dashboard for these three metrics. When thresholds breach, trigger PagerDuty integration. Production cohort architecture is not \"build and forget\"—it requires continuous monitoring.",{"type":32,"tag":33,"props":1932,"children":1933},{},[1934],{"type":37,"value":1935},"When cohort table architecture is built correctly, retention analysis becomes an engineering product: it updates every morning, analysts extract insights in 3 seconds, query costs are predictable. BigQuery partition strategy, dbt incremental models, and identity resolution integration are the three pillars of this architecture. Scaling retention analysis in production demands technical depth—but the payoff is measurable: $5,000+ annual query cost savings and 20% more accurate retention metrics.",{"type":32,"tag":1937,"props":1938,"children":1939},"style",{},[1940],{"type":37,"value":1941},"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":190,"depth":190,"links":1943},[1944,1945,1946,1949,1950,1951],{"id":42,"depth":169,"text":45},{"id":63,"depth":169,"text":66},{"id":332,"depth":169,"text":335,"children":1947},[1948],{"id":1443,"depth":190,"text":1446},{"id":1508,"depth":169,"text":1511},{"id":1556,"depth":169,"text":1559},{"id":1868,"depth":169,"text":1871},"markdown","content:de:data:cohort-table-architecture-production-retention-scaling.md","content","de\u002Fdata\u002Fcohort-table-architecture-production-retention-scaling.md","de\u002Fdata\u002Fcohort-table-architecture-production-retention-scaling","md",1785103517942]