[{"data":1,"prerenderedAt":2309},["ShallowReactive",2],{"article-alternates":3,"article-\u002Ffr\u002Fdata\u002Farchitecture-cohort-table-production-retention":11},{"i18nKey":4,"paths":5},"data-007-2026-06",{"de":6,"es":7,"fr":8,"it":9,"ru":10},"\u002Fde\u002Fdata\u002Fcohort-table-architecture-retention-at-scale","\u002Fes\u002Fdata\u002Farquitectura-tabla-cohortes","\u002Ffr\u002Fdata\u002Fcohort-table-architecture-production-scaling","\u002Fit\u002Fdata\u002Farchitettura-tabella-cohort-retention-production","\u002Fru\u002Fdata\u002Fcohort-tablitsa-arkhitektura-masshtabirovanie-analiza-uderzhaniia-v-produkcii",{"_path":12,"_dir":13,"_draft":14,"_partial":14,"_locale":15,"title":16,"description":17,"publishedAt":18,"modifiedAt":18,"category":13,"i18nKey":4,"tags":19,"readingTime":25,"author":26,"body":27,"_type":2303,"_id":2304,"_source":2305,"_file":2306,"_stem":2307,"_extension":2308},"\u002Ffr\u002Fdata\u002Farchitecture-cohort-table-production-retention","data",false,"","Architecture de Table Cohort : Scaling de l'Analyse de Rétention en Production","Materialized view, partitioning et optimisation des coûts de requêtes pour exécuter les analyses cohort sur 10M+ événements quotidiens avec une latence en millisecondes.","2026-06-26",[20,21,22,23,24],"cohort-analysis","bigquery-optimization","materialized-views","retention-engineering","data-partitioning",9,"Roibase",{"type":28,"children":29,"toc":2290},"root",[30,38,45,50,415,420,459,464,470,483,868,880,900,907,940,953,959,971,1510,1536,1542,1555,1574,1580,1585,1603,1613,1623,1629,1641,1694,1944,1963,1969,1974,1982,2007,2012,2084,2105,2111,2116,2269,2274,2279,2284],{"type":31,"tag":32,"props":33,"children":34},"element","p",{},[35],{"type":36,"value":37},"text","Si votre tableau de bord de rétention attend 45 secondes à chaque chargement, le problème n'est pas votre définition cohort — c'est votre architecture de table. Calculer D1, D7, D30 retention sur 10 millions d'événements quotidiens dans BigQuery peut coûter 2 To de scan et 10 dollars. Ou, avec une stratégie de partitioning appropriée, une materialized view incrémentale et une pré-agrégation, cela peut descendre à 200 Mo de scan et 50 millisecondes. La différence, c'est la limite entre « production-ready » et « ça marche mais personne ne peut l'utiliser ».",{"type":31,"tag":39,"props":40,"children":42},"h2",{"id":41},"pourquoi-lanalyse-cohort-explose-en-production",[43],{"type":36,"value":44},"Pourquoi l'Analyse Cohort Explose en Production",{"type":31,"tag":32,"props":46,"children":47},{},[48],{"type":36,"value":49},"Le calcul de rétention est par nature une opération full-scan. Vous devez trouver la première transaction de chaque utilisateur, compter ses activités les jours suivants, les regrouper par cohort, calculer les pourcentages. L'approche SQL naïve ressemble à ceci :",{"type":31,"tag":51,"props":52,"children":56},"pre",{"className":53,"code":54,"language":55,"meta":15,"style":15},"language-sql shiki shiki-themes github-dark","WITH first_events AS (\n  SELECT user_id, MIN(event_date) AS cohort_date\n  FROM events\n  GROUP BY user_id\n),\nretention_raw AS (\n  SELECT \n    f.cohort_date,\n    DATE_DIFF(e.event_date, f.cohort_date, DAY) AS day_offset,\n    COUNT(DISTINCT e.user_id) AS active_users\n  FROM events e\n  JOIN first_events f USING(user_id)\n  GROUP BY 1, 2\n)\nSELECT * FROM retention_raw;\n","sql",[57],{"type":31,"tag":58,"props":59,"children":60},"code",{"__ignoreMap":15},[61,89,123,137,151,160,177,190,214,277,323,336,360,382,391],{"type":31,"tag":62,"props":63,"children":66},"span",{"class":64,"line":65},"line",1,[67,73,79,84],{"type":31,"tag":62,"props":68,"children":70},{"style":69},"--shiki-default:#F97583",[71],{"type":36,"value":72},"WITH",{"type":31,"tag":62,"props":74,"children":76},{"style":75},"--shiki-default:#E1E4E8",[77],{"type":36,"value":78}," first_events ",{"type":31,"tag":62,"props":80,"children":81},{"style":69},[82],{"type":36,"value":83},"AS",{"type":31,"tag":62,"props":85,"children":86},{"style":75},[87],{"type":36,"value":88}," (\n",{"type":31,"tag":62,"props":90,"children":92},{"class":64,"line":91},2,[93,98,103,109,114,118],{"type":31,"tag":62,"props":94,"children":95},{"style":69},[96],{"type":36,"value":97},"  SELECT",{"type":31,"tag":62,"props":99,"children":100},{"style":75},[101],{"type":36,"value":102}," user_id, ",{"type":31,"tag":62,"props":104,"children":106},{"style":105},"--shiki-default:#79B8FF",[107],{"type":36,"value":108},"MIN",{"type":31,"tag":62,"props":110,"children":111},{"style":75},[112],{"type":36,"value":113},"(event_date) ",{"type":31,"tag":62,"props":115,"children":116},{"style":69},[117],{"type":36,"value":83},{"type":31,"tag":62,"props":119,"children":120},{"style":75},[121],{"type":36,"value":122}," cohort_date\n",{"type":31,"tag":62,"props":124,"children":126},{"class":64,"line":125},3,[127,132],{"type":31,"tag":62,"props":128,"children":129},{"style":69},[130],{"type":36,"value":131},"  FROM",{"type":31,"tag":62,"props":133,"children":134},{"style":75},[135],{"type":36,"value":136}," events\n",{"type":31,"tag":62,"props":138,"children":140},{"class":64,"line":139},4,[141,146],{"type":31,"tag":62,"props":142,"children":143},{"style":69},[144],{"type":36,"value":145},"  GROUP BY",{"type":31,"tag":62,"props":147,"children":148},{"style":75},[149],{"type":36,"value":150}," user_id\n",{"type":31,"tag":62,"props":152,"children":154},{"class":64,"line":153},5,[155],{"type":31,"tag":62,"props":156,"children":157},{"style":75},[158],{"type":36,"value":159},"),\n",{"type":31,"tag":62,"props":161,"children":163},{"class":64,"line":162},6,[164,169,173],{"type":31,"tag":62,"props":165,"children":166},{"style":75},[167],{"type":36,"value":168},"retention_raw ",{"type":31,"tag":62,"props":170,"children":171},{"style":69},[172],{"type":36,"value":83},{"type":31,"tag":62,"props":174,"children":175},{"style":75},[176],{"type":36,"value":88},{"type":31,"tag":62,"props":178,"children":180},{"class":64,"line":179},7,[181,185],{"type":31,"tag":62,"props":182,"children":183},{"style":69},[184],{"type":36,"value":97},{"type":31,"tag":62,"props":186,"children":187},{"style":75},[188],{"type":36,"value":189}," \n",{"type":31,"tag":62,"props":191,"children":193},{"class":64,"line":192},8,[194,199,204,209],{"type":31,"tag":62,"props":195,"children":196},{"style":105},[197],{"type":36,"value":198},"    f",{"type":31,"tag":62,"props":200,"children":201},{"style":75},[202],{"type":36,"value":203},".",{"type":31,"tag":62,"props":205,"children":206},{"style":105},[207],{"type":36,"value":208},"cohort_date",{"type":31,"tag":62,"props":210,"children":211},{"style":75},[212],{"type":36,"value":213},",\n",{"type":31,"tag":62,"props":215,"children":216},{"class":64,"line":25},[217,222,227,231,236,241,246,250,254,258,263,268,272],{"type":31,"tag":62,"props":218,"children":219},{"style":75},[220],{"type":36,"value":221},"    DATE_DIFF(",{"type":31,"tag":62,"props":223,"children":224},{"style":105},[225],{"type":36,"value":226},"e",{"type":31,"tag":62,"props":228,"children":229},{"style":75},[230],{"type":36,"value":203},{"type":31,"tag":62,"props":232,"children":233},{"style":105},[234],{"type":36,"value":235},"event_date",{"type":31,"tag":62,"props":237,"children":238},{"style":75},[239],{"type":36,"value":240},", ",{"type":31,"tag":62,"props":242,"children":243},{"style":105},[244],{"type":36,"value":245},"f",{"type":31,"tag":62,"props":247,"children":248},{"style":75},[249],{"type":36,"value":203},{"type":31,"tag":62,"props":251,"children":252},{"style":105},[253],{"type":36,"value":208},{"type":31,"tag":62,"props":255,"children":256},{"style":75},[257],{"type":36,"value":240},{"type":31,"tag":62,"props":259,"children":260},{"style":69},[261],{"type":36,"value":262},"DAY",{"type":31,"tag":62,"props":264,"children":265},{"style":75},[266],{"type":36,"value":267},") ",{"type":31,"tag":62,"props":269,"children":270},{"style":69},[271],{"type":36,"value":83},{"type":31,"tag":62,"props":273,"children":274},{"style":75},[275],{"type":36,"value":276}," day_offset,\n",{"type":31,"tag":62,"props":278,"children":280},{"class":64,"line":279},10,[281,286,291,296,301,305,310,314,318],{"type":31,"tag":62,"props":282,"children":283},{"style":105},[284],{"type":36,"value":285},"    COUNT",{"type":31,"tag":62,"props":287,"children":288},{"style":75},[289],{"type":36,"value":290},"(",{"type":31,"tag":62,"props":292,"children":293},{"style":69},[294],{"type":36,"value":295},"DISTINCT",{"type":31,"tag":62,"props":297,"children":298},{"style":105},[299],{"type":36,"value":300}," e",{"type":31,"tag":62,"props":302,"children":303},{"style":75},[304],{"type":36,"value":203},{"type":31,"tag":62,"props":306,"children":307},{"style":105},[308],{"type":36,"value":309},"user_id",{"type":31,"tag":62,"props":311,"children":312},{"style":75},[313],{"type":36,"value":267},{"type":31,"tag":62,"props":315,"children":316},{"style":69},[317],{"type":36,"value":83},{"type":31,"tag":62,"props":319,"children":320},{"style":75},[321],{"type":36,"value":322}," active_users\n",{"type":31,"tag":62,"props":324,"children":326},{"class":64,"line":325},11,[327,331],{"type":31,"tag":62,"props":328,"children":329},{"style":69},[330],{"type":36,"value":131},{"type":31,"tag":62,"props":332,"children":333},{"style":75},[334],{"type":36,"value":335}," events e\n",{"type":31,"tag":62,"props":337,"children":339},{"class":64,"line":338},12,[340,345,350,355],{"type":31,"tag":62,"props":341,"children":342},{"style":69},[343],{"type":36,"value":344},"  JOIN",{"type":31,"tag":62,"props":346,"children":347},{"style":75},[348],{"type":36,"value":349}," first_events f ",{"type":31,"tag":62,"props":351,"children":352},{"style":69},[353],{"type":36,"value":354},"USING",{"type":31,"tag":62,"props":356,"children":357},{"style":75},[358],{"type":36,"value":359},"(user_id)\n",{"type":31,"tag":62,"props":361,"children":363},{"class":64,"line":362},13,[364,368,373,377],{"type":31,"tag":62,"props":365,"children":366},{"style":69},[367],{"type":36,"value":145},{"type":31,"tag":62,"props":369,"children":370},{"style":105},[371],{"type":36,"value":372}," 1",{"type":31,"tag":62,"props":374,"children":375},{"style":75},[376],{"type":36,"value":240},{"type":31,"tag":62,"props":378,"children":379},{"style":105},[380],{"type":36,"value":381},"2\n",{"type":31,"tag":62,"props":383,"children":385},{"class":64,"line":384},14,[386],{"type":31,"tag":62,"props":387,"children":388},{"style":75},[389],{"type":36,"value":390},")\n",{"type":31,"tag":62,"props":392,"children":394},{"class":64,"line":393},15,[395,400,405,410],{"type":31,"tag":62,"props":396,"children":397},{"style":69},[398],{"type":36,"value":399},"SELECT",{"type":31,"tag":62,"props":401,"children":402},{"style":69},[403],{"type":36,"value":404}," *",{"type":31,"tag":62,"props":406,"children":407},{"style":69},[408],{"type":36,"value":409}," FROM",{"type":31,"tag":62,"props":411,"children":412},{"style":75},[413],{"type":36,"value":414}," retention_raw;\n",{"type":31,"tag":32,"props":416,"children":417},{},[418],{"type":36,"value":419},"Cette requête lit la table events de bout en bout à chaque exécution. 500 jours d'historique × 10M événements quotidiens = 5 milliards de lignes. L'utilisation de slots dans BigQuery explose, votre tableau de bord attend 40 secondes, votre outil BI timeout. Le problème se concentre sur trois points :",{"type":31,"tag":32,"props":421,"children":422},{},[423,429,431,436,438,442,447,449,452,457],{"type":31,"tag":424,"props":425,"children":426},"strong",{},[427],{"type":36,"value":428},"1. Full table scan :",{"type":36,"value":430}," Pas d'élagage de partition, car la jointure ",{"type":31,"tag":58,"props":432,"children":434},{"className":433},[],[435],{"type":36,"value":309},{"type":36,"value":437}," dépasse les limites de partition.",{"type":31,"tag":439,"props":440,"children":441},"br",{},[],{"type":31,"tag":424,"props":443,"children":444},{},[445],{"type":36,"value":446},"2. Calcul répété :",{"type":36,"value":448}," Chaque cohort_date est déjà connu mais recalculé à chaque requête.",{"type":31,"tag":439,"props":450,"children":451},{},[],{"type":31,"tag":424,"props":453,"children":454},{},[455],{"type":36,"value":456},"3. Surcharge d'agrégation :",{"type":36,"value":458}," Vous extraires 45 000 lignes (500 cohorts × 90 jours) de 5 milliards de lignes — un ratio compute\u002Foutput de 100 000:1.",{"type":31,"tag":32,"props":460,"children":461},{},[462],{"type":36,"value":463},"Cette approche n'est pas viable en production. La solution est de repenser l'architecture de la table.",{"type":31,"tag":39,"props":465,"children":467},{"id":466},"base-cohort-matérialisée-première-étape-avec-snapshot-incrémental",[468],{"type":36,"value":469},"Base Cohort Matérialisée : Première Étape avec Snapshot Incrémental",{"type":31,"tag":32,"props":471,"children":472},{},[473,475,481],{"type":36,"value":474},"La partie coûteuse de l'analyse cohort est le calcul ",{"type":31,"tag":58,"props":476,"children":478},{"className":477},[],[479],{"type":36,"value":480},"MIN(event_date)",{"type":36,"value":482},". Faites ce calcul une fois, écrivez le résultat dans une table snapshot, puis ajoutez quotidiennement seulement les nouveaux utilisateurs. Dans BigQuery, au lieu d'une materialized view incrémentale, nous utilisons un modèle dbt incrémental :",{"type":31,"tag":51,"props":484,"children":486},{"className":53,"code":485,"language":55,"meta":15,"style":15},"-- models\u002Fcohorts\u002Fuser_cohort_base.sql\n{{ config(\n  materialized='incremental',\n  unique_key='user_id',\n  partition_by={'field': 'cohort_date', 'data_type': 'date'},\n  cluster_by=['cohort_date', 'user_id']\n) }}\n\nSELECT\n  user_id,\n  MIN(event_date) AS cohort_date,\n  COUNT(*) AS first_day_events\nFROM {{ source('raw', 'events') }}\n{% if is_incremental() %}\nWHERE event_date >= (SELECT MAX(cohort_date) FROM {{ this }})\n  AND user_id NOT IN (SELECT user_id FROM {{ this }})\n{% endif %}\nGROUP BY user_id\n",[487],{"type":31,"tag":58,"props":488,"children":489},{"__ignoreMap":15},[490,499,507,530,551,606,623,631,640,648,656,677,707,738,756,802,846,855],{"type":31,"tag":62,"props":491,"children":492},{"class":64,"line":65},[493],{"type":31,"tag":62,"props":494,"children":496},{"style":495},"--shiki-default:#6A737D",[497],{"type":36,"value":498},"-- models\u002Fcohorts\u002Fuser_cohort_base.sql\n",{"type":31,"tag":62,"props":500,"children":501},{"class":64,"line":91},[502],{"type":31,"tag":62,"props":503,"children":504},{"style":75},[505],{"type":36,"value":506},"{{ config(\n",{"type":31,"tag":62,"props":508,"children":509},{"class":64,"line":125},[510,515,520,526],{"type":31,"tag":62,"props":511,"children":512},{"style":75},[513],{"type":36,"value":514},"  materialized",{"type":31,"tag":62,"props":516,"children":517},{"style":69},[518],{"type":36,"value":519},"=",{"type":31,"tag":62,"props":521,"children":523},{"style":522},"--shiki-default:#9ECBFF",[524],{"type":36,"value":525},"'incremental'",{"type":31,"tag":62,"props":527,"children":528},{"style":75},[529],{"type":36,"value":213},{"type":31,"tag":62,"props":531,"children":532},{"class":64,"line":139},[533,538,542,547],{"type":31,"tag":62,"props":534,"children":535},{"style":75},[536],{"type":36,"value":537},"  unique_key",{"type":31,"tag":62,"props":539,"children":540},{"style":69},[541],{"type":36,"value":519},{"type":31,"tag":62,"props":543,"children":544},{"style":522},[545],{"type":36,"value":546},"'user_id'",{"type":31,"tag":62,"props":548,"children":549},{"style":75},[550],{"type":36,"value":213},{"type":31,"tag":62,"props":552,"children":553},{"class":64,"line":153},[554,559,563,568,573,578,583,587,592,596,601],{"type":31,"tag":62,"props":555,"children":556},{"style":75},[557],{"type":36,"value":558},"  partition_by",{"type":31,"tag":62,"props":560,"children":561},{"style":69},[562],{"type":36,"value":519},{"type":31,"tag":62,"props":564,"children":565},{"style":75},[566],{"type":36,"value":567},"{",{"type":31,"tag":62,"props":569,"children":570},{"style":522},[571],{"type":36,"value":572},"'field'",{"type":31,"tag":62,"props":574,"children":575},{"style":75},[576],{"type":36,"value":577},": ",{"type":31,"tag":62,"props":579,"children":580},{"style":522},[581],{"type":36,"value":582},"'cohort_date'",{"type":31,"tag":62,"props":584,"children":585},{"style":75},[586],{"type":36,"value":240},{"type":31,"tag":62,"props":588,"children":589},{"style":522},[590],{"type":36,"value":591},"'data_type'",{"type":31,"tag":62,"props":593,"children":594},{"style":75},[595],{"type":36,"value":577},{"type":31,"tag":62,"props":597,"children":598},{"style":522},[599],{"type":36,"value":600},"'date'",{"type":31,"tag":62,"props":602,"children":603},{"style":75},[604],{"type":36,"value":605},"},\n",{"type":31,"tag":62,"props":607,"children":608},{"class":64,"line":162},[609,614,618],{"type":31,"tag":62,"props":610,"children":611},{"style":75},[612],{"type":36,"value":613},"  cluster_by",{"type":31,"tag":62,"props":615,"children":616},{"style":69},[617],{"type":36,"value":519},{"type":31,"tag":62,"props":619,"children":620},{"style":75},[621],{"type":36,"value":622},"['cohort_date', 'user_id']\n",{"type":31,"tag":62,"props":624,"children":625},{"class":64,"line":179},[626],{"type":31,"tag":62,"props":627,"children":628},{"style":75},[629],{"type":36,"value":630},") }}\n",{"type":31,"tag":62,"props":632,"children":633},{"class":64,"line":192},[634],{"type":31,"tag":62,"props":635,"children":637},{"emptyLinePlaceholder":636},true,[638],{"type":36,"value":639},"\n",{"type":31,"tag":62,"props":641,"children":642},{"class":64,"line":25},[643],{"type":31,"tag":62,"props":644,"children":645},{"style":69},[646],{"type":36,"value":647},"SELECT\n",{"type":31,"tag":62,"props":649,"children":650},{"class":64,"line":279},[651],{"type":31,"tag":62,"props":652,"children":653},{"style":75},[654],{"type":36,"value":655},"  user_id,\n",{"type":31,"tag":62,"props":657,"children":658},{"class":64,"line":325},[659,664,668,672],{"type":31,"tag":62,"props":660,"children":661},{"style":105},[662],{"type":36,"value":663},"  MIN",{"type":31,"tag":62,"props":665,"children":666},{"style":75},[667],{"type":36,"value":113},{"type":31,"tag":62,"props":669,"children":670},{"style":69},[671],{"type":36,"value":83},{"type":31,"tag":62,"props":673,"children":674},{"style":75},[675],{"type":36,"value":676}," cohort_date,\n",{"type":31,"tag":62,"props":678,"children":679},{"class":64,"line":338},[680,685,689,694,698,702],{"type":31,"tag":62,"props":681,"children":682},{"style":105},[683],{"type":36,"value":684},"  COUNT",{"type":31,"tag":62,"props":686,"children":687},{"style":75},[688],{"type":36,"value":290},{"type":31,"tag":62,"props":690,"children":691},{"style":69},[692],{"type":36,"value":693},"*",{"type":31,"tag":62,"props":695,"children":696},{"style":75},[697],{"type":36,"value":267},{"type":31,"tag":62,"props":699,"children":700},{"style":69},[701],{"type":36,"value":83},{"type":31,"tag":62,"props":703,"children":704},{"style":75},[705],{"type":36,"value":706}," first_day_events\n",{"type":31,"tag":62,"props":708,"children":709},{"class":64,"line":362},[710,715,720,725,729,734],{"type":31,"tag":62,"props":711,"children":712},{"style":69},[713],{"type":36,"value":714},"FROM",{"type":31,"tag":62,"props":716,"children":717},{"style":75},[718],{"type":36,"value":719}," {{ source(",{"type":31,"tag":62,"props":721,"children":722},{"style":522},[723],{"type":36,"value":724},"'raw'",{"type":31,"tag":62,"props":726,"children":727},{"style":75},[728],{"type":36,"value":240},{"type":31,"tag":62,"props":730,"children":731},{"style":522},[732],{"type":36,"value":733},"'events'",{"type":31,"tag":62,"props":735,"children":736},{"style":75},[737],{"type":36,"value":630},{"type":31,"tag":62,"props":739,"children":740},{"class":64,"line":384},[741,746,751],{"type":31,"tag":62,"props":742,"children":743},{"style":75},[744],{"type":36,"value":745},"{% ",{"type":31,"tag":62,"props":747,"children":748},{"style":69},[749],{"type":36,"value":750},"if",{"type":31,"tag":62,"props":752,"children":753},{"style":75},[754],{"type":36,"value":755}," is_incremental() %}\n",{"type":31,"tag":62,"props":757,"children":758},{"class":64,"line":393},[759,764,769,774,779,783,788,793,797],{"type":31,"tag":62,"props":760,"children":761},{"style":69},[762],{"type":36,"value":763},"WHERE",{"type":31,"tag":62,"props":765,"children":766},{"style":75},[767],{"type":36,"value":768}," event_date ",{"type":31,"tag":62,"props":770,"children":771},{"style":69},[772],{"type":36,"value":773},">=",{"type":31,"tag":62,"props":775,"children":776},{"style":75},[777],{"type":36,"value":778}," (",{"type":31,"tag":62,"props":780,"children":781},{"style":69},[782],{"type":36,"value":399},{"type":31,"tag":62,"props":784,"children":785},{"style":105},[786],{"type":36,"value":787}," MAX",{"type":31,"tag":62,"props":789,"children":790},{"style":75},[791],{"type":36,"value":792},"(cohort_date) ",{"type":31,"tag":62,"props":794,"children":795},{"style":69},[796],{"type":36,"value":714},{"type":31,"tag":62,"props":798,"children":799},{"style":75},[800],{"type":36,"value":801}," {{ this }})\n",{"type":31,"tag":62,"props":803,"children":805},{"class":64,"line":804},16,[806,811,816,821,826,830,834,838,842],{"type":31,"tag":62,"props":807,"children":808},{"style":69},[809],{"type":36,"value":810},"  AND",{"type":31,"tag":62,"props":812,"children":813},{"style":75},[814],{"type":36,"value":815}," user_id ",{"type":31,"tag":62,"props":817,"children":818},{"style":69},[819],{"type":36,"value":820},"NOT",{"type":31,"tag":62,"props":822,"children":823},{"style":69},[824],{"type":36,"value":825}," IN",{"type":31,"tag":62,"props":827,"children":828},{"style":75},[829],{"type":36,"value":778},{"type":31,"tag":62,"props":831,"children":832},{"style":69},[833],{"type":36,"value":399},{"type":31,"tag":62,"props":835,"children":836},{"style":75},[837],{"type":36,"value":815},{"type":31,"tag":62,"props":839,"children":840},{"style":69},[841],{"type":36,"value":714},{"type":31,"tag":62,"props":843,"children":844},{"style":75},[845],{"type":36,"value":801},{"type":31,"tag":62,"props":847,"children":849},{"class":64,"line":848},17,[850],{"type":31,"tag":62,"props":851,"children":852},{"style":75},[853],{"type":36,"value":854},"{% endif %}\n",{"type":31,"tag":62,"props":856,"children":858},{"class":64,"line":857},18,[859,864],{"type":31,"tag":62,"props":860,"children":861},{"style":69},[862],{"type":36,"value":863},"GROUP BY",{"type":31,"tag":62,"props":865,"children":866},{"style":75},[867],{"type":36,"value":150},{"type":31,"tag":32,"props":869,"children":870},{},[871,873,878],{"type":36,"value":872},"Ce modèle scanne tout l'historique lors de la première exécution (coût ponctuel), puis lors des exécutions quotidiennes suivantes, ajoute seulement les nouveaux utilisateurs du jour précédent. Avec le partitioning par ",{"type":31,"tag":58,"props":874,"children":876},{"className":875},[],[877],{"type":36,"value":208},{"type":36,"value":879},", BigQuery ne touche pas aux anciennes partitions — le coût de la requête reste proportionnel au volume d'événements quotidiens (10M événements nouveaux → ~50 Mo de scan).",{"type":31,"tag":32,"props":881,"children":882},{},[883,885,890,892,898],{"type":36,"value":884},"Le clustering par ",{"type":31,"tag":58,"props":886,"children":888},{"className":887},[],[889],{"type":36,"value":309},{"type":36,"value":891}," améliore les performances de jointure. Lorsque les requêtes de rétention en aval joignent ",{"type":31,"tag":58,"props":893,"children":895},{"className":894},[],[896],{"type":36,"value":897},"user_cohort_base",{"type":36,"value":899},", BigQuery effectue une recherche binaire dans les micro-partitions — vous lisez uniquement les blocs de cluster pertinents au lieu de 5 milliards de lignes.",{"type":31,"tag":901,"props":902,"children":904},"h3",{"id":903},"stratégie-de-partitioning-date-ou-cohort",[905],{"type":36,"value":906},"Stratégie de Partitioning : Date ou Cohort ?",{"type":31,"tag":32,"props":908,"children":909},{},[910,912,917,919,924,926,931,933,938],{"type":36,"value":911},"Si vous avez partitionné la table events par ",{"type":31,"tag":58,"props":913,"children":915},{"className":914},[],[916],{"type":36,"value":235},{"type":36,"value":918},", vous devez partitionner la base cohort par ",{"type":31,"tag":58,"props":920,"children":922},{"className":921},[],[923],{"type":36,"value":208},{"type":36,"value":925},". C'est essentiel. Pourquoi ? Les requêtes de rétention posent des questions « cross-period » comme « quelle est la rétention de la cohorte de janvier en février ? ». Une partition ",{"type":31,"tag":58,"props":927,"children":929},{"className":928},[],[930],{"type":36,"value":235},{"type":36,"value":932}," ne peut pas faire l'élagage ici. Une partition ",{"type":31,"tag":58,"props":934,"children":936},{"className":935},[],[937],{"type":36,"value":208},{"type":36,"value":939}," peut — « cohort de janvier » signifie lire seulement la partition de janvier, soit 1 jour de données au lieu de 30.",{"type":31,"tag":32,"props":941,"children":942},{},[943,945,951],{"type":36,"value":944},"Attention cependant : le nombre de partitions ne doit pas dépasser 4 000 (limite BigQuery). 10 ans de données = 3 650 partitions — vous êtes à la limite. Si la granularité cohort par semaine ou mois suffît, utilisez ",{"type":31,"tag":58,"props":946,"children":948},{"className":947},[],[949],{"type":36,"value":950},"DATE_TRUNC(cohort_date, WEEK)",{"type":36,"value":952}," pour le partitioning.",{"type":31,"tag":39,"props":954,"children":956},{"id":955},"cube-de-rétention-pré-agrégé-réduire-le-coût-par-100x",[957],{"type":36,"value":958},"Cube de Rétention Pré-Agrégé : Réduire le Coût par 100x",{"type":31,"tag":32,"props":960,"children":961},{},[962,964,969],{"type":36,"value":963},"Vous avez maintenant ",{"type":31,"tag":58,"props":965,"children":967},{"className":966},[],[968],{"type":36,"value":897},{"type":36,"value":970},", mais chaque requête de rétention jointe encore à la table events. L'étape suivante consiste à pré-calculer les métriques de rétention quotidiennes et les écrire dans une table matérialisée :",{"type":31,"tag":51,"props":972,"children":974},{"className":53,"code":973,"language":55,"meta":15,"style":15},"-- models\u002Fcohorts\u002Fdaily_retention_cube.sql\n{{ config(\n  materialized='incremental',\n  unique_key=['cohort_date', 'day_offset'],\n  partition_by={'field': 'cohort_date', 'data_type': 'date'}\n) }}\n\nWITH cohort_activity AS (\n  SELECT\n    c.cohort_date,\n    DATE_DIFF(e.event_date, c.cohort_date, DAY) AS day_offset,\n    COUNT(DISTINCT e.user_id) AS active_users\n  FROM {{ ref('user_cohort_base') }} c\n  JOIN {{ source('raw', 'events') }} e USING(user_id)\n  {% if is_incremental() %}\n  WHERE e.event_date >= CURRENT_DATE() - 1\n  {% endif %}\n  GROUP BY 1, 2\n)\nSELECT\n  cohort_date,\n  day_offset,\n  active_users,\n  active_users \u002F FIRST_VALUE(active_users) OVER (\n    PARTITION BY cohort_date ORDER BY day_offset\n  ) AS retention_rate\nFROM cohort_activity\n",[975],{"type":31,"tag":58,"props":976,"children":977},{"__ignoreMap":15},[978,986,993,1012,1028,1076,1083,1090,1110,1118,1138,1194,1233,1255,1291,1307,1347,1355,1374,1382,1390,1399,1408,1417,1450,1479,1497],{"type":31,"tag":62,"props":979,"children":980},{"class":64,"line":65},[981],{"type":31,"tag":62,"props":982,"children":983},{"style":495},[984],{"type":36,"value":985},"-- models\u002Fcohorts\u002Fdaily_retention_cube.sql\n",{"type":31,"tag":62,"props":987,"children":988},{"class":64,"line":91},[989],{"type":31,"tag":62,"props":990,"children":991},{"style":75},[992],{"type":36,"value":506},{"type":31,"tag":62,"props":994,"children":995},{"class":64,"line":125},[996,1000,1004,1008],{"type":31,"tag":62,"props":997,"children":998},{"style":75},[999],{"type":36,"value":514},{"type":31,"tag":62,"props":1001,"children":1002},{"style":69},[1003],{"type":36,"value":519},{"type":31,"tag":62,"props":1005,"children":1006},{"style":522},[1007],{"type":36,"value":525},{"type":31,"tag":62,"props":1009,"children":1010},{"style":75},[1011],{"type":36,"value":213},{"type":31,"tag":62,"props":1013,"children":1014},{"class":64,"line":139},[1015,1019,1023],{"type":31,"tag":62,"props":1016,"children":1017},{"style":75},[1018],{"type":36,"value":537},{"type":31,"tag":62,"props":1020,"children":1021},{"style":69},[1022],{"type":36,"value":519},{"type":31,"tag":62,"props":1024,"children":1025},{"style":75},[1026],{"type":36,"value":1027},"['cohort_date', 'day_offset'],\n",{"type":31,"tag":62,"props":1029,"children":1030},{"class":64,"line":153},[1031,1035,1039,1043,1047,1051,1055,1059,1063,1067,1071],{"type":31,"tag":62,"props":1032,"children":1033},{"style":75},[1034],{"type":36,"value":558},{"type":31,"tag":62,"props":1036,"children":1037},{"style":69},[1038],{"type":36,"value":519},{"type":31,"tag":62,"props":1040,"children":1041},{"style":75},[1042],{"type":36,"value":567},{"type":31,"tag":62,"props":1044,"children":1045},{"style":522},[1046],{"type":36,"value":572},{"type":31,"tag":62,"props":1048,"children":1049},{"style":75},[1050],{"type":36,"value":577},{"type":31,"tag":62,"props":1052,"children":1053},{"style":522},[1054],{"type":36,"value":582},{"type":31,"tag":62,"props":1056,"children":1057},{"style":75},[1058],{"type":36,"value":240},{"type":31,"tag":62,"props":1060,"children":1061},{"style":522},[1062],{"type":36,"value":591},{"type":31,"tag":62,"props":1064,"children":1065},{"style":75},[1066],{"type":36,"value":577},{"type":31,"tag":62,"props":1068,"children":1069},{"style":522},[1070],{"type":36,"value":600},{"type":31,"tag":62,"props":1072,"children":1073},{"style":75},[1074],{"type":36,"value":1075},"}\n",{"type":31,"tag":62,"props":1077,"children":1078},{"class":64,"line":162},[1079],{"type":31,"tag":62,"props":1080,"children":1081},{"style":75},[1082],{"type":36,"value":630},{"type":31,"tag":62,"props":1084,"children":1085},{"class":64,"line":179},[1086],{"type":31,"tag":62,"props":1087,"children":1088},{"emptyLinePlaceholder":636},[1089],{"type":36,"value":639},{"type":31,"tag":62,"props":1091,"children":1092},{"class":64,"line":192},[1093,1097,1102,1106],{"type":31,"tag":62,"props":1094,"children":1095},{"style":69},[1096],{"type":36,"value":72},{"type":31,"tag":62,"props":1098,"children":1099},{"style":75},[1100],{"type":36,"value":1101}," cohort_activity ",{"type":31,"tag":62,"props":1103,"children":1104},{"style":69},[1105],{"type":36,"value":83},{"type":31,"tag":62,"props":1107,"children":1108},{"style":75},[1109],{"type":36,"value":88},{"type":31,"tag":62,"props":1111,"children":1112},{"class":64,"line":25},[1113],{"type":31,"tag":62,"props":1114,"children":1115},{"style":69},[1116],{"type":36,"value":1117},"  SELECT\n",{"type":31,"tag":62,"props":1119,"children":1120},{"class":64,"line":279},[1121,1126,1130,1134],{"type":31,"tag":62,"props":1122,"children":1123},{"style":105},[1124],{"type":36,"value":1125},"    c",{"type":31,"tag":62,"props":1127,"children":1128},{"style":75},[1129],{"type":36,"value":203},{"type":31,"tag":62,"props":1131,"children":1132},{"style":105},[1133],{"type":36,"value":208},{"type":31,"tag":62,"props":1135,"children":1136},{"style":75},[1137],{"type":36,"value":213},{"type":31,"tag":62,"props":1139,"children":1140},{"class":64,"line":325},[1141,1145,1149,1153,1157,1161,1166,1170,1174,1178,1182,1186,1190],{"type":31,"tag":62,"props":1142,"children":1143},{"style":75},[1144],{"type":36,"value":221},{"type":31,"tag":62,"props":1146,"children":1147},{"style":105},[1148],{"type":36,"value":226},{"type":31,"tag":62,"props":1150,"children":1151},{"style":75},[1152],{"type":36,"value":203},{"type":31,"tag":62,"props":1154,"children":1155},{"style":105},[1156],{"type":36,"value":235},{"type":31,"tag":62,"props":1158,"children":1159},{"style":75},[1160],{"type":36,"value":240},{"type":31,"tag":62,"props":1162,"children":1163},{"style":105},[1164],{"type":36,"value":1165},"c",{"type":31,"tag":62,"props":1167,"children":1168},{"style":75},[1169],{"type":36,"value":203},{"type":31,"tag":62,"props":1171,"children":1172},{"style":105},[1173],{"type":36,"value":208},{"type":31,"tag":62,"props":1175,"children":1176},{"style":75},[1177],{"type":36,"value":240},{"type":31,"tag":62,"props":1179,"children":1180},{"style":69},[1181],{"type":36,"value":262},{"type":31,"tag":62,"props":1183,"children":1184},{"style":75},[1185],{"type":36,"value":267},{"type":31,"tag":62,"props":1187,"children":1188},{"style":69},[1189],{"type":36,"value":83},{"type":31,"tag":62,"props":1191,"children":1192},{"style":75},[1193],{"type":36,"value":276},{"type":31,"tag":62,"props":1195,"children":1196},{"class":64,"line":338},[1197,1201,1205,1209,1213,1217,1221,1225,1229],{"type":31,"tag":62,"props":1198,"children":1199},{"style":105},[1200],{"type":36,"value":285},{"type":31,"tag":62,"props":1202,"children":1203},{"style":75},[1204],{"type":36,"value":290},{"type":31,"tag":62,"props":1206,"children":1207},{"style":69},[1208],{"type":36,"value":295},{"type":31,"tag":62,"props":1210,"children":1211},{"style":105},[1212],{"type":36,"value":300},{"type":31,"tag":62,"props":1214,"children":1215},{"style":75},[1216],{"type":36,"value":203},{"type":31,"tag":62,"props":1218,"children":1219},{"style":105},[1220],{"type":36,"value":309},{"type":31,"tag":62,"props":1222,"children":1223},{"style":75},[1224],{"type":36,"value":267},{"type":31,"tag":62,"props":1226,"children":1227},{"style":69},[1228],{"type":36,"value":83},{"type":31,"tag":62,"props":1230,"children":1231},{"style":75},[1232],{"type":36,"value":322},{"type":31,"tag":62,"props":1234,"children":1235},{"class":64,"line":362},[1236,1240,1245,1250],{"type":31,"tag":62,"props":1237,"children":1238},{"style":69},[1239],{"type":36,"value":131},{"type":31,"tag":62,"props":1241,"children":1242},{"style":75},[1243],{"type":36,"value":1244}," {{ ref(",{"type":31,"tag":62,"props":1246,"children":1247},{"style":522},[1248],{"type":36,"value":1249},"'user_cohort_base'",{"type":31,"tag":62,"props":1251,"children":1252},{"style":75},[1253],{"type":36,"value":1254},") }} c\n",{"type":31,"tag":62,"props":1256,"children":1257},{"class":64,"line":384},[1258,1262,1266,1270,1274,1278,1283,1287],{"type":31,"tag":62,"props":1259,"children":1260},{"style":69},[1261],{"type":36,"value":344},{"type":31,"tag":62,"props":1263,"children":1264},{"style":75},[1265],{"type":36,"value":719},{"type":31,"tag":62,"props":1267,"children":1268},{"style":522},[1269],{"type":36,"value":724},{"type":31,"tag":62,"props":1271,"children":1272},{"style":75},[1273],{"type":36,"value":240},{"type":31,"tag":62,"props":1275,"children":1276},{"style":522},[1277],{"type":36,"value":733},{"type":31,"tag":62,"props":1279,"children":1280},{"style":75},[1281],{"type":36,"value":1282},") }} e ",{"type":31,"tag":62,"props":1284,"children":1285},{"style":69},[1286],{"type":36,"value":354},{"type":31,"tag":62,"props":1288,"children":1289},{"style":75},[1290],{"type":36,"value":359},{"type":31,"tag":62,"props":1292,"children":1293},{"class":64,"line":393},[1294,1299,1303],{"type":31,"tag":62,"props":1295,"children":1296},{"style":75},[1297],{"type":36,"value":1298},"  {% ",{"type":31,"tag":62,"props":1300,"children":1301},{"style":69},[1302],{"type":36,"value":750},{"type":31,"tag":62,"props":1304,"children":1305},{"style":75},[1306],{"type":36,"value":755},{"type":31,"tag":62,"props":1308,"children":1309},{"class":64,"line":804},[1310,1315,1319,1323,1327,1332,1337,1342],{"type":31,"tag":62,"props":1311,"children":1312},{"style":69},[1313],{"type":36,"value":1314},"  WHERE",{"type":31,"tag":62,"props":1316,"children":1317},{"style":105},[1318],{"type":36,"value":300},{"type":31,"tag":62,"props":1320,"children":1321},{"style":75},[1322],{"type":36,"value":203},{"type":31,"tag":62,"props":1324,"children":1325},{"style":105},[1326],{"type":36,"value":235},{"type":31,"tag":62,"props":1328,"children":1329},{"style":69},[1330],{"type":36,"value":1331}," >=",{"type":31,"tag":62,"props":1333,"children":1334},{"style":75},[1335],{"type":36,"value":1336}," CURRENT_DATE() ",{"type":31,"tag":62,"props":1338,"children":1339},{"style":69},[1340],{"type":36,"value":1341},"-",{"type":31,"tag":62,"props":1343,"children":1344},{"style":105},[1345],{"type":36,"value":1346}," 1\n",{"type":31,"tag":62,"props":1348,"children":1349},{"class":64,"line":848},[1350],{"type":31,"tag":62,"props":1351,"children":1352},{"style":75},[1353],{"type":36,"value":1354},"  {% endif %}\n",{"type":31,"tag":62,"props":1356,"children":1357},{"class":64,"line":857},[1358,1362,1366,1370],{"type":31,"tag":62,"props":1359,"children":1360},{"style":69},[1361],{"type":36,"value":145},{"type":31,"tag":62,"props":1363,"children":1364},{"style":105},[1365],{"type":36,"value":372},{"type":31,"tag":62,"props":1367,"children":1368},{"style":75},[1369],{"type":36,"value":240},{"type":31,"tag":62,"props":1371,"children":1372},{"style":105},[1373],{"type":36,"value":381},{"type":31,"tag":62,"props":1375,"children":1377},{"class":64,"line":1376},19,[1378],{"type":31,"tag":62,"props":1379,"children":1380},{"style":75},[1381],{"type":36,"value":390},{"type":31,"tag":62,"props":1383,"children":1385},{"class":64,"line":1384},20,[1386],{"type":31,"tag":62,"props":1387,"children":1388},{"style":69},[1389],{"type":36,"value":647},{"type":31,"tag":62,"props":1391,"children":1393},{"class":64,"line":1392},21,[1394],{"type":31,"tag":62,"props":1395,"children":1396},{"style":75},[1397],{"type":36,"value":1398},"  cohort_date,\n",{"type":31,"tag":62,"props":1400,"children":1402},{"class":64,"line":1401},22,[1403],{"type":31,"tag":62,"props":1404,"children":1405},{"style":75},[1406],{"type":36,"value":1407},"  day_offset,\n",{"type":31,"tag":62,"props":1409,"children":1411},{"class":64,"line":1410},23,[1412],{"type":31,"tag":62,"props":1413,"children":1414},{"style":75},[1415],{"type":36,"value":1416},"  active_users,\n",{"type":31,"tag":62,"props":1418,"children":1420},{"class":64,"line":1419},24,[1421,1426,1431,1436,1441,1446],{"type":31,"tag":62,"props":1422,"children":1423},{"style":75},[1424],{"type":36,"value":1425},"  active_users ",{"type":31,"tag":62,"props":1427,"children":1428},{"style":69},[1429],{"type":36,"value":1430},"\u002F",{"type":31,"tag":62,"props":1432,"children":1433},{"style":105},[1434],{"type":36,"value":1435}," FIRST_VALUE",{"type":31,"tag":62,"props":1437,"children":1438},{"style":75},[1439],{"type":36,"value":1440},"(active_users) ",{"type":31,"tag":62,"props":1442,"children":1443},{"style":69},[1444],{"type":36,"value":1445},"OVER",{"type":31,"tag":62,"props":1447,"children":1448},{"style":75},[1449],{"type":36,"value":88},{"type":31,"tag":62,"props":1451,"children":1453},{"class":64,"line":1452},25,[1454,1459,1464,1469,1474],{"type":31,"tag":62,"props":1455,"children":1456},{"style":69},[1457],{"type":36,"value":1458},"    PARTITION",{"type":31,"tag":62,"props":1460,"children":1461},{"style":69},[1462],{"type":36,"value":1463}," BY",{"type":31,"tag":62,"props":1465,"children":1466},{"style":75},[1467],{"type":36,"value":1468}," cohort_date ",{"type":31,"tag":62,"props":1470,"children":1471},{"style":69},[1472],{"type":36,"value":1473},"ORDER BY",{"type":31,"tag":62,"props":1475,"children":1476},{"style":75},[1477],{"type":36,"value":1478}," day_offset\n",{"type":31,"tag":62,"props":1480,"children":1482},{"class":64,"line":1481},26,[1483,1488,1492],{"type":31,"tag":62,"props":1484,"children":1485},{"style":75},[1486],{"type":36,"value":1487},"  ) ",{"type":31,"tag":62,"props":1489,"children":1490},{"style":69},[1491],{"type":36,"value":83},{"type":31,"tag":62,"props":1493,"children":1494},{"style":75},[1495],{"type":36,"value":1496}," retention_rate\n",{"type":31,"tag":62,"props":1498,"children":1500},{"class":64,"line":1499},27,[1501,1505],{"type":31,"tag":62,"props":1502,"children":1503},{"style":69},[1504],{"type":36,"value":714},{"type":31,"tag":62,"props":1506,"children":1507},{"style":75},[1508],{"type":36,"value":1509}," cohort_activity\n",{"type":31,"tag":32,"props":1511,"children":1512},{},[1513,1515,1520,1522,1527,1529,1534],{"type":36,"value":1514},"Cette table s'exécute chaque jour et ajoute seulement la nouvelle activité du jour précédent. Avec le partitioning par ",{"type":31,"tag":58,"props":1516,"children":1518},{"className":1517},[],[1519],{"type":36,"value":208},{"type":36,"value":1521},", les partitions des anciennes cohorts restent inchangées. Résultat : au lieu de ",{"type":31,"tag":424,"props":1523,"children":1524},{},[1525],{"type":36,"value":1526},"5 milliards de lignes d'events",{"type":36,"value":1528},", vous lisez un ",{"type":31,"tag":424,"props":1530,"children":1531},{},[1532],{"type":36,"value":1533},"cube de 45 000 lignes",{"type":36,"value":1535}," (500 cohorts × 90 jours). Les requêtes du tableau de bord lisent directement ce cube — le volume de scan diminue de 100 000x, la latence passe de 45 secondes à 50 millisecondes.",{"type":31,"tag":901,"props":1537,"children":1539},{"id":1538},"stratégie-window-function-calcul-du-taux-de-rétention",[1540],{"type":36,"value":1541},"Stratégie Window Function : Calcul du Taux de Rétention",{"type":31,"tag":32,"props":1543,"children":1544},{},[1545,1547,1553],{"type":36,"value":1546},"L'expression ",{"type":31,"tag":58,"props":1548,"children":1550},{"className":1549},[],[1551],{"type":36,"value":1552},"FIRST_VALUE(active_users) OVER (PARTITION BY cohort_date ORDER BY day_offset)",{"type":36,"value":1554}," transporte le nombre d'utilisateurs D0 à chaque ligne. De cette manière, le calcul du taux de rétention se fait au temps d'écriture, pas au temps de requête. Vous pourriez aussi chercher D0 via une jointure séparée, mais la window function dans BigQuery optimise l'utilisation des slots (lecture séquentielle au sein de la partition).",{"type":31,"tag":32,"props":1556,"children":1557},{},[1558,1560,1565,1567,1572],{"type":36,"value":1559},"Important : la clause ",{"type":31,"tag":58,"props":1561,"children":1563},{"className":1562},[],[1564],{"type":36,"value":1445},{"type":36,"value":1566}," ne casse pas l'élagage de partition car la partition physique (",{"type":31,"tag":58,"props":1568,"children":1570},{"className":1569},[],[1571],{"type":36,"value":208},{"type":36,"value":1573},") correspond à la partition window. BigQuery traite chaque partition indépendamment — aucun shuffle cross-partition.",{"type":31,"tag":39,"props":1575,"children":1577},{"id":1576},"optimisation-du-coût-des-requêtes-utilisation-des-slots-et-caching",[1578],{"type":36,"value":1579},"Optimisation du Coût des Requêtes : Utilisation des Slots et Caching",{"type":31,"tag":32,"props":1581,"children":1582},{},[1583],{"type":36,"value":1584},"Le modèle de coût BigQuery repose sur les octets scannés (5 dollars\u002FTo). Cependant, pour la latence en production, l'utilisation des slots est plus critique. La stratégie materialized view réduit le coût mais peut toujours créer une contention de slots — notamment si 10 utilisateurs du tableau de bord récupèrent simultanément différents filtres cohort.",{"type":31,"tag":32,"props":1586,"children":1587},{},[1588,1593,1595,1601],{"type":31,"tag":424,"props":1589,"children":1590},{},[1591],{"type":36,"value":1592},"Caching BI-engine :",{"type":36,"value":1594}," BigQuery BI Engine conserve jusqu'à 100 Go de données chaudes en RAM. ",{"type":31,"tag":58,"props":1596,"children":1598},{"className":1597},[],[1599],{"type":36,"value":1600},"daily_retention_cube",{"type":36,"value":1602}," avec 45 000 lignes × 200 octets ≈ 9 Mo tient entièrement dans le cache. Les requêtes suivantes utilisent 0 slot, retournent en \u003C10 millisecondes. Vous activez manuellement BI Engine (BigQuery console → Capacity Management → tier 100 Go = 300 dollars\u002Fmois). Le ROI est fort — 1 000 requêtes\u002Fjour × 0,01 dollar cost slot = 10 dollars\u002Fjour contre 10 dollars\u002Fjour forfaitaire.",{"type":31,"tag":32,"props":1604,"children":1605},{},[1606,1611],{"type":31,"tag":424,"props":1607,"children":1608},{},[1609],{"type":36,"value":1610},"Caching des résultats de requête :",{"type":36,"value":1612}," BigQuery cache les résultats de requête pendant 24 heures. Si votre tableau de bord pose « cohorts des 7 derniers jours » pour chaque utilisateur, le premier hit est en cache, les utilisateurs suivants obtiennent un résultat cached. Le cache miss se produit quand les paramètres changent (plage de dates, filtre de segment). C'est ici que le cube pré-agrégé excelle à nouveau.",{"type":31,"tag":32,"props":1614,"children":1615},{},[1616,1621],{"type":31,"tag":424,"props":1617,"children":1618},{},[1619],{"type":36,"value":1620},"Allocation de slots :",{"type":36,"value":1622}," Si vous envisagez la tarification plate (500 slots = 10 000 dollars\u002Fmois) au lieu de l'on-demand, dédiez un pool de slots à votre pipeline de rétention. Les requêtes utilisateur-facing ne doivent pas rivaliser avec les calculs de rétention pour les slots. Chez Roibase, les requêtes planifiées s'exécutent hors heures de pointe (03h00–05h00), les tableaux de bord utilisateur emploient des slots flex (autoscale 100–500).",{"type":31,"tag":39,"props":1624,"children":1626},{"id":1625},"intégration-de-la-résolution-didentité-cohort-cross-device",[1627],{"type":36,"value":1628},"Intégration de la Résolution d'Identité : Cohort Cross-Device",{"type":31,"tag":32,"props":1630,"children":1631},{},[1632,1634,1639],{"type":36,"value":1633},"L'analyse cohort classique fonctionne sur ",{"type":31,"tag":58,"props":1635,"children":1637},{"className":1636},[],[1638],{"type":36,"value":309},{"type":36,"value":1640}," mais dans un parcours utilisateur cross-device, une même personne peut porter 3 ID différents (web anonyme, app connectée, CRM). Si vous mesurez 15 % de rétention, la vraie rétention est peut-être 22 % — fragmentée par l'ID.",{"type":31,"tag":32,"props":1642,"children":1643},{},[1644,1646,1655,1657,1663,1665,1671,1672,1677,1678,1684,1686,1692],{"type":36,"value":1645},"Dans le cadre de ",{"type":31,"tag":1647,"props":1648,"children":1652},"a",{"href":1649,"rel":1650},"https:\u002F\u002Fwww.roibase.com.tr\u002Ffr\u002Ffirstparty",[1651],"nofollow",[1653],{"type":36,"value":1654},"Données First-Party et Architecture de Mesure",{"type":36,"value":1656},", vous construisez un identity graph : la table ",{"type":31,"tag":58,"props":1658,"children":1660},{"className":1659},[],[1661],{"type":36,"value":1662},"identity_map",{"type":36,"value":1664}," mappe chaque ",{"type":31,"tag":58,"props":1666,"children":1668},{"className":1667},[],[1669],{"type":36,"value":1670},"anonymous_id",{"type":36,"value":240},{"type":31,"tag":58,"props":1673,"children":1675},{"className":1674},[],[1676],{"type":36,"value":309},{"type":36,"value":240},{"type":31,"tag":58,"props":1679,"children":1681},{"className":1680},[],[1682],{"type":36,"value":1683},"crm_id",{"type":36,"value":1685}," à un ",{"type":31,"tag":58,"props":1687,"children":1689},{"className":1688},[],[1690],{"type":36,"value":1691},"person_id",{"type":36,"value":1693}," canonique. Enrichissez votre modèle de base cohort avec ce graphe :",{"type":31,"tag":51,"props":1695,"children":1697},{"className":53,"code":1696,"language":55,"meta":15,"style":15},"WITH resolved_events AS (\n  SELECT\n    COALESCE(i.person_id, e.user_id) AS person_id,\n    e.event_date\n  FROM {{ source('raw', 'events') }} e\n  LEFT JOIN {{ ref('identity_map') }} i ON e.user_id = i.user_id\n)\nSELECT person_id, MIN(event_date) AS cohort_date\nFROM resolved_events\nGROUP BY person_id\n",[1698],{"type":31,"tag":58,"props":1699,"children":1700},{"__ignoreMap":15},[1701,1721,1728,1782,1799,1827,1885,1892,1920,1932],{"type":31,"tag":62,"props":1702,"children":1703},{"class":64,"line":65},[1704,1708,1713,1717],{"type":31,"tag":62,"props":1705,"children":1706},{"style":69},[1707],{"type":36,"value":72},{"type":31,"tag":62,"props":1709,"children":1710},{"style":75},[1711],{"type":36,"value":1712}," resolved_events ",{"type":31,"tag":62,"props":1714,"children":1715},{"style":69},[1716],{"type":36,"value":83},{"type":31,"tag":62,"props":1718,"children":1719},{"style":75},[1720],{"type":36,"value":88},{"type":31,"tag":62,"props":1722,"children":1723},{"class":64,"line":91},[1724],{"type":31,"tag":62,"props":1725,"children":1726},{"style":69},[1727],{"type":36,"value":1117},{"type":31,"tag":62,"props":1729,"children":1730},{"class":64,"line":125},[1731,1736,1740,1745,1749,1753,1757,1761,1765,1769,1773,1777],{"type":31,"tag":62,"props":1732,"children":1733},{"style":105},[1734],{"type":36,"value":1735},"    COALESCE",{"type":31,"tag":62,"props":1737,"children":1738},{"style":75},[1739],{"type":36,"value":290},{"type":31,"tag":62,"props":1741,"children":1742},{"style":105},[1743],{"type":36,"value":1744},"i",{"type":31,"tag":62,"props":1746,"children":1747},{"style":75},[1748],{"type":36,"value":203},{"type":31,"tag":62,"props":1750,"children":1751},{"style":105},[1752],{"type":36,"value":1691},{"type":31,"tag":62,"props":1754,"children":1755},{"style":75},[1756],{"type":36,"value":240},{"type":31,"tag":62,"props":1758,"children":1759},{"style":105},[1760],{"type":36,"value":226},{"type":31,"tag":62,"props":1762,"children":1763},{"style":75},[1764],{"type":36,"value":203},{"type":31,"tag":62,"props":1766,"children":1767},{"style":105},[1768],{"type":36,"value":309},{"type":31,"tag":62,"props":1770,"children":1771},{"style":75},[1772],{"type":36,"value":267},{"type":31,"tag":62,"props":1774,"children":1775},{"style":69},[1776],{"type":36,"value":83},{"type":31,"tag":62,"props":1778,"children":1779},{"style":75},[1780],{"type":36,"value":1781}," person_id,\n",{"type":31,"tag":62,"props":1783,"children":1784},{"class":64,"line":139},[1785,1790,1794],{"type":31,"tag":62,"props":1786,"children":1787},{"style":105},[1788],{"type":36,"value":1789},"    e",{"type":31,"tag":62,"props":1791,"children":1792},{"style":75},[1793],{"type":36,"value":203},{"type":31,"tag":62,"props":1795,"children":1796},{"style":105},[1797],{"type":36,"value":1798},"event_date\n",{"type":31,"tag":62,"props":1800,"children":1801},{"class":64,"line":153},[1802,1806,1810,1814,1818,1822],{"type":31,"tag":62,"props":1803,"children":1804},{"style":69},[1805],{"type":36,"value":131},{"type":31,"tag":62,"props":1807,"children":1808},{"style":75},[1809],{"type":36,"value":719},{"type":31,"tag":62,"props":1811,"children":1812},{"style":522},[1813],{"type":36,"value":724},{"type":31,"tag":62,"props":1815,"children":1816},{"style":75},[1817],{"type":36,"value":240},{"type":31,"tag":62,"props":1819,"children":1820},{"style":522},[1821],{"type":36,"value":733},{"type":31,"tag":62,"props":1823,"children":1824},{"style":75},[1825],{"type":36,"value":1826},") }} e\n",{"type":31,"tag":62,"props":1828,"children":1829},{"class":64,"line":162},[1830,1835,1839,1844,1849,1854,1858,1862,1866,1871,1876,1880],{"type":31,"tag":62,"props":1831,"children":1832},{"style":69},[1833],{"type":36,"value":1834},"  LEFT JOIN",{"type":31,"tag":62,"props":1836,"children":1837},{"style":75},[1838],{"type":36,"value":1244},{"type":31,"tag":62,"props":1840,"children":1841},{"style":522},[1842],{"type":36,"value":1843},"'identity_map'",{"type":31,"tag":62,"props":1845,"children":1846},{"style":75},[1847],{"type":36,"value":1848},") }} i ",{"type":31,"tag":62,"props":1850,"children":1851},{"style":69},[1852],{"type":36,"value":1853},"ON",{"type":31,"tag":62,"props":1855,"children":1856},{"style":105},[1857],{"type":36,"value":300},{"type":31,"tag":62,"props":1859,"children":1860},{"style":75},[1861],{"type":36,"value":203},{"type":31,"tag":62,"props":1863,"children":1864},{"style":105},[1865],{"type":36,"value":309},{"type":31,"tag":62,"props":1867,"children":1868},{"style":69},[1869],{"type":36,"value":1870}," =",{"type":31,"tag":62,"props":1872,"children":1873},{"style":105},[1874],{"type":36,"value":1875}," i",{"type":31,"tag":62,"props":1877,"children":1878},{"style":75},[1879],{"type":36,"value":203},{"type":31,"tag":62,"props":1881,"children":1882},{"style":105},[1883],{"type":36,"value":1884},"user_id\n",{"type":31,"tag":62,"props":1886,"children":1887},{"class":64,"line":179},[1888],{"type":31,"tag":62,"props":1889,"children":1890},{"style":75},[1891],{"type":36,"value":390},{"type":31,"tag":62,"props":1893,"children":1894},{"class":64,"line":192},[1895,1899,1904,1908,1912,1916],{"type":31,"tag":62,"props":1896,"children":1897},{"style":69},[1898],{"type":36,"value":399},{"type":31,"tag":62,"props":1900,"children":1901},{"style":75},[1902],{"type":36,"value":1903}," person_id, ",{"type":31,"tag":62,"props":1905,"children":1906},{"style":105},[1907],{"type":36,"value":108},{"type":31,"tag":62,"props":1909,"children":1910},{"style":75},[1911],{"type":36,"value":113},{"type":31,"tag":62,"props":1913,"children":1914},{"style":69},[1915],{"type":36,"value":83},{"type":31,"tag":62,"props":1917,"children":1918},{"style":75},[1919],{"type":36,"value":122},{"type":31,"tag":62,"props":1921,"children":1922},{"class":64,"line":25},[1923,1927],{"type":31,"tag":62,"props":1924,"children":1925},{"style":69},[1926],{"type":36,"value":714},{"type":31,"tag":62,"props":1928,"children":1929},{"style":75},[1930],{"type":36,"value":1931}," resolved_events\n",{"type":31,"tag":62,"props":1933,"children":1934},{"class":64,"line":279},[1935,1939],{"type":31,"tag":62,"props":1936,"children":1937},{"style":69},[1938],{"type":36,"value":863},{"type":31,"tag":62,"props":1940,"children":1941},{"style":75},[1942],{"type":36,"value":1943}," person_id\n",{"type":31,"tag":32,"props":1945,"children":1946},{},[1947,1949,1954,1956,1961],{"type":36,"value":1948},"Cette jointure peut être coûteuse mais ",{"type":31,"tag":58,"props":1950,"children":1952},{"className":1951},[],[1953],{"type":36,"value":1662},{"type":36,"value":1955}," reçoit des mises à jour incrémentales quotidiennes et dispose de clustering par ",{"type":31,"tag":58,"props":1957,"children":1959},{"className":1958},[],[1960],{"type":36,"value":309},{"type":36,"value":1962}," — BigQuery effectue une hash join, pas de broadcast join overhead. La cohort résultante capture la vraie rétention, et les décisions marketing (réallocation budgétaire, prévision de LTV) reposent sur des données exactes.",{"type":31,"tag":39,"props":1964,"children":1966},{"id":1965},"stratégie-de-refresh-incrémental-backfill-vs-delta-quotidien",[1967],{"type":36,"value":1968},"Stratégie de Refresh Incrémental : Backfill vs Delta Quotidien",{"type":31,"tag":32,"props":1970,"children":1971},{},[1972],{"type":36,"value":1973},"Le risque critique des materialized views : quand les données amont sont corrigées (événement tardif, suppression RGPD), la vue aval devient obsolète. BigQuery n'offre pas de refresh automatique des materialized views — c'est vous qui déclenchez.",{"type":31,"tag":32,"props":1975,"children":1976},{},[1977],{"type":31,"tag":424,"props":1978,"children":1979},{},[1980],{"type":36,"value":1981},"Deux stratégies :",{"type":31,"tag":1983,"props":1984,"children":1985},"ol",{},[1986,1997],{"type":31,"tag":1987,"props":1988,"children":1989},"li",{},[1990,1995],{"type":31,"tag":424,"props":1991,"children":1992},{},[1993],{"type":36,"value":1994},"Delta quotidien :",{"type":36,"value":1996}," Calculez seulement la nouvelle partition chaque jour. Rapide mais ignore les corrections historiques.",{"type":31,"tag":1987,"props":1998,"children":1999},{},[2000,2005],{"type":31,"tag":424,"props":2001,"children":2002},{},[2003],{"type":36,"value":2004},"Backfill glissant :",{"type":36,"value":2006}," Recalculez les 7 derniers jours à chaque exécution. Capture les événements tardifs mais 7x plus de compute.",{"type":31,"tag":32,"props":2008,"children":2009},{},[2010],{"type":36,"value":2011},"Dans la production Roibase, nous employons une approche hybride : delta quotidien + full refresh hebdomadaire. En dbt :",{"type":31,"tag":51,"props":2013,"children":2017},{"className":2014,"code":2015,"language":2016,"meta":15,"style":15},"language-yaml shiki shiki-themes github-dark","# dbt_project.yml\nmodels:\n  cohorts:\n    daily_retention_cube:\n      +full_refresh: \"{{ var('force_backfill', false) }}\"\n","yaml",[2018],{"type":31,"tag":58,"props":2019,"children":2020},{"__ignoreMap":15},[2021,2029,2043,2055,2067],{"type":31,"tag":62,"props":2022,"children":2023},{"class":64,"line":65},[2024],{"type":31,"tag":62,"props":2025,"children":2026},{"style":495},[2027],{"type":36,"value":2028},"# dbt_project.yml\n",{"type":31,"tag":62,"props":2030,"children":2031},{"class":64,"line":91},[2032,2038],{"type":31,"tag":62,"props":2033,"children":2035},{"style":2034},"--shiki-default:#85E89D",[2036],{"type":36,"value":2037},"models",{"type":31,"tag":62,"props":2039,"children":2040},{"style":75},[2041],{"type":36,"value":2042},":\n",{"type":31,"tag":62,"props":2044,"children":2045},{"class":64,"line":125},[2046,2051],{"type":31,"tag":62,"props":2047,"children":2048},{"style":2034},[2049],{"type":36,"value":2050},"  cohorts",{"type":31,"tag":62,"props":2052,"children":2053},{"style":75},[2054],{"type":36,"value":2042},{"type":31,"tag":62,"props":2056,"children":2057},{"class":64,"line":139},[2058,2063],{"type":31,"tag":62,"props":2059,"children":2060},{"style":2034},[2061],{"type":36,"value":2062},"    daily_retention_cube",{"type":31,"tag":62,"props":2064,"children":2065},{"style":75},[2066],{"type":36,"value":2042},{"type":31,"tag":62,"props":2068,"children":2069},{"class":64,"line":153},[2070,2075,2079],{"type":31,"tag":62,"props":2071,"children":2072},{"style":2034},[2073],{"type":36,"value":2074},"      +full_refresh",{"type":31,"tag":62,"props":2076,"children":2077},{"style":75},[2078],{"type":36,"value":577},{"type":31,"tag":62,"props":2080,"children":2081},{"style":522},[2082],{"type":36,"value":2083},"\"{{ var('force_backfill', false) }}\"\n",{"type":31,"tag":32,"props":2085,"children":2086},{},[2087,2089,2095,2097,2103],{"type":36,"value":2088},"L'exécution normale ",{"type":31,"tag":58,"props":2090,"children":2092},{"className":2091},[],[2093],{"type":36,"value":2094},"dbt run --select daily_retention_cube",{"type":36,"value":2096}," (incrémental). Le week-end ",{"type":31,"tag":58,"props":2098,"children":2100},{"className":2099},[],[2101],{"type":36,"value":2102},"dbt run --select daily_retention_cube --vars '{force_backfill: true}'",{"type":36,"value":2104}," (full refresh). Vous maîtrisez le compromis coût-exactitude.",{"type":31,"tag":39,"props":2106,"children":2108},{"id":2107},"benchmark-de-performance-naïf-vs-optimisé",[2109],{"type":36,"value":2110},"Benchmark de Performance : Naïf vs Optimisé",{"type":31,"tag":32,"props":2112,"children":2113},{},[2114],{"type":36,"value":2115},"Dataset production : 10M événements\u002Fjour, 18 mois d'historique, 5,4 milliards de lignes.",{"type":31,"tag":2117,"props":2118,"children":2119},"table",{},[2120,2149],{"type":31,"tag":2121,"props":2122,"children":2123},"thead",{},[2124],{"type":31,"tag":2125,"props":2126,"children":2127},"tr",{},[2128,2134,2139,2144],{"type":31,"tag":2129,"props":2130,"children":2131},"th",{},[2132],{"type":36,"value":2133},"Métrique",{"type":31,"tag":2129,"props":2135,"children":2136},{},[2137],{"type":36,"value":2138},"SQL Naïf",{"type":31,"tag":2129,"props":2140,"children":2141},{},[2142],{"type":36,"value":2143},"Cube Matérialisé",{"type":31,"tag":2129,"props":2145,"children":2146},{},[2147],{"type":36,"value":2148},"Amélioration",{"type":31,"tag":2150,"props":2151,"children":2152},"tbody",{},[2153,2177,2200,2223,2246],{"type":31,"tag":2125,"props":2154,"children":2155},{},[2156,2162,2167,2172],{"type":31,"tag":2157,"props":2158,"children":2159},"td",{},[2160],{"type":36,"value":2161},"Volume de scan (rétention D7)",{"type":31,"tag":2157,"props":2163,"children":2164},{},[2165],{"type":36,"value":2166},"2,1 To",{"type":31,"tag":2157,"props":2168,"children":2169},{},[2170],{"type":36,"value":2171},"18 Mo",{"type":31,"tag":2157,"props":2173,"children":2174},{},[2175],{"type":36,"value":2176},"116x",{"type":31,"tag":2125,"props":2178,"children":2179},{},[2180,2185,2190,2195],{"type":31,"tag":2157,"props":2181,"children":2182},{},[2183],{"type":36,"value":2184},"Latence de requête (p95)",{"type":31,"tag":2157,"props":2186,"children":2187},{},[2188],{"type":36,"value":2189},"42 s",{"type":31,"tag":2157,"props":2191,"children":2192},{},[2193],{"type":36,"value":2194},"0,08 s",{"type":31,"tag":2157,"props":2196,"children":2197},{},[2198],{"type":36,"value":2199},"525x",{"type":31,"tag":2125,"props":2201,"children":2202},{},[2203,2208,2213,2218],{"type":31,"tag":2157,"props":2204,"children":2205},{},[2206],{"type":36,"value":2207},"Coût BigQuery\u002Frequête",{"type":31,"tag":2157,"props":2209,"children":2210},{},[2211],{"type":36,"value":2212},"10,50 $",{"type":31,"tag":2157,"props":2214,"children":2215},{},[2216],{"type":36,"value":2217},"0,01 $",{"type":31,"tag":2157,"props":2219,"children":2220},{},[2221],{"type":36,"value":2222},"1050x",{"type":31,"tag":2125,"props":2224,"children":2225},{},[2226,2231,2236,2241],{"type":31,"tag":2157,"props":2227,"children":2228},{},[2229],{"type":36,"value":2230},"Temps de chargement tableau de bord",{"type":31,"tag":2157,"props":2232,"children":2233},{},[2234],{"type":36,"value":2235},"timeout",{"type":31,"tag":2157,"props":2237,"children":2238},{},[2239],{"type":36,"value":2240},"\u003C1 s",{"type":31,"tag":2157,"props":2242,"children":2243},{},[2244],{"type":36,"value":2245},"—",{"type":31,"tag":2125,"props":2247,"children":2248},{},[2249,2254,2259,2264],{"type":31,"tag":2157,"props":2250,"children":2251},{},[2252],{"type":36,"value":2253},"Utilisation de slots (pic)",{"type":31,"tag":2157,"props":2255,"children":2256},{},[2257],{"type":36,"value":2258},"2 000",{"type":31,"tag":2157,"props":2260,"children":2261},{},[2262],{"type":36,"value":2263},"5",{"type":31,"tag":2157,"props":2265,"children":2266},{},[2267],{"type":36,"value":2268},"400x",{"type":31,"tag":32,"props":2270,"children":2271},{},[2272],{"type":36,"value":2273},"Requête de test : « Courbe de rétention 30 jours de la cohorte janvier 2026 ». La requête naïve scanne la table events 18 fois (une par jour). Le cube matérialisé lit 30 lignes.",{"type":31,"tag":32,"props":2275,"children":2276},{},[2277],{"type":36,"value":2278},"Avec le cache BI-engine activé, la latence passe de 80 ms à 12 ms — utilisation de slots zéro. Lors d'un test avec 50 utilisateurs concurrents sur le tableau de bord, 99,5 % de disponibilité, réponse médiane 18 ms. C'est la performance production — votre équipe marketing fait de la segmentation cohort en temps réel (par exemple, « envoyer une campagne push à ceux avec rétention D3 \u003C20% »).",{"type":31,"tag":32,"props":2280,"children":2281},{},[2282],{"type":36,"value":2283},"L'analyse de rétention est au",{"type":31,"tag":2285,"props":2286,"children":2287},"style",{},[2288],{"type":36,"value":2289},"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":15,"searchDepth":125,"depth":125,"links":2291},[2292,2293,2296,2299,2300,2301,2302],{"id":41,"depth":91,"text":44},{"id":466,"depth":91,"text":469,"children":2294},[2295],{"id":903,"depth":125,"text":906},{"id":955,"depth":91,"text":958,"children":2297},[2298],{"id":1538,"depth":125,"text":1541},{"id":1576,"depth":91,"text":1579},{"id":1625,"depth":91,"text":1628},{"id":1965,"depth":91,"text":1968},{"id":2107,"depth":91,"text":2110},"markdown","content:fr:data:architecture-cohort-table-production-retention.md","content","fr\u002Fdata\u002Farchitecture-cohort-table-production-retention.md","fr\u002Fdata\u002Farchitecture-cohort-table-production-retention","md",1785103541858]