How Medallion Modularization Cut a Critical-Path ETL Nearly in Half
- Antonio Souza
- Aug 26
- 8 min read
A few months into a data platform engagement, the client’s main daily ETL routinely ran for seven to nine hours. The pipeline kicked off around 1 a.m., so on a clean night that was mostly an overnight cost and a long warehouse bill. The real pain showed up when something failed partway through. A mid-run incident meant stakeholders were left waiting while the critical path crawled the rest of the way to fresh gold-layer tables. The instinct in situations like that is to hunt for slow SQL, throw more compute at the warehouse, or tune a handful of joins. We did some of that, and it bought a little headroom around the edges.
The bigger lever was structural: a remodel of how medallion layers depended on each other. A small set of end-of-pipeline gold tables had become god-nodes in the DAG. Dozens of downstream consumers, including other silver and gold transforms, were blocked on wide builds they only partially needed. By materializing the logical modules already sitting inside those builds as silver tables, and rewiring consumers to the smallest sufficient layer, we cut the same pipeline to roughly three to four hours — nearly half the critical-path time, with the cost dropping alongside it.
This article is a client-story account of that work. By the end you will see why wide gold tables snowball into pipeline debt, what modularization looked like in practice for a signup fact table and an account dimension, and why treating gold as a curated product surface for its consumers is one of the highest-leverage moves you can make on a mature medallion lakehouse.
Finding the Bottleneck in the Dependency Graph
To understand where those seven-to-nine hours were going, we traced the jobs that finished last and asked a simpler question: what were they waiting on? The answer pointed less at individual query cost and more at a handful of gold tables sitting near the end of the medallion stack, with half the lake queued behind them.
Diagnosis: Gold Became a God-Node
We traced dependencies upward from the jobs that finished last. The pattern repeated. A gold signup fact table sat near the end of the pipeline. Marketing transforms waited on it. Attribution waited on it. Telehealth and experimentation mappings waited on it. Other gold and silver tables waited on it. The same shape showed up around a gold account dimension: engagement, messaging, and privacy-sensitive export jobs all blocked on a build that joined far more sources than most of them needed.
In medallion terms, gold was doing two jobs at once:
Serving analysts with a rich, trustworthy business entity.
Acting as an internal API for dozens of pipeline consumers that only needed a slice of that entity.
Those jobs conflict. The first wants completeness. The second wants early availability and a narrow contract. When you force both through the same table, completeness wins in the schema, and latency loses in the DAG.
The Snowball: 250+ Columns and Growing
The clearest example was the gold signup fact table. Over time it had grown past 250 columns. That did not happen in one redesign. Data science needed propensity features. Finance needed richer revenue and refund rollups. Marketing needed attribution and LTV enrichment. Coaching and engagement needed relationship and survey fields. Each request was reasonable in isolation. Each one added “just a few more columns” to the shared gold build.
Then the second half of the snowball started. Because the table was the canonical place to find signup-level truth, new transforms referenced it by default. Every new dependent lengthened the critical path. Longer waits increased the pressure to keep putting domain needs into the same table, so the next team would not have to invent a parallel source. Wider gold, more dependents, longer waits, more columns — the loop reinforced itself.
At that point the CTEs inside the gold transform were already telling us the truth. The SQL was modular in spirit — mapping logic, time bounds, core demographics and financials, coaching joins, engagement history — but the orchestration treated the whole thing as one atomic task. Downstream sensors could not start until the fattest version of the entity was done.
The Insight: Dependency Granularity Beats Micro-Optimization
The fix pushed the medallion idea one layer deeper, into gold’s own upstream boundary.
If a gold table is composed of distinct logical modules, those modules deserve to exist as silver tables with their own contracts, freshness, and consumers. Materializing what used to be CTEs does add storage and a few orchestration tasks. In return, you get:
Earlier starts for consumers that only need the core facts.
Parallelism between modules that do not share the same upstream sources.
A shorter critical path for the daily DAG, because the heaviest joins no longer gate the lightest dependents.
Room to ship lighter pipelines — for example a marketing-focused DAG that never waits on coaching or engagement enrichment.
The guiding question became simple enough to put on a whiteboard: does this consumer need the full gold row, or a silver module?
Case Study 1: Modularizing the Gold Signup Fact
This is where most of the 7–9 hour to 3–4 hour improvement came from.
We decomposed the wide gold signup fact into a layered silver stack:
A subscription mapping module that established the entity grain and linked related commercial rows.
A bounds module for start/end timing and the subscription anchors that define a signup period.
A core signup facts module with identifiers, attribution, demographics, and the financial fields most analytics needed — deliberately excluding coaching, engagement, and survey-heavy joins.
An LTV-enriched silver slice for marketing allocation and ad performance workloads that needed core facts plus expected-value fields, still without the full gold payload.
The original gold table, kept as the wide public surface for consumers that truly needed the coaching and engagement-enriched row.
Then we rewired. Dozens of downstream transforms that had been reading the full gold build moved to the core or LTV-enriched silver layers. Marketing and attribution workloads stopped waiting on joins they never selected. Orchestration sensors followed the new contracts: each consumer now waited on the silver module that actually feeds it, and the habitual dependency on the end-of-pipeline gold entity fell away.
We did not delete gold. Analysts and a set of domain pipelines still needed the wide table. What changed was the default. Gold stopped being the internal bus for every signup-shaped dependency and returned to being a curated product for the consumers that require its full width.
That rewiring was the largest single contributor to cutting the daily pipeline roughly in half. Runtime dropped into the three-to-four-hour range. Shorter warehouse occupancy followed naturally — the same work finished earlier, and large classes of jobs no longer held expensive clusters open waiting on irrelevant upstream joins. Cost came down as a direct result of the shorter critical path: the same work simply finished sooner and released compute earlier.
Case Study 2: Splitting the Gold Account Dimension
The same pattern showed up one layer earlier in the identity spine of the platform: a gold account dimension assembled from many upstream sources. It made sense as the home for a complete account view, yet it forced every silver job that only needed an account key, a creation timestamp, or a privacy flag to wait on the full build.
Here the method was deliberately boring and effective. We audited which columns each downstream consumer actually read. Most needed a handful. Almost none needed the full joined row. From that audit we extracted silver modules:
A core identity spine that could run as soon as the primary account sources were ready.
A person-resolution module for consumers that needed stable person keys.
A sparse privacy-flags module that marketing and export pipelines could consume without waiting on the full account build.
An attributes module for enrollment and related flags used when composing gold.
Gold itself became a thin composition — joins across those silver modules — with the same outward schema for BI and policy consumers that still expected the gold contract. Downstream transforms and DAG sensors were rewired to the smallest sufficient module. High-volume silver jobs on the critical path could start earlier. Privacy-sensitive marketing jobs could run from the sparse flags table in parallel with core identity work.
The gains here were smaller than the signup remodel, and that is worth saying plainly. Account modularization shortened waits and cleaned the dependency graph, delivering minutes on its own rather than another multi-hour cliff. Paired with the signup work, it reinforced the same operating principle: gold can stay wide for the humans who read it, while pipelines depend on silver contracts sized to what they actually consume.
How We De-Risked the Remodel
Structural changes fail when they are shipped as a big-bang rewrite of every consumer on the same day. We staged the work.
First we introduced the silver modules and recomposed gold from them, with parity checks against the previous gold outputs. Behavior for existing gold consumers stayed stable while the new tables proved themselves. Only after that did we migrate downstream transforms in batches, with BI-style compare checks on the fields each consumer cared about. Orchestration updates followed the code: sensors and lineage moved from “wait for gold” to “wait for this silver module.”
Two habits mattered more than any single SQL trick:
Column honesty. If a transform only selected three fields from a 250-column table, it did not get to keep the gold dependency out of convenience.
Preserve the public gold interface where analysts, access policies, and monitoring already pointed at it. Modularization stayed an orchestration and contract change, leaving existing dashboards and their names untouched.
That sequence kept trust high while the runtime fell. Nobody had to choose between a faster DAG and a broken metric store.
What This Means If Your Gold Layer Feels “Done”
Medallion architecture is often sold as a storage taxonomy: bronze raw, silver cleaned, gold served. That framing is only half the story. On a real platform, medallion is also a dependency design. If gold becomes the shared trunk for every internal consumer, you recreate the monolith you thought you left behind — only now it is a wide table with a polite name.
The anti-pattern to watch for looks like this:
A gold entity keeps growing to accommodate DS, finance, marketing, product, and operations in one row.
New transforms reference that gold table by default because it is “the source of truth.”
The daily critical path lengthens even when individual queries are well written.
Teams ask for more compute instead of fewer, more precise contracts.
The corrective pattern is equally concrete:
Materialize the CTEs that already describe natural modules.
Publish silver contracts with clear grain and ownership.
Rewire pipeline consumers to the smallest sufficient layer.
Keep gold as the curated product surface for the people and systems that need the full entity.
When we applied that pattern on this engagement, a seven-to-nine-hour daily ETL became a three-to-four-hour run — roughly half the critical-path time. The payoff was concrete: a shorter recovery window when something failed, lower overnight compute occupancy, and room to peel lighter workloads off the main trunk, all because gold stopped being a bottleneck disguised as a best practice.
Closing
I still like wide gold tables. Analysts should not have to reconstruct a business entity from six silver joins every morning. What I no longer accept is the idea that pipelines must wait on that same width. The work described here came down to a disciplined remodeling of medallion boundaries: materialize the modules, rewire the dependents, measure the critical path, and leave gold for the consumers that actually need gold.
If your overnight critical path is quietly drifting toward a seven-to-nine-hour run, look at the tables everyone depends on before you look at the cluster size — especially if a mid-pipeline failure is what turns that length into a stakeholder queue. The cheapest hour you can buy is often the one you stop waiting for.



