[{"data":1,"prerenderedAt":2338},["ShallowReactive",2],{"article-alternates":3,"article-\u002Fes\u002Fdata\u002Farquitectura-tabla-cohortes":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":7,"_dir":12,"_draft":13,"_partial":13,"_locale":14,"title":15,"description":16,"publishedAt":17,"modifiedAt":17,"category":12,"i18nKey":4,"tags":18,"readingTime":24,"author":25,"body":26,"_type":2332,"_id":2333,"_source":2334,"_file":2335,"_stem":2336,"_extension":2337},"data",false,"","Arquitectura de Tabla de Cohortes: Escalado de Análisis de Retención en Producción","Materialized views, particionamiento e optimización de costos de queries para ejecutar análisis de cohortes en 10M+ eventos diarios con latencia en milisegundos.","2026-06-26",[19,20,21,22,23],"analisis-cohortes","bigquery-optimization","materialized-views","retention-engineering","data-partitioning",8,"Roibase",{"type":27,"children":28,"toc":2319},"root",[29,37,44,49,414,419,465,470,476,489,874,886,906,913,954,975,981,1000,1539,1565,1571,1584,1603,1609,1614,1632,1642,1652,1658,1670,1723,1973,1992,1998,2003,2011,2036,2041,2113,2134,2140,2145,2298,2303,2308,2313],{"type":30,"tag":31,"props":32,"children":33},"element","p",{},[34],{"type":35,"value":36},"text","Si tu dashboard de retención se detiene 45 segundos en cada carga, el problema no es tu definición de cohorte — es tu arquitectura de tabla. Calcular retención D1, D7, D30 sobre 10 millones de eventos diarios en BigQuery puede costar 2TB de escaneo y 10 dólares. O, con la estrategia correcta de particionamiento, materialized views incrementales y pre-agregación, puede reducirse a 200MB y 50 milisegundos. La diferencia está entre lo que funciona en producción y lo que \"funciona pero nadie puede usarlo\".",{"type":30,"tag":38,"props":39,"children":41},"h2",{"id":40},"por-qué-el-análisis-de-cohortes-explota-en-producción",[42],{"type":35,"value":43},"Por Qué el Análisis de Cohortes Explota en Producción",{"type":30,"tag":31,"props":45,"children":46},{},[47],{"type":35,"value":48},"El cálculo de retención es inherentemente un proceso de escaneo completo. Debes encontrar la primera transacción de cada usuario, contar qué hicieron en días posteriores, agrupar por cohorte, calcular porcentajes. El enfoque SQL ingenuo es este:",{"type":30,"tag":50,"props":51,"children":55},"pre",{"className":52,"code":53,"language":54,"meta":14,"style":14},"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",[56],{"type":30,"tag":57,"props":58,"children":59},"code",{"__ignoreMap":14},[60,88,122,136,150,159,176,189,212,276,322,335,359,381,390],{"type":30,"tag":61,"props":62,"children":65},"span",{"class":63,"line":64},"line",1,[66,72,78,83],{"type":30,"tag":61,"props":67,"children":69},{"style":68},"--shiki-default:#F97583",[70],{"type":35,"value":71},"WITH",{"type":30,"tag":61,"props":73,"children":75},{"style":74},"--shiki-default:#E1E4E8",[76],{"type":35,"value":77}," first_events ",{"type":30,"tag":61,"props":79,"children":80},{"style":68},[81],{"type":35,"value":82},"AS",{"type":30,"tag":61,"props":84,"children":85},{"style":74},[86],{"type":35,"value":87}," (\n",{"type":30,"tag":61,"props":89,"children":91},{"class":63,"line":90},2,[92,97,102,108,113,117],{"type":30,"tag":61,"props":93,"children":94},{"style":68},[95],{"type":35,"value":96},"  SELECT",{"type":30,"tag":61,"props":98,"children":99},{"style":74},[100],{"type":35,"value":101}," user_id, ",{"type":30,"tag":61,"props":103,"children":105},{"style":104},"--shiki-default:#79B8FF",[106],{"type":35,"value":107},"MIN",{"type":30,"tag":61,"props":109,"children":110},{"style":74},[111],{"type":35,"value":112},"(event_date) ",{"type":30,"tag":61,"props":114,"children":115},{"style":68},[116],{"type":35,"value":82},{"type":30,"tag":61,"props":118,"children":119},{"style":74},[120],{"type":35,"value":121}," cohort_date\n",{"type":30,"tag":61,"props":123,"children":125},{"class":63,"line":124},3,[126,131],{"type":30,"tag":61,"props":127,"children":128},{"style":68},[129],{"type":35,"value":130},"  FROM",{"type":30,"tag":61,"props":132,"children":133},{"style":74},[134],{"type":35,"value":135}," events\n",{"type":30,"tag":61,"props":137,"children":139},{"class":63,"line":138},4,[140,145],{"type":30,"tag":61,"props":141,"children":142},{"style":68},[143],{"type":35,"value":144},"  GROUP BY",{"type":30,"tag":61,"props":146,"children":147},{"style":74},[148],{"type":35,"value":149}," user_id\n",{"type":30,"tag":61,"props":151,"children":153},{"class":63,"line":152},5,[154],{"type":30,"tag":61,"props":155,"children":156},{"style":74},[157],{"type":35,"value":158},"),\n",{"type":30,"tag":61,"props":160,"children":162},{"class":63,"line":161},6,[163,168,172],{"type":30,"tag":61,"props":164,"children":165},{"style":74},[166],{"type":35,"value":167},"retention_raw ",{"type":30,"tag":61,"props":169,"children":170},{"style":68},[171],{"type":35,"value":82},{"type":30,"tag":61,"props":173,"children":174},{"style":74},[175],{"type":35,"value":87},{"type":30,"tag":61,"props":177,"children":179},{"class":63,"line":178},7,[180,184],{"type":30,"tag":61,"props":181,"children":182},{"style":68},[183],{"type":35,"value":96},{"type":30,"tag":61,"props":185,"children":186},{"style":74},[187],{"type":35,"value":188}," \n",{"type":30,"tag":61,"props":190,"children":191},{"class":63,"line":24},[192,197,202,207],{"type":30,"tag":61,"props":193,"children":194},{"style":104},[195],{"type":35,"value":196},"    f",{"type":30,"tag":61,"props":198,"children":199},{"style":74},[200],{"type":35,"value":201},".",{"type":30,"tag":61,"props":203,"children":204},{"style":104},[205],{"type":35,"value":206},"cohort_date",{"type":30,"tag":61,"props":208,"children":209},{"style":74},[210],{"type":35,"value":211},",\n",{"type":30,"tag":61,"props":213,"children":215},{"class":63,"line":214},9,[216,221,226,230,235,240,245,249,253,257,262,267,271],{"type":30,"tag":61,"props":217,"children":218},{"style":74},[219],{"type":35,"value":220},"    DATE_DIFF(",{"type":30,"tag":61,"props":222,"children":223},{"style":104},[224],{"type":35,"value":225},"e",{"type":30,"tag":61,"props":227,"children":228},{"style":74},[229],{"type":35,"value":201},{"type":30,"tag":61,"props":231,"children":232},{"style":104},[233],{"type":35,"value":234},"event_date",{"type":30,"tag":61,"props":236,"children":237},{"style":74},[238],{"type":35,"value":239},", ",{"type":30,"tag":61,"props":241,"children":242},{"style":104},[243],{"type":35,"value":244},"f",{"type":30,"tag":61,"props":246,"children":247},{"style":74},[248],{"type":35,"value":201},{"type":30,"tag":61,"props":250,"children":251},{"style":104},[252],{"type":35,"value":206},{"type":30,"tag":61,"props":254,"children":255},{"style":74},[256],{"type":35,"value":239},{"type":30,"tag":61,"props":258,"children":259},{"style":68},[260],{"type":35,"value":261},"DAY",{"type":30,"tag":61,"props":263,"children":264},{"style":74},[265],{"type":35,"value":266},") ",{"type":30,"tag":61,"props":268,"children":269},{"style":68},[270],{"type":35,"value":82},{"type":30,"tag":61,"props":272,"children":273},{"style":74},[274],{"type":35,"value":275}," day_offset,\n",{"type":30,"tag":61,"props":277,"children":279},{"class":63,"line":278},10,[280,285,290,295,300,304,309,313,317],{"type":30,"tag":61,"props":281,"children":282},{"style":104},[283],{"type":35,"value":284},"    COUNT",{"type":30,"tag":61,"props":286,"children":287},{"style":74},[288],{"type":35,"value":289},"(",{"type":30,"tag":61,"props":291,"children":292},{"style":68},[293],{"type":35,"value":294},"DISTINCT",{"type":30,"tag":61,"props":296,"children":297},{"style":104},[298],{"type":35,"value":299}," e",{"type":30,"tag":61,"props":301,"children":302},{"style":74},[303],{"type":35,"value":201},{"type":30,"tag":61,"props":305,"children":306},{"style":104},[307],{"type":35,"value":308},"user_id",{"type":30,"tag":61,"props":310,"children":311},{"style":74},[312],{"type":35,"value":266},{"type":30,"tag":61,"props":314,"children":315},{"style":68},[316],{"type":35,"value":82},{"type":30,"tag":61,"props":318,"children":319},{"style":74},[320],{"type":35,"value":321}," active_users\n",{"type":30,"tag":61,"props":323,"children":325},{"class":63,"line":324},11,[326,330],{"type":30,"tag":61,"props":327,"children":328},{"style":68},[329],{"type":35,"value":130},{"type":30,"tag":61,"props":331,"children":332},{"style":74},[333],{"type":35,"value":334}," events e\n",{"type":30,"tag":61,"props":336,"children":338},{"class":63,"line":337},12,[339,344,349,354],{"type":30,"tag":61,"props":340,"children":341},{"style":68},[342],{"type":35,"value":343},"  JOIN",{"type":30,"tag":61,"props":345,"children":346},{"style":74},[347],{"type":35,"value":348}," first_events f ",{"type":30,"tag":61,"props":350,"children":351},{"style":68},[352],{"type":35,"value":353},"USING",{"type":30,"tag":61,"props":355,"children":356},{"style":74},[357],{"type":35,"value":358},"(user_id)\n",{"type":30,"tag":61,"props":360,"children":362},{"class":63,"line":361},13,[363,367,372,376],{"type":30,"tag":61,"props":364,"children":365},{"style":68},[366],{"type":35,"value":144},{"type":30,"tag":61,"props":368,"children":369},{"style":104},[370],{"type":35,"value":371}," 1",{"type":30,"tag":61,"props":373,"children":374},{"style":74},[375],{"type":35,"value":239},{"type":30,"tag":61,"props":377,"children":378},{"style":104},[379],{"type":35,"value":380},"2\n",{"type":30,"tag":61,"props":382,"children":384},{"class":63,"line":383},14,[385],{"type":30,"tag":61,"props":386,"children":387},{"style":74},[388],{"type":35,"value":389},")\n",{"type":30,"tag":61,"props":391,"children":393},{"class":63,"line":392},15,[394,399,404,409],{"type":30,"tag":61,"props":395,"children":396},{"style":68},[397],{"type":35,"value":398},"SELECT",{"type":30,"tag":61,"props":400,"children":401},{"style":68},[402],{"type":35,"value":403}," *",{"type":30,"tag":61,"props":405,"children":406},{"style":68},[407],{"type":35,"value":408}," FROM",{"type":30,"tag":61,"props":410,"children":411},{"style":74},[412],{"type":35,"value":413}," retention_raw;\n",{"type":30,"tag":31,"props":415,"children":416},{},[417],{"type":35,"value":418},"Esta query lee la tabla completa en cada ejecución. 500 días de data × 10M eventos diarios = 5 mil millones de filas. BigQuery consume slots de manera explosiva, el dashboard se congela 40 segundos, la herramienta BI vence. El problema converge en tres puntos:",{"type":30,"tag":31,"props":420,"children":421},{},[422,428,430,435,437,441,446,448,453,455,458,463],{"type":30,"tag":423,"props":424,"children":425},"strong",{},[426],{"type":35,"value":427},"1. Escaneo de tabla completa:",{"type":35,"value":429}," No hay poda de particiones porque el JOIN por ",{"type":30,"tag":57,"props":431,"children":433},{"className":432},[],[434],{"type":35,"value":308},{"type":35,"value":436}," cruza los límites de partición.",{"type":30,"tag":438,"props":439,"children":440},"br",{},[],{"type":30,"tag":423,"props":442,"children":443},{},[444],{"type":35,"value":445},"2. Cálculo repetido:",{"type":35,"value":447}," Ya conoces cada ",{"type":30,"tag":57,"props":449,"children":451},{"className":450},[],[452],{"type":35,"value":206},{"type":35,"value":454},", pero lo recalculas en cada query.",{"type":30,"tag":438,"props":456,"children":457},{},[],{"type":30,"tag":423,"props":459,"children":460},{},[461],{"type":35,"value":462},"3. Overhead de agregación:",{"type":35,"value":464}," De 5 mil millones de filas extraes 500 cohortes × 90 días = 45.000 filas — ratio compute\u002Foutput de 100.000:1.",{"type":30,"tag":31,"props":466,"children":467},{},[468],{"type":35,"value":469},"En producción este enfoque es insostenible. La solución es rediseñar la arquitectura de tabla.",{"type":30,"tag":38,"props":471,"children":473},{"id":472},"base-de-cohortes-materializada-el-paso-incremental",[474],{"type":35,"value":475},"Base de Cohortes Materializada: El Paso Incremental",{"type":30,"tag":31,"props":477,"children":478},{},[479,481,487],{"type":35,"value":480},"La parte costosa del análisis de cohortes es calcular ",{"type":30,"tag":57,"props":482,"children":484},{"className":483},[],[485],{"type":35,"value":486},"MIN(event_date)",{"type":35,"value":488},". Hazlo una vez, escribe el resultado en una tabla snapshot, suma cada día solo los usuarios nuevos. En BigQuery, usamos un modelo incremental dbt en lugar de materialized views:",{"type":30,"tag":50,"props":490,"children":492},{"className":52,"code":491,"language":54,"meta":14,"style":14},"-- 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",[493],{"type":30,"tag":57,"props":494,"children":495},{"__ignoreMap":14},[496,505,513,536,557,612,629,637,646,654,662,683,713,744,762,808,852,861],{"type":30,"tag":61,"props":497,"children":498},{"class":63,"line":64},[499],{"type":30,"tag":61,"props":500,"children":502},{"style":501},"--shiki-default:#6A737D",[503],{"type":35,"value":504},"-- models\u002Fcohorts\u002Fuser_cohort_base.sql\n",{"type":30,"tag":61,"props":506,"children":507},{"class":63,"line":90},[508],{"type":30,"tag":61,"props":509,"children":510},{"style":74},[511],{"type":35,"value":512},"{{ config(\n",{"type":30,"tag":61,"props":514,"children":515},{"class":63,"line":124},[516,521,526,532],{"type":30,"tag":61,"props":517,"children":518},{"style":74},[519],{"type":35,"value":520},"  materialized",{"type":30,"tag":61,"props":522,"children":523},{"style":68},[524],{"type":35,"value":525},"=",{"type":30,"tag":61,"props":527,"children":529},{"style":528},"--shiki-default:#9ECBFF",[530],{"type":35,"value":531},"'incremental'",{"type":30,"tag":61,"props":533,"children":534},{"style":74},[535],{"type":35,"value":211},{"type":30,"tag":61,"props":537,"children":538},{"class":63,"line":138},[539,544,548,553],{"type":30,"tag":61,"props":540,"children":541},{"style":74},[542],{"type":35,"value":543},"  unique_key",{"type":30,"tag":61,"props":545,"children":546},{"style":68},[547],{"type":35,"value":525},{"type":30,"tag":61,"props":549,"children":550},{"style":528},[551],{"type":35,"value":552},"'user_id'",{"type":30,"tag":61,"props":554,"children":555},{"style":74},[556],{"type":35,"value":211},{"type":30,"tag":61,"props":558,"children":559},{"class":63,"line":152},[560,565,569,574,579,584,589,593,598,602,607],{"type":30,"tag":61,"props":561,"children":562},{"style":74},[563],{"type":35,"value":564},"  partition_by",{"type":30,"tag":61,"props":566,"children":567},{"style":68},[568],{"type":35,"value":525},{"type":30,"tag":61,"props":570,"children":571},{"style":74},[572],{"type":35,"value":573},"{",{"type":30,"tag":61,"props":575,"children":576},{"style":528},[577],{"type":35,"value":578},"'field'",{"type":30,"tag":61,"props":580,"children":581},{"style":74},[582],{"type":35,"value":583},": ",{"type":30,"tag":61,"props":585,"children":586},{"style":528},[587],{"type":35,"value":588},"'cohort_date'",{"type":30,"tag":61,"props":590,"children":591},{"style":74},[592],{"type":35,"value":239},{"type":30,"tag":61,"props":594,"children":595},{"style":528},[596],{"type":35,"value":597},"'data_type'",{"type":30,"tag":61,"props":599,"children":600},{"style":74},[601],{"type":35,"value":583},{"type":30,"tag":61,"props":603,"children":604},{"style":528},[605],{"type":35,"value":606},"'date'",{"type":30,"tag":61,"props":608,"children":609},{"style":74},[610],{"type":35,"value":611},"},\n",{"type":30,"tag":61,"props":613,"children":614},{"class":63,"line":161},[615,620,624],{"type":30,"tag":61,"props":616,"children":617},{"style":74},[618],{"type":35,"value":619},"  cluster_by",{"type":30,"tag":61,"props":621,"children":622},{"style":68},[623],{"type":35,"value":525},{"type":30,"tag":61,"props":625,"children":626},{"style":74},[627],{"type":35,"value":628},"['cohort_date', 'user_id']\n",{"type":30,"tag":61,"props":630,"children":631},{"class":63,"line":178},[632],{"type":30,"tag":61,"props":633,"children":634},{"style":74},[635],{"type":35,"value":636},") }}\n",{"type":30,"tag":61,"props":638,"children":639},{"class":63,"line":24},[640],{"type":30,"tag":61,"props":641,"children":643},{"emptyLinePlaceholder":642},true,[644],{"type":35,"value":645},"\n",{"type":30,"tag":61,"props":647,"children":648},{"class":63,"line":214},[649],{"type":30,"tag":61,"props":650,"children":651},{"style":68},[652],{"type":35,"value":653},"SELECT\n",{"type":30,"tag":61,"props":655,"children":656},{"class":63,"line":278},[657],{"type":30,"tag":61,"props":658,"children":659},{"style":74},[660],{"type":35,"value":661},"  user_id,\n",{"type":30,"tag":61,"props":663,"children":664},{"class":63,"line":324},[665,670,674,678],{"type":30,"tag":61,"props":666,"children":667},{"style":104},[668],{"type":35,"value":669},"  MIN",{"type":30,"tag":61,"props":671,"children":672},{"style":74},[673],{"type":35,"value":112},{"type":30,"tag":61,"props":675,"children":676},{"style":68},[677],{"type":35,"value":82},{"type":30,"tag":61,"props":679,"children":680},{"style":74},[681],{"type":35,"value":682}," cohort_date,\n",{"type":30,"tag":61,"props":684,"children":685},{"class":63,"line":337},[686,691,695,700,704,708],{"type":30,"tag":61,"props":687,"children":688},{"style":104},[689],{"type":35,"value":690},"  COUNT",{"type":30,"tag":61,"props":692,"children":693},{"style":74},[694],{"type":35,"value":289},{"type":30,"tag":61,"props":696,"children":697},{"style":68},[698],{"type":35,"value":699},"*",{"type":30,"tag":61,"props":701,"children":702},{"style":74},[703],{"type":35,"value":266},{"type":30,"tag":61,"props":705,"children":706},{"style":68},[707],{"type":35,"value":82},{"type":30,"tag":61,"props":709,"children":710},{"style":74},[711],{"type":35,"value":712}," first_day_events\n",{"type":30,"tag":61,"props":714,"children":715},{"class":63,"line":361},[716,721,726,731,735,740],{"type":30,"tag":61,"props":717,"children":718},{"style":68},[719],{"type":35,"value":720},"FROM",{"type":30,"tag":61,"props":722,"children":723},{"style":74},[724],{"type":35,"value":725}," {{ source(",{"type":30,"tag":61,"props":727,"children":728},{"style":528},[729],{"type":35,"value":730},"'raw'",{"type":30,"tag":61,"props":732,"children":733},{"style":74},[734],{"type":35,"value":239},{"type":30,"tag":61,"props":736,"children":737},{"style":528},[738],{"type":35,"value":739},"'events'",{"type":30,"tag":61,"props":741,"children":742},{"style":74},[743],{"type":35,"value":636},{"type":30,"tag":61,"props":745,"children":746},{"class":63,"line":383},[747,752,757],{"type":30,"tag":61,"props":748,"children":749},{"style":74},[750],{"type":35,"value":751},"{% ",{"type":30,"tag":61,"props":753,"children":754},{"style":68},[755],{"type":35,"value":756},"if",{"type":30,"tag":61,"props":758,"children":759},{"style":74},[760],{"type":35,"value":761}," is_incremental() %}\n",{"type":30,"tag":61,"props":763,"children":764},{"class":63,"line":392},[765,770,775,780,785,789,794,799,803],{"type":30,"tag":61,"props":766,"children":767},{"style":68},[768],{"type":35,"value":769},"WHERE",{"type":30,"tag":61,"props":771,"children":772},{"style":74},[773],{"type":35,"value":774}," event_date ",{"type":30,"tag":61,"props":776,"children":777},{"style":68},[778],{"type":35,"value":779},">=",{"type":30,"tag":61,"props":781,"children":782},{"style":74},[783],{"type":35,"value":784}," (",{"type":30,"tag":61,"props":786,"children":787},{"style":68},[788],{"type":35,"value":398},{"type":30,"tag":61,"props":790,"children":791},{"style":104},[792],{"type":35,"value":793}," MAX",{"type":30,"tag":61,"props":795,"children":796},{"style":74},[797],{"type":35,"value":798},"(cohort_date) ",{"type":30,"tag":61,"props":800,"children":801},{"style":68},[802],{"type":35,"value":720},{"type":30,"tag":61,"props":804,"children":805},{"style":74},[806],{"type":35,"value":807}," {{ this }})\n",{"type":30,"tag":61,"props":809,"children":811},{"class":63,"line":810},16,[812,817,822,827,832,836,840,844,848],{"type":30,"tag":61,"props":813,"children":814},{"style":68},[815],{"type":35,"value":816},"  AND",{"type":30,"tag":61,"props":818,"children":819},{"style":74},[820],{"type":35,"value":821}," user_id ",{"type":30,"tag":61,"props":823,"children":824},{"style":68},[825],{"type":35,"value":826},"NOT",{"type":30,"tag":61,"props":828,"children":829},{"style":68},[830],{"type":35,"value":831}," IN",{"type":30,"tag":61,"props":833,"children":834},{"style":74},[835],{"type":35,"value":784},{"type":30,"tag":61,"props":837,"children":838},{"style":68},[839],{"type":35,"value":398},{"type":30,"tag":61,"props":841,"children":842},{"style":74},[843],{"type":35,"value":821},{"type":30,"tag":61,"props":845,"children":846},{"style":68},[847],{"type":35,"value":720},{"type":30,"tag":61,"props":849,"children":850},{"style":74},[851],{"type":35,"value":807},{"type":30,"tag":61,"props":853,"children":855},{"class":63,"line":854},17,[856],{"type":30,"tag":61,"props":857,"children":858},{"style":74},[859],{"type":35,"value":860},"{% endif %}\n",{"type":30,"tag":61,"props":862,"children":864},{"class":63,"line":863},18,[865,870],{"type":30,"tag":61,"props":866,"children":867},{"style":68},[868],{"type":35,"value":869},"GROUP BY",{"type":30,"tag":61,"props":871,"children":872},{"style":74},[873],{"type":35,"value":149},{"type":30,"tag":31,"props":875,"children":876},{},[877,879,884],{"type":35,"value":878},"Este modelo, en su primera ejecución, escanea todo el historial (costo único). Las ejecuciones posteriores cada día agregan solo los usuarios nuevos de ayer. Particionar por ",{"type":30,"tag":57,"props":880,"children":882},{"className":881},[],[883],{"type":35,"value":206},{"type":35,"value":885}," significa que BigQuery no toca particiones antiguas — el costo de query se mantiene proporcional al volumen diario (10M eventos nuevos → ~50MB de escaneo).",{"type":30,"tag":31,"props":887,"children":888},{},[889,891,896,898,904],{"type":35,"value":890},"Agrupar por ",{"type":30,"tag":57,"props":892,"children":894},{"className":893},[],[895],{"type":35,"value":308},{"type":35,"value":897}," mejora el rendimiento de JOIN. Cuando los queries de retención descendentes se unen con ",{"type":30,"tag":57,"props":899,"children":901},{"className":900},[],[902],{"type":35,"value":903},"user_cohort_base",{"type":35,"value":905},", BigQuery realiza búsqueda binaria en micro-particiones — lee solo los bloques de clúster relevantes, no 5 mil millones de filas.",{"type":30,"tag":907,"props":908,"children":910},"h3",{"id":909},"estrategia-de-particionamiento-fecha-de-evento-o-de-cohorte",[911],{"type":35,"value":912},"Estrategia de Particionamiento: ¿Fecha de Evento o de Cohorte?",{"type":30,"tag":31,"props":914,"children":915},{},[916,918,924,926,931,933,938,940,945,947,952],{"type":35,"value":917},"Si tu tabla ",{"type":30,"tag":57,"props":919,"children":921},{"className":920},[],[922],{"type":35,"value":923},"events",{"type":35,"value":925}," se particiona por ",{"type":30,"tag":57,"props":927,"children":929},{"className":928},[],[930],{"type":35,"value":234},{"type":35,"value":932},", tu base de cohortes debe particionarse por ",{"type":30,"tag":57,"props":934,"children":936},{"className":935},[],[937],{"type":35,"value":206},{"type":35,"value":939},". Esto es crítico. Los queries de retención hacen preguntas del tipo \"retención de la cohorte de enero en febrero\". La partición ",{"type":30,"tag":57,"props":941,"children":943},{"className":942},[],[944],{"type":35,"value":234},{"type":35,"value":946}," no puede podar en este caso. La partición ",{"type":30,"tag":57,"props":948,"children":950},{"className":949},[],[951],{"type":35,"value":206},{"type":35,"value":953}," sí — cuando dices \"cohorte de enero\", solo lee la partición de enero — 30 días de datos en lugar de 1 día.",{"type":30,"tag":31,"props":955,"children":956},{},[957,959,965,967,973],{"type":35,"value":958},"Pero respeta el límite de 4.000 particiones de BigQuery. 10 años de data = 3.650 particiones — estás al límite. Si necesitas más antigüedad, particiona ",{"type":30,"tag":57,"props":960,"children":962},{"className":961},[],[963],{"type":35,"value":964},"DATE_TRUNC(cohort_date, WEEK)",{"type":35,"value":966}," semanalmente o ",{"type":30,"tag":57,"props":968,"children":970},{"className":969},[],[971],{"type":35,"value":972},"DATE_TRUNC(cohort_date, MONTH)",{"type":35,"value":974}," mensualmente.",{"type":30,"tag":38,"props":976,"children":978},{"id":977},"cubo-de-retención-pre-agregado-reduce-el-costo-100-veces",[979],{"type":35,"value":980},"Cubo de Retención Pre-Agregado: Reduce el Costo 100 Veces",{"type":30,"tag":31,"props":982,"children":983},{},[984,986,991,993,998],{"type":35,"value":985},"Tienes ",{"type":30,"tag":57,"props":987,"children":989},{"className":988},[],[990],{"type":35,"value":903},{"type":35,"value":992}," listo, pero cada query de retención aún se une con la tabla ",{"type":30,"tag":57,"props":994,"children":996},{"className":995},[],[997],{"type":35,"value":923},{"type":35,"value":999},". El siguiente paso es pre-calcular las métricas diarias de retención y escribirlas en una tabla materializada:",{"type":30,"tag":50,"props":1001,"children":1003},{"className":52,"code":1002,"language":54,"meta":14,"style":14},"-- 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",[1004],{"type":30,"tag":57,"props":1005,"children":1006},{"__ignoreMap":14},[1007,1015,1022,1041,1057,1105,1112,1119,1139,1147,1167,1223,1262,1284,1320,1336,1376,1384,1403,1411,1419,1428,1437,1446,1479,1508,1526],{"type":30,"tag":61,"props":1008,"children":1009},{"class":63,"line":64},[1010],{"type":30,"tag":61,"props":1011,"children":1012},{"style":501},[1013],{"type":35,"value":1014},"-- models\u002Fcohorts\u002Fdaily_retention_cube.sql\n",{"type":30,"tag":61,"props":1016,"children":1017},{"class":63,"line":90},[1018],{"type":30,"tag":61,"props":1019,"children":1020},{"style":74},[1021],{"type":35,"value":512},{"type":30,"tag":61,"props":1023,"children":1024},{"class":63,"line":124},[1025,1029,1033,1037],{"type":30,"tag":61,"props":1026,"children":1027},{"style":74},[1028],{"type":35,"value":520},{"type":30,"tag":61,"props":1030,"children":1031},{"style":68},[1032],{"type":35,"value":525},{"type":30,"tag":61,"props":1034,"children":1035},{"style":528},[1036],{"type":35,"value":531},{"type":30,"tag":61,"props":1038,"children":1039},{"style":74},[1040],{"type":35,"value":211},{"type":30,"tag":61,"props":1042,"children":1043},{"class":63,"line":138},[1044,1048,1052],{"type":30,"tag":61,"props":1045,"children":1046},{"style":74},[1047],{"type":35,"value":543},{"type":30,"tag":61,"props":1049,"children":1050},{"style":68},[1051],{"type":35,"value":525},{"type":30,"tag":61,"props":1053,"children":1054},{"style":74},[1055],{"type":35,"value":1056},"['cohort_date', 'day_offset'],\n",{"type":30,"tag":61,"props":1058,"children":1059},{"class":63,"line":152},[1060,1064,1068,1072,1076,1080,1084,1088,1092,1096,1100],{"type":30,"tag":61,"props":1061,"children":1062},{"style":74},[1063],{"type":35,"value":564},{"type":30,"tag":61,"props":1065,"children":1066},{"style":68},[1067],{"type":35,"value":525},{"type":30,"tag":61,"props":1069,"children":1070},{"style":74},[1071],{"type":35,"value":573},{"type":30,"tag":61,"props":1073,"children":1074},{"style":528},[1075],{"type":35,"value":578},{"type":30,"tag":61,"props":1077,"children":1078},{"style":74},[1079],{"type":35,"value":583},{"type":30,"tag":61,"props":1081,"children":1082},{"style":528},[1083],{"type":35,"value":588},{"type":30,"tag":61,"props":1085,"children":1086},{"style":74},[1087],{"type":35,"value":239},{"type":30,"tag":61,"props":1089,"children":1090},{"style":528},[1091],{"type":35,"value":597},{"type":30,"tag":61,"props":1093,"children":1094},{"style":74},[1095],{"type":35,"value":583},{"type":30,"tag":61,"props":1097,"children":1098},{"style":528},[1099],{"type":35,"value":606},{"type":30,"tag":61,"props":1101,"children":1102},{"style":74},[1103],{"type":35,"value":1104},"}\n",{"type":30,"tag":61,"props":1106,"children":1107},{"class":63,"line":161},[1108],{"type":30,"tag":61,"props":1109,"children":1110},{"style":74},[1111],{"type":35,"value":636},{"type":30,"tag":61,"props":1113,"children":1114},{"class":63,"line":178},[1115],{"type":30,"tag":61,"props":1116,"children":1117},{"emptyLinePlaceholder":642},[1118],{"type":35,"value":645},{"type":30,"tag":61,"props":1120,"children":1121},{"class":63,"line":24},[1122,1126,1131,1135],{"type":30,"tag":61,"props":1123,"children":1124},{"style":68},[1125],{"type":35,"value":71},{"type":30,"tag":61,"props":1127,"children":1128},{"style":74},[1129],{"type":35,"value":1130}," cohort_activity ",{"type":30,"tag":61,"props":1132,"children":1133},{"style":68},[1134],{"type":35,"value":82},{"type":30,"tag":61,"props":1136,"children":1137},{"style":74},[1138],{"type":35,"value":87},{"type":30,"tag":61,"props":1140,"children":1141},{"class":63,"line":214},[1142],{"type":30,"tag":61,"props":1143,"children":1144},{"style":68},[1145],{"type":35,"value":1146},"  SELECT\n",{"type":30,"tag":61,"props":1148,"children":1149},{"class":63,"line":278},[1150,1155,1159,1163],{"type":30,"tag":61,"props":1151,"children":1152},{"style":104},[1153],{"type":35,"value":1154},"    c",{"type":30,"tag":61,"props":1156,"children":1157},{"style":74},[1158],{"type":35,"value":201},{"type":30,"tag":61,"props":1160,"children":1161},{"style":104},[1162],{"type":35,"value":206},{"type":30,"tag":61,"props":1164,"children":1165},{"style":74},[1166],{"type":35,"value":211},{"type":30,"tag":61,"props":1168,"children":1169},{"class":63,"line":324},[1170,1174,1178,1182,1186,1190,1195,1199,1203,1207,1211,1215,1219],{"type":30,"tag":61,"props":1171,"children":1172},{"style":74},[1173],{"type":35,"value":220},{"type":30,"tag":61,"props":1175,"children":1176},{"style":104},[1177],{"type":35,"value":225},{"type":30,"tag":61,"props":1179,"children":1180},{"style":74},[1181],{"type":35,"value":201},{"type":30,"tag":61,"props":1183,"children":1184},{"style":104},[1185],{"type":35,"value":234},{"type":30,"tag":61,"props":1187,"children":1188},{"style":74},[1189],{"type":35,"value":239},{"type":30,"tag":61,"props":1191,"children":1192},{"style":104},[1193],{"type":35,"value":1194},"c",{"type":30,"tag":61,"props":1196,"children":1197},{"style":74},[1198],{"type":35,"value":201},{"type":30,"tag":61,"props":1200,"children":1201},{"style":104},[1202],{"type":35,"value":206},{"type":30,"tag":61,"props":1204,"children":1205},{"style":74},[1206],{"type":35,"value":239},{"type":30,"tag":61,"props":1208,"children":1209},{"style":68},[1210],{"type":35,"value":261},{"type":30,"tag":61,"props":1212,"children":1213},{"style":74},[1214],{"type":35,"value":266},{"type":30,"tag":61,"props":1216,"children":1217},{"style":68},[1218],{"type":35,"value":82},{"type":30,"tag":61,"props":1220,"children":1221},{"style":74},[1222],{"type":35,"value":275},{"type":30,"tag":61,"props":1224,"children":1225},{"class":63,"line":337},[1226,1230,1234,1238,1242,1246,1250,1254,1258],{"type":30,"tag":61,"props":1227,"children":1228},{"style":104},[1229],{"type":35,"value":284},{"type":30,"tag":61,"props":1231,"children":1232},{"style":74},[1233],{"type":35,"value":289},{"type":30,"tag":61,"props":1235,"children":1236},{"style":68},[1237],{"type":35,"value":294},{"type":30,"tag":61,"props":1239,"children":1240},{"style":104},[1241],{"type":35,"value":299},{"type":30,"tag":61,"props":1243,"children":1244},{"style":74},[1245],{"type":35,"value":201},{"type":30,"tag":61,"props":1247,"children":1248},{"style":104},[1249],{"type":35,"value":308},{"type":30,"tag":61,"props":1251,"children":1252},{"style":74},[1253],{"type":35,"value":266},{"type":30,"tag":61,"props":1255,"children":1256},{"style":68},[1257],{"type":35,"value":82},{"type":30,"tag":61,"props":1259,"children":1260},{"style":74},[1261],{"type":35,"value":321},{"type":30,"tag":61,"props":1263,"children":1264},{"class":63,"line":361},[1265,1269,1274,1279],{"type":30,"tag":61,"props":1266,"children":1267},{"style":68},[1268],{"type":35,"value":130},{"type":30,"tag":61,"props":1270,"children":1271},{"style":74},[1272],{"type":35,"value":1273}," {{ ref(",{"type":30,"tag":61,"props":1275,"children":1276},{"style":528},[1277],{"type":35,"value":1278},"'user_cohort_base'",{"type":30,"tag":61,"props":1280,"children":1281},{"style":74},[1282],{"type":35,"value":1283},") }} c\n",{"type":30,"tag":61,"props":1285,"children":1286},{"class":63,"line":383},[1287,1291,1295,1299,1303,1307,1312,1316],{"type":30,"tag":61,"props":1288,"children":1289},{"style":68},[1290],{"type":35,"value":343},{"type":30,"tag":61,"props":1292,"children":1293},{"style":74},[1294],{"type":35,"value":725},{"type":30,"tag":61,"props":1296,"children":1297},{"style":528},[1298],{"type":35,"value":730},{"type":30,"tag":61,"props":1300,"children":1301},{"style":74},[1302],{"type":35,"value":239},{"type":30,"tag":61,"props":1304,"children":1305},{"style":528},[1306],{"type":35,"value":739},{"type":30,"tag":61,"props":1308,"children":1309},{"style":74},[1310],{"type":35,"value":1311},") }} e ",{"type":30,"tag":61,"props":1313,"children":1314},{"style":68},[1315],{"type":35,"value":353},{"type":30,"tag":61,"props":1317,"children":1318},{"style":74},[1319],{"type":35,"value":358},{"type":30,"tag":61,"props":1321,"children":1322},{"class":63,"line":392},[1323,1328,1332],{"type":30,"tag":61,"props":1324,"children":1325},{"style":74},[1326],{"type":35,"value":1327},"  {% ",{"type":30,"tag":61,"props":1329,"children":1330},{"style":68},[1331],{"type":35,"value":756},{"type":30,"tag":61,"props":1333,"children":1334},{"style":74},[1335],{"type":35,"value":761},{"type":30,"tag":61,"props":1337,"children":1338},{"class":63,"line":810},[1339,1344,1348,1352,1356,1361,1366,1371],{"type":30,"tag":61,"props":1340,"children":1341},{"style":68},[1342],{"type":35,"value":1343},"  WHERE",{"type":30,"tag":61,"props":1345,"children":1346},{"style":104},[1347],{"type":35,"value":299},{"type":30,"tag":61,"props":1349,"children":1350},{"style":74},[1351],{"type":35,"value":201},{"type":30,"tag":61,"props":1353,"children":1354},{"style":104},[1355],{"type":35,"value":234},{"type":30,"tag":61,"props":1357,"children":1358},{"style":68},[1359],{"type":35,"value":1360}," >=",{"type":30,"tag":61,"props":1362,"children":1363},{"style":74},[1364],{"type":35,"value":1365}," CURRENT_DATE() ",{"type":30,"tag":61,"props":1367,"children":1368},{"style":68},[1369],{"type":35,"value":1370},"-",{"type":30,"tag":61,"props":1372,"children":1373},{"style":104},[1374],{"type":35,"value":1375}," 1\n",{"type":30,"tag":61,"props":1377,"children":1378},{"class":63,"line":854},[1379],{"type":30,"tag":61,"props":1380,"children":1381},{"style":74},[1382],{"type":35,"value":1383},"  {% endif %}\n",{"type":30,"tag":61,"props":1385,"children":1386},{"class":63,"line":863},[1387,1391,1395,1399],{"type":30,"tag":61,"props":1388,"children":1389},{"style":68},[1390],{"type":35,"value":144},{"type":30,"tag":61,"props":1392,"children":1393},{"style":104},[1394],{"type":35,"value":371},{"type":30,"tag":61,"props":1396,"children":1397},{"style":74},[1398],{"type":35,"value":239},{"type":30,"tag":61,"props":1400,"children":1401},{"style":104},[1402],{"type":35,"value":380},{"type":30,"tag":61,"props":1404,"children":1406},{"class":63,"line":1405},19,[1407],{"type":30,"tag":61,"props":1408,"children":1409},{"style":74},[1410],{"type":35,"value":389},{"type":30,"tag":61,"props":1412,"children":1414},{"class":63,"line":1413},20,[1415],{"type":30,"tag":61,"props":1416,"children":1417},{"style":68},[1418],{"type":35,"value":653},{"type":30,"tag":61,"props":1420,"children":1422},{"class":63,"line":1421},21,[1423],{"type":30,"tag":61,"props":1424,"children":1425},{"style":74},[1426],{"type":35,"value":1427},"  cohort_date,\n",{"type":30,"tag":61,"props":1429,"children":1431},{"class":63,"line":1430},22,[1432],{"type":30,"tag":61,"props":1433,"children":1434},{"style":74},[1435],{"type":35,"value":1436},"  day_offset,\n",{"type":30,"tag":61,"props":1438,"children":1440},{"class":63,"line":1439},23,[1441],{"type":30,"tag":61,"props":1442,"children":1443},{"style":74},[1444],{"type":35,"value":1445},"  active_users,\n",{"type":30,"tag":61,"props":1447,"children":1449},{"class":63,"line":1448},24,[1450,1455,1460,1465,1470,1475],{"type":30,"tag":61,"props":1451,"children":1452},{"style":74},[1453],{"type":35,"value":1454},"  active_users ",{"type":30,"tag":61,"props":1456,"children":1457},{"style":68},[1458],{"type":35,"value":1459},"\u002F",{"type":30,"tag":61,"props":1461,"children":1462},{"style":104},[1463],{"type":35,"value":1464}," FIRST_VALUE",{"type":30,"tag":61,"props":1466,"children":1467},{"style":74},[1468],{"type":35,"value":1469},"(active_users) ",{"type":30,"tag":61,"props":1471,"children":1472},{"style":68},[1473],{"type":35,"value":1474},"OVER",{"type":30,"tag":61,"props":1476,"children":1477},{"style":74},[1478],{"type":35,"value":87},{"type":30,"tag":61,"props":1480,"children":1482},{"class":63,"line":1481},25,[1483,1488,1493,1498,1503],{"type":30,"tag":61,"props":1484,"children":1485},{"style":68},[1486],{"type":35,"value":1487},"    PARTITION",{"type":30,"tag":61,"props":1489,"children":1490},{"style":68},[1491],{"type":35,"value":1492}," BY",{"type":30,"tag":61,"props":1494,"children":1495},{"style":74},[1496],{"type":35,"value":1497}," cohort_date ",{"type":30,"tag":61,"props":1499,"children":1500},{"style":68},[1501],{"type":35,"value":1502},"ORDER BY",{"type":30,"tag":61,"props":1504,"children":1505},{"style":74},[1506],{"type":35,"value":1507}," day_offset\n",{"type":30,"tag":61,"props":1509,"children":1511},{"class":63,"line":1510},26,[1512,1517,1521],{"type":30,"tag":61,"props":1513,"children":1514},{"style":74},[1515],{"type":35,"value":1516},"  ) ",{"type":30,"tag":61,"props":1518,"children":1519},{"style":68},[1520],{"type":35,"value":82},{"type":30,"tag":61,"props":1522,"children":1523},{"style":74},[1524],{"type":35,"value":1525}," retention_rate\n",{"type":30,"tag":61,"props":1527,"children":1529},{"class":63,"line":1528},27,[1530,1534],{"type":30,"tag":61,"props":1531,"children":1532},{"style":68},[1533],{"type":35,"value":720},{"type":30,"tag":61,"props":1535,"children":1536},{"style":74},[1537],{"type":35,"value":1538}," cohort_activity\n",{"type":30,"tag":31,"props":1540,"children":1541},{},[1542,1544,1549,1551,1556,1558,1563],{"type":35,"value":1543},"Esta tabla se ejecuta cada día, agregando solo la actividad nueva de ayer. Particionar por ",{"type":30,"tag":57,"props":1545,"children":1547},{"className":1546},[],[1548],{"type":35,"value":206},{"type":35,"value":1550}," significa que las particiones antiguas no se tocan. El resultado: ",{"type":30,"tag":423,"props":1552,"children":1553},{},[1554],{"type":35,"value":1555},"5 mil millones de filas en events",{"type":35,"value":1557}," frente a ",{"type":30,"tag":423,"props":1559,"children":1560},{},[1561],{"type":35,"value":1562},"500 cohortes × 90 días = 45.000 filas en el cubo",{"type":35,"value":1564},". Los dashboards ahora leen directamente desde el cubo — el volumen de escaneo cae 100.000 veces, la latencia de 45 segundos baja a 50 milisegundos.",{"type":30,"tag":907,"props":1566,"children":1568},{"id":1567},"estrategia-de-ventanas-cálculo-de-tasa-de-retención",[1569],{"type":35,"value":1570},"Estrategia de Ventanas: Cálculo de Tasa de Retención",{"type":30,"tag":31,"props":1572,"children":1573},{},[1574,1576,1582],{"type":35,"value":1575},"La expresión ",{"type":30,"tag":57,"props":1577,"children":1579},{"className":1578},[],[1580],{"type":35,"value":1581},"FIRST_VALUE(active_users) OVER (PARTITION BY cohort_date ORDER BY day_offset)",{"type":35,"value":1583}," lleva el conteo D0 a cada fila. Así la tasa de retención se calcula al escribir, no al consultar. Podrías alternarlo con un JOIN separado para D0, pero BigQuery optimiza funciones de ventana con uso de slots eficiente (lectura ordenada dentro de particiones).",{"type":30,"tag":31,"props":1585,"children":1586},{},[1587,1589,1594,1596,1601],{"type":35,"value":1588},"Nota importante: la cláusula ",{"type":30,"tag":57,"props":1590,"children":1592},{"className":1591},[],[1593],{"type":35,"value":1474},{"type":35,"value":1595}," no rompe la poda de particiones porque la partición lógica (",{"type":30,"tag":57,"props":1597,"children":1599},{"className":1598},[],[1600],{"type":35,"value":206},{"type":35,"value":1602},") coincide con la partición física. BigQuery procesa cada partición de forma independiente, sin shuffle entre particiones.",{"type":30,"tag":38,"props":1604,"children":1606},{"id":1605},"optimización-de-costos-de-queries-uso-de-slots-y-caching",[1607],{"type":35,"value":1608},"Optimización de Costos de Queries: Uso de Slots y Caching",{"type":30,"tag":31,"props":1610,"children":1611},{},[1612],{"type":35,"value":1613},"El modelo de costos de BigQuery se basa en bytes escaneados (5 dólares\u002FTB). Pero para latencia en producción, el uso de slots es más crítico. La estrategia de materialized view reduce costo, pero puede haber contención de slots — especialmente si 10 usuarios en el dashboard ejecutan diferentes filtros de cohorte simultáneamente.",{"type":30,"tag":31,"props":1615,"children":1616},{},[1617,1622,1624,1630],{"type":30,"tag":423,"props":1618,"children":1619},{},[1620],{"type":35,"value":1621},"Caching con BI Engine:",{"type":35,"value":1623}," BigQuery BI Engine mantiene en RAM hasta 100GB de datos calientes. Tu ",{"type":30,"tag":57,"props":1625,"children":1627},{"className":1626},[],[1628],{"type":35,"value":1629},"daily_retention_cube",{"type":35,"value":1631}," con 45.000 filas × 200 bytes ≈ 9MB se cachea completamente. Las queries posteriores consumen 0 slots, responden en \u003C10 milisegundos. BI Engine requiere reserva manual (console BigQuery → Capacity Management → nivel 100GB = 300 dólares\u002Fmes). El ROI es alto — 1.000 queries\u002Fdía × 0.01 dólar costo de slots = 10 dólares\u002Fdía versus 10 dólares\u002Fdía de tarifa plana.",{"type":30,"tag":31,"props":1633,"children":1634},{},[1635,1640],{"type":30,"tag":423,"props":1636,"children":1637},{},[1638],{"type":35,"value":1639},"Caching de resultados de queries:",{"type":35,"value":1641}," BigQuery cachea resultados durante 24 horas. Si el dashboard ejecuta \"cohortes de los últimos 7 días\" para cada usuario y es la misma query, la primera consulta hace hit, las posteriores vienen del caché. Pero si cambian parámetros (rango de fechas, filtros de segmento), el caché falla. Aquí el cubo pre-agregado sigue siendo ventajoso.",{"type":30,"tag":31,"props":1643,"children":1644},{},[1645,1650],{"type":30,"tag":423,"props":1646,"children":1647},{},[1648],{"type":35,"value":1649},"Asignación de slots:",{"type":35,"value":1651}," Si consideras pricing de tarifa plana (500 slots = 10.000 dólares\u002Fmes) en lugar de bajo demanda, asigna tu pipeline de retención a un pool de slots dedicado. Durante horas pico, los cálculos de retención no compiten con queries de BI por slots. En la configuración de producción de Roibase, las queries programadas corren fuera de horas pico (03:00-05:00), los dashboards frente al usuario usan slots flex (autoescalado 100-500).",{"type":30,"tag":38,"props":1653,"children":1655},{"id":1654},"integración-de-resolución-de-identidad-cohortes-multidispositivo",[1656],{"type":35,"value":1657},"Integración de Resolución de Identidad: Cohortes Multidispositivo",{"type":30,"tag":31,"props":1659,"children":1660},{},[1661,1663,1668],{"type":35,"value":1662},"El análisis clásico de cohortes se basa en ",{"type":30,"tag":57,"props":1664,"children":1666},{"className":1665},[],[1667],{"type":35,"value":308},{"type":35,"value":1669},", pero en journeys multidispositivo, la misma persona puede tener 3 IDs diferentes (web anónimo, app autenticada, CRM). Si ves retención del 15%, la retención real podría ser 22% — fragmentación de IDs.",{"type":30,"tag":31,"props":1671,"children":1672},{},[1673,1675,1684,1686,1692,1694,1700,1701,1706,1707,1713,1715,1721],{"type":35,"value":1674},"Dentro del marco de ",{"type":30,"tag":1676,"props":1677,"children":1681},"a",{"href":1678,"rel":1679},"https:\u002F\u002Fwww.roibase.com.tr\u002Fes\u002Ffirstparty",[1680],"nofollow",[1682],{"type":35,"value":1683},"First-Party Veri & Arquitectura de Medición",{"type":35,"value":1685},", se construye un grafo de identidades: la tabla ",{"type":30,"tag":57,"props":1687,"children":1689},{"className":1688},[],[1690],{"type":35,"value":1691},"identity_map",{"type":35,"value":1693}," vincula cada ",{"type":30,"tag":57,"props":1695,"children":1697},{"className":1696},[],[1698],{"type":35,"value":1699},"anonymous_id",{"type":35,"value":239},{"type":30,"tag":57,"props":1702,"children":1704},{"className":1703},[],[1705],{"type":35,"value":308},{"type":35,"value":239},{"type":30,"tag":57,"props":1708,"children":1710},{"className":1709},[],[1711],{"type":35,"value":1712},"crm_id",{"type":35,"value":1714}," a un canonical ",{"type":30,"tag":57,"props":1716,"children":1718},{"className":1717},[],[1719],{"type":35,"value":1720},"person_id",{"type":35,"value":1722},". Enriquece tu modelo base de cohortes con este grafo:",{"type":30,"tag":50,"props":1724,"children":1726},{"className":52,"code":1725,"language":54,"meta":14,"style":14},"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",[1727],{"type":30,"tag":57,"props":1728,"children":1729},{"__ignoreMap":14},[1730,1750,1757,1811,1828,1856,1914,1921,1949,1961],{"type":30,"tag":61,"props":1731,"children":1732},{"class":63,"line":64},[1733,1737,1742,1746],{"type":30,"tag":61,"props":1734,"children":1735},{"style":68},[1736],{"type":35,"value":71},{"type":30,"tag":61,"props":1738,"children":1739},{"style":74},[1740],{"type":35,"value":1741}," resolved_events ",{"type":30,"tag":61,"props":1743,"children":1744},{"style":68},[1745],{"type":35,"value":82},{"type":30,"tag":61,"props":1747,"children":1748},{"style":74},[1749],{"type":35,"value":87},{"type":30,"tag":61,"props":1751,"children":1752},{"class":63,"line":90},[1753],{"type":30,"tag":61,"props":1754,"children":1755},{"style":68},[1756],{"type":35,"value":1146},{"type":30,"tag":61,"props":1758,"children":1759},{"class":63,"line":124},[1760,1765,1769,1774,1778,1782,1786,1790,1794,1798,1802,1806],{"type":30,"tag":61,"props":1761,"children":1762},{"style":104},[1763],{"type":35,"value":1764},"    COALESCE",{"type":30,"tag":61,"props":1766,"children":1767},{"style":74},[1768],{"type":35,"value":289},{"type":30,"tag":61,"props":1770,"children":1771},{"style":104},[1772],{"type":35,"value":1773},"i",{"type":30,"tag":61,"props":1775,"children":1776},{"style":74},[1777],{"type":35,"value":201},{"type":30,"tag":61,"props":1779,"children":1780},{"style":104},[1781],{"type":35,"value":1720},{"type":30,"tag":61,"props":1783,"children":1784},{"style":74},[1785],{"type":35,"value":239},{"type":30,"tag":61,"props":1787,"children":1788},{"style":104},[1789],{"type":35,"value":225},{"type":30,"tag":61,"props":1791,"children":1792},{"style":74},[1793],{"type":35,"value":201},{"type":30,"tag":61,"props":1795,"children":1796},{"style":104},[1797],{"type":35,"value":308},{"type":30,"tag":61,"props":1799,"children":1800},{"style":74},[1801],{"type":35,"value":266},{"type":30,"tag":61,"props":1803,"children":1804},{"style":68},[1805],{"type":35,"value":82},{"type":30,"tag":61,"props":1807,"children":1808},{"style":74},[1809],{"type":35,"value":1810}," person_id,\n",{"type":30,"tag":61,"props":1812,"children":1813},{"class":63,"line":138},[1814,1819,1823],{"type":30,"tag":61,"props":1815,"children":1816},{"style":104},[1817],{"type":35,"value":1818},"    e",{"type":30,"tag":61,"props":1820,"children":1821},{"style":74},[1822],{"type":35,"value":201},{"type":30,"tag":61,"props":1824,"children":1825},{"style":104},[1826],{"type":35,"value":1827},"event_date\n",{"type":30,"tag":61,"props":1829,"children":1830},{"class":63,"line":152},[1831,1835,1839,1843,1847,1851],{"type":30,"tag":61,"props":1832,"children":1833},{"style":68},[1834],{"type":35,"value":130},{"type":30,"tag":61,"props":1836,"children":1837},{"style":74},[1838],{"type":35,"value":725},{"type":30,"tag":61,"props":1840,"children":1841},{"style":528},[1842],{"type":35,"value":730},{"type":30,"tag":61,"props":1844,"children":1845},{"style":74},[1846],{"type":35,"value":239},{"type":30,"tag":61,"props":1848,"children":1849},{"style":528},[1850],{"type":35,"value":739},{"type":30,"tag":61,"props":1852,"children":1853},{"style":74},[1854],{"type":35,"value":1855},") }} e\n",{"type":30,"tag":61,"props":1857,"children":1858},{"class":63,"line":161},[1859,1864,1868,1873,1878,1883,1887,1891,1895,1900,1905,1909],{"type":30,"tag":61,"props":1860,"children":1861},{"style":68},[1862],{"type":35,"value":1863},"  LEFT JOIN",{"type":30,"tag":61,"props":1865,"children":1866},{"style":74},[1867],{"type":35,"value":1273},{"type":30,"tag":61,"props":1869,"children":1870},{"style":528},[1871],{"type":35,"value":1872},"'identity_map'",{"type":30,"tag":61,"props":1874,"children":1875},{"style":74},[1876],{"type":35,"value":1877},") }} i ",{"type":30,"tag":61,"props":1879,"children":1880},{"style":68},[1881],{"type":35,"value":1882},"ON",{"type":30,"tag":61,"props":1884,"children":1885},{"style":104},[1886],{"type":35,"value":299},{"type":30,"tag":61,"props":1888,"children":1889},{"style":74},[1890],{"type":35,"value":201},{"type":30,"tag":61,"props":1892,"children":1893},{"style":104},[1894],{"type":35,"value":308},{"type":30,"tag":61,"props":1896,"children":1897},{"style":68},[1898],{"type":35,"value":1899}," =",{"type":30,"tag":61,"props":1901,"children":1902},{"style":104},[1903],{"type":35,"value":1904}," i",{"type":30,"tag":61,"props":1906,"children":1907},{"style":74},[1908],{"type":35,"value":201},{"type":30,"tag":61,"props":1910,"children":1911},{"style":104},[1912],{"type":35,"value":1913},"user_id\n",{"type":30,"tag":61,"props":1915,"children":1916},{"class":63,"line":178},[1917],{"type":30,"tag":61,"props":1918,"children":1919},{"style":74},[1920],{"type":35,"value":389},{"type":30,"tag":61,"props":1922,"children":1923},{"class":63,"line":24},[1924,1928,1933,1937,1941,1945],{"type":30,"tag":61,"props":1925,"children":1926},{"style":68},[1927],{"type":35,"value":398},{"type":30,"tag":61,"props":1929,"children":1930},{"style":74},[1931],{"type":35,"value":1932}," person_id, ",{"type":30,"tag":61,"props":1934,"children":1935},{"style":104},[1936],{"type":35,"value":107},{"type":30,"tag":61,"props":1938,"children":1939},{"style":74},[1940],{"type":35,"value":112},{"type":30,"tag":61,"props":1942,"children":1943},{"style":68},[1944],{"type":35,"value":82},{"type":30,"tag":61,"props":1946,"children":1947},{"style":74},[1948],{"type":35,"value":121},{"type":30,"tag":61,"props":1950,"children":1951},{"class":63,"line":214},[1952,1956],{"type":30,"tag":61,"props":1953,"children":1954},{"style":68},[1955],{"type":35,"value":720},{"type":30,"tag":61,"props":1957,"children":1958},{"style":74},[1959],{"type":35,"value":1960}," resolved_events\n",{"type":30,"tag":61,"props":1962,"children":1963},{"class":63,"line":278},[1964,1968],{"type":30,"tag":61,"props":1965,"children":1966},{"style":68},[1967],{"type":35,"value":869},{"type":30,"tag":61,"props":1969,"children":1970},{"style":74},[1971],{"type":35,"value":1972}," person_id\n",{"type":30,"tag":31,"props":1974,"children":1975},{},[1976,1978,1983,1985,1990],{"type":35,"value":1977},"Este JOIN puede ser costoso pero ",{"type":30,"tag":57,"props":1979,"children":1981},{"className":1980},[],[1982],{"type":35,"value":1691},{"type":35,"value":1984}," recibe actualizaciones incrementales diarias con clustering por ",{"type":30,"tag":57,"props":1986,"children":1988},{"className":1987},[],[1989],{"type":35,"value":308},{"type":35,"value":1991}," — BigQuery hace hash join, sin overhead de broadcast. El resultado: la cohorte refleja retención real, las decisiones de marketing (realocación de presupuesto, pronóstico de LTV) se basan en datos precisos.",{"type":30,"tag":38,"props":1993,"children":1995},{"id":1994},"estrategia-de-refresh-incremental-backfill-vs-delta-diario",[1996],{"type":35,"value":1997},"Estrategia de Refresh Incremental: Backfill vs Delta Diario",{"type":30,"tag":31,"props":1999,"children":2000},{},[2001],{"type":35,"value":2002},"El riesgo crítico de las materialized views es: si los datos upstream se corrijen (evento atrasado, eliminación GDPR), la vista se queda obsoleta. BigQuery no refresca materialized views automáticamente — tú la gatillas.",{"type":30,"tag":31,"props":2004,"children":2005},{},[2006],{"type":30,"tag":423,"props":2007,"children":2008},{},[2009],{"type":35,"value":2010},"Dos estrategias:",{"type":30,"tag":2012,"props":2013,"children":2014},"ol",{},[2015,2026],{"type":30,"tag":2016,"props":2017,"children":2018},"li",{},[2019,2024],{"type":30,"tag":423,"props":2020,"children":2021},{},[2022],{"type":35,"value":2023},"Delta diario:",{"type":35,"value":2025}," Calcula solo la nueva partición cada día. Rápido pero pierde correcciones históricas.",{"type":30,"tag":2016,"props":2027,"children":2028},{},[2029,2034],{"type":30,"tag":423,"props":2030,"children":2031},{},[2032],{"type":35,"value":2033},"Backfill rodante:",{"type":35,"value":2035}," Recalcula los últimos 7 días cada ejecución. Captura eventos tardíos pero consume 7x compute.",{"type":30,"tag":31,"props":2037,"children":2038},{},[2039],{"type":35,"value":2040},"En la configuración de producción de Roibase usamos hybrid: delta diario + refresh completo semanal. En dbt:",{"type":30,"tag":50,"props":2042,"children":2046},{"className":2043,"code":2044,"language":2045,"meta":14,"style":14},"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",[2047],{"type":30,"tag":57,"props":2048,"children":2049},{"__ignoreMap":14},[2050,2058,2072,2084,2096],{"type":30,"tag":61,"props":2051,"children":2052},{"class":63,"line":64},[2053],{"type":30,"tag":61,"props":2054,"children":2055},{"style":501},[2056],{"type":35,"value":2057},"# dbt_project.yml\n",{"type":30,"tag":61,"props":2059,"children":2060},{"class":63,"line":90},[2061,2067],{"type":30,"tag":61,"props":2062,"children":2064},{"style":2063},"--shiki-default:#85E89D",[2065],{"type":35,"value":2066},"models",{"type":30,"tag":61,"props":2068,"children":2069},{"style":74},[2070],{"type":35,"value":2071},":\n",{"type":30,"tag":61,"props":2073,"children":2074},{"class":63,"line":124},[2075,2080],{"type":30,"tag":61,"props":2076,"children":2077},{"style":2063},[2078],{"type":35,"value":2079},"  cohorts",{"type":30,"tag":61,"props":2081,"children":2082},{"style":74},[2083],{"type":35,"value":2071},{"type":30,"tag":61,"props":2085,"children":2086},{"class":63,"line":138},[2087,2092],{"type":30,"tag":61,"props":2088,"children":2089},{"style":2063},[2090],{"type":35,"value":2091},"    daily_retention_cube",{"type":30,"tag":61,"props":2093,"children":2094},{"style":74},[2095],{"type":35,"value":2071},{"type":30,"tag":61,"props":2097,"children":2098},{"class":63,"line":152},[2099,2104,2108],{"type":30,"tag":61,"props":2100,"children":2101},{"style":2063},[2102],{"type":35,"value":2103},"      +full_refresh",{"type":30,"tag":61,"props":2105,"children":2106},{"style":74},[2107],{"type":35,"value":583},{"type":30,"tag":61,"props":2109,"children":2110},{"style":528},[2111],{"type":35,"value":2112},"\"{{ var('force_backfill', false) }}\"\n",{"type":30,"tag":31,"props":2114,"children":2115},{},[2116,2118,2124,2126,2132],{"type":35,"value":2117},"Ejecución normal ",{"type":30,"tag":57,"props":2119,"children":2121},{"className":2120},[],[2122],{"type":35,"value":2123},"dbt run --select daily_retention_cube",{"type":35,"value":2125}," (incremental). Fin de semana ",{"type":30,"tag":57,"props":2127,"children":2129},{"className":2128},[],[2130],{"type":35,"value":2131},"dbt run --select daily_retention_cube --vars '{force_backfill: true}'",{"type":35,"value":2133}," (refresh completo). Así controlas el tradeoff costo-precisión.",{"type":30,"tag":38,"props":2135,"children":2137},{"id":2136},"benchmark-de-rendimiento-ingenuo-vs-optimizado",[2138],{"type":35,"value":2139},"Benchmark de Rendimiento: Ingenuo vs Optimizado",{"type":30,"tag":31,"props":2141,"children":2142},{},[2143],{"type":35,"value":2144},"Dataset en producción: 10M eventos\u002Fdía, 18 meses de historial, 5.4 mil millones de filas.",{"type":30,"tag":2146,"props":2147,"children":2148},"table",{},[2149,2178],{"type":30,"tag":2150,"props":2151,"children":2152},"thead",{},[2153],{"type":30,"tag":2154,"props":2155,"children":2156},"tr",{},[2157,2163,2168,2173],{"type":30,"tag":2158,"props":2159,"children":2160},"th",{},[2161],{"type":35,"value":2162},"Métrica",{"type":30,"tag":2158,"props":2164,"children":2165},{},[2166],{"type":35,"value":2167},"SQL Ingenuo",{"type":30,"tag":2158,"props":2169,"children":2170},{},[2171],{"type":35,"value":2172},"Cubo Materializado",{"type":30,"tag":2158,"props":2174,"children":2175},{},[2176],{"type":35,"value":2177},"Mejora",{"type":30,"tag":2179,"props":2180,"children":2181},"tbody",{},[2182,2206,2229,2252,2275],{"type":30,"tag":2154,"props":2183,"children":2184},{},[2185,2191,2196,2201],{"type":30,"tag":2186,"props":2187,"children":2188},"td",{},[2189],{"type":35,"value":2190},"Volumen de escaneo (retención D7)",{"type":30,"tag":2186,"props":2192,"children":2193},{},[2194],{"type":35,"value":2195},"2.1 TB",{"type":30,"tag":2186,"props":2197,"children":2198},{},[2199],{"type":35,"value":2200},"18 MB",{"type":30,"tag":2186,"props":2202,"children":2203},{},[2204],{"type":35,"value":2205},"116x",{"type":30,"tag":2154,"props":2207,"children":2208},{},[2209,2214,2219,2224],{"type":30,"tag":2186,"props":2210,"children":2211},{},[2212],{"type":35,"value":2213},"Latencia de query (p95)",{"type":30,"tag":2186,"props":2215,"children":2216},{},[2217],{"type":35,"value":2218},"42 s",{"type":30,"tag":2186,"props":2220,"children":2221},{},[2222],{"type":35,"value":2223},"0.08 s",{"type":30,"tag":2186,"props":2225,"children":2226},{},[2227],{"type":35,"value":2228},"525x",{"type":30,"tag":2154,"props":2230,"children":2231},{},[2232,2237,2242,2247],{"type":30,"tag":2186,"props":2233,"children":2234},{},[2235],{"type":35,"value":2236},"Costo BigQuery\u002Fquery",{"type":30,"tag":2186,"props":2238,"children":2239},{},[2240],{"type":35,"value":2241},"10.50 USD",{"type":30,"tag":2186,"props":2243,"children":2244},{},[2245],{"type":35,"value":2246},"0.01 USD",{"type":30,"tag":2186,"props":2248,"children":2249},{},[2250],{"type":35,"value":2251},"1050x",{"type":30,"tag":2154,"props":2253,"children":2254},{},[2255,2260,2265,2270],{"type":30,"tag":2186,"props":2256,"children":2257},{},[2258],{"type":35,"value":2259},"Tiempo de carga dashboard",{"type":30,"tag":2186,"props":2261,"children":2262},{},[2263],{"type":35,"value":2264},"timeout",{"type":30,"tag":2186,"props":2266,"children":2267},{},[2268],{"type":35,"value":2269},"\u003C1 s",{"type":30,"tag":2186,"props":2271,"children":2272},{},[2273],{"type":35,"value":2274},"—",{"type":30,"tag":2154,"props":2276,"children":2277},{},[2278,2283,2288,2293],{"type":30,"tag":2186,"props":2279,"children":2280},{},[2281],{"type":35,"value":2282},"Uso de slots (pico)",{"type":30,"tag":2186,"props":2284,"children":2285},{},[2286],{"type":35,"value":2287},"2000",{"type":30,"tag":2186,"props":2289,"children":2290},{},[2291],{"type":35,"value":2292},"5",{"type":30,"tag":2186,"props":2294,"children":2295},{},[2296],{"type":35,"value":2297},"400x",{"type":30,"tag":31,"props":2299,"children":2300},{},[2301],{"type":35,"value":2302},"Query de test: \"Curva de retención de 30 días para la cohorte de enero 2026\". El query ingenuo lee la tabla events 18 veces (una por día). El cubo materializado lee 30 filas.",{"type":30,"tag":31,"props":2304,"children":2305},{},[2306],{"type":35,"value":2307},"Con BI-engine cache activado, la latencia cayó de 80ms a 12ms — el uso de slots fue cero. Testeamos 50 usuarios concurrentes en el dashboard, 99.5% uptime, latencia mediana 18ms. Este es el SLA de producción — el equipo de marketing puede hacer segmentación de cohortes en tiempo real (ej. \"usuarios con retención D3 \u003C20%, enviarles campaign push\").",{"type":30,"tag":31,"props":2309,"children":2310},{},[2311],{"type":35,"value":2312},"El análisis de retención es el corazón de cualquier growth stack moderno, pero la implementación ingenua no escala en producción. Con particionamiento estratégico, materialized views incrementales, pre-agregación y caching BI-engine, alcanzas latencia \u003C100ms a escala de millones de usuarios. El costo cae 100 veces, la contención de slots desaparece, tu equipo de marketing acelera la toma de decisiones basada en datos. Evalúa tu arquitectura hoy — si ves el spinner girando en tu dashboard de retención, el problema no es los datos, es el diseño de tabla.",{"type":30,"tag":2314,"props":2315,"children":2316},"style",{},[2317],{"type":35,"value":2318},"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":14,"searchDepth":124,"depth":124,"links":2320},[2321,2322,2325,2328,2329,2330,2331],{"id":40,"depth":90,"text":43},{"id":472,"depth":90,"text":475,"children":2323},[2324],{"id":909,"depth":124,"text":912},{"id":977,"depth":90,"text":980,"children":2326},[2327],{"id":1567,"depth":124,"text":1570},{"id":1605,"depth":90,"text":1608},{"id":1654,"depth":90,"text":1657},{"id":1994,"depth":90,"text":1997},{"id":2136,"depth":90,"text":2139},"markdown","content:es:data:arquitectura-tabla-cohortes.md","content","es\u002Fdata\u002Farquitectura-tabla-cohortes.md","es\u002Fdata\u002Farquitectura-tabla-cohortes","md",1783893866097]