Part 7 — Implementing the Warehouse: Snowflake, Fabric, BigQuery, Synapse, and Redshift

Part 7 — Implementing the Warehouse: Snowflake, Fabric, BigQuery, Synapse, and Redshift – SQLYARD
Deep Technical Series — Building a Modern Data Warehouse and Lakehouse

Part 7 — Implementing the Warehouse: Snowflake, Fabric, BigQuery, Synapse, and Redshift


Part 7 of 14 — Deep Technical Series: Building a Modern Data Warehouse and Lakehouse. Series index (Part 0) · ← Part 6: SCD Types and Fact Loading

Parts 1 through 6 gave you the dimensional model, the medallion architecture, the ingestion pipelines, and the transformation logic. Now it is time to implement the physical warehouse — the compute and storage engine where your Gold layer actually lives and where BI tools connect.

This part covers the physical implementation details for each major cloud warehouse: table design decisions, storage organization, compute configuration, connection setup, and the platform-specific tuning that separates a warehouse that performs well from one that is slow and expensive. Every platform has different physical mechanisms — clustering keys, distribution strategies, columnstore indexes, partitioning — and choosing the wrong one for your workload has a direct, measurable impact on query time and cost.

How this connects to the series: The dimensional model from Part 1 and the Gold layer transformations from Parts 5 and 6 are implemented here as physical tables on your chosen warehouse platform. Part 8 covers lakehouse implementation (Fabric Lakehouse, Databricks, AWS Iceberg). Part 9 adds the semantic layer on top of the physical Gold tables built here.

1 Physical Warehouse Design Principles Beginner

Before diving into platform-specific implementation, three universal principles apply regardless of which warehouse you choose. Getting these right at design time prevents expensive redesigns later.

Principle 1: Design for the Query, Not the Load

Warehouse physical design decisions — clustering keys, distribution keys, sort keys, partition columns — should be driven by how the data will be queried, not how it will be loaded. The most common analytical query pattern on a fact table (filter by date, group by customer or product) should inform every physical design decision. Design for reads first because reads are what BI tools and analysts do constantly. Writes happen once per pipeline run.

Principle 2: Small Dimensions, Large Facts

Dimension tables are typically small — thousands to millions of rows. Fact tables are large — hundreds of millions to billions of rows. Physical optimization effort should be concentrated on fact tables. A poorly designed dimension table rarely causes performance problems. A poorly designed fact table with no partitioning or clustering causes every dashboard query to be slow and expensive.

Principle 3: Storage and Compute Are Separate on Modern Warehouses

Snowflake, BigQuery, Fabric, Redshift RA3, and Synapse all separate storage from compute. Storage is cheap and scales independently. Compute is what costs money and what you tune for performance. Scaling up compute (larger warehouse, more slots, more DWUs) is the fastest way to improve query performance when physical design alone is not enough — but it is not a substitute for correct physical design.

2 Columnstore vs Rowstore — What Every Warehouse Uses Beginner

All modern analytical warehouses store data in columnar format — data is organized by column rather than by row. This is fundamental to why warehouses perform well for analytics and why OLTP databases (row-oriented) are not appropriate for analytical workloads.

In a columnar store, a query that selects 3 columns from a 100-column table reads only those 3 columns from disk. In a row store, the same query reads all 100 columns for every row and discards 97 of them. At billion-row scale, this difference is the difference between a 2-second query and a 20-minute query.

CharacteristicColumnstore (Analytical)Rowstore (Transactional)
Storage layoutData organized by columnData organized by row
CompressionExcellent — similar values compress togetherModerate — mixed values per row block
Aggregation speedVery fast — scans one column in memorySlow — reads full rows to extract one column
Point lookup speedSlower — must read column filesFast — row is co-located
Best forAnalytics, BI, aggregations, wide scansOLTP, point lookups, updates, deletes
Used bySnowflake, BigQuery, Redshift, Synapse, Fabric WarehouseSQL Server OLTP, MySQL, PostgreSQL

3 Snowflake

Snowflake Intermediate

Snowflake uses a unique micro-partition architecture. Data is automatically divided into compressed micro-partitions of 50–500 MB each when loaded. Each micro-partition stores metadata about the min and max values of every column — enabling the query engine to skip entire micro-partitions that cannot contain rows matching a WHERE clause. This automatic pruning happens without any explicit partitioning configuration on small to medium tables.

Virtual Warehouses — Compute Configuration

A Virtual Warehouse is a named cluster of compute resources. You create separate warehouses for different workloads — ETL loading, BI queries, data science — and size each independently. Warehouses auto-suspend when idle (billing stops) and auto-resume when a query arrives (billing resumes within seconds).

-- Create separate virtual warehouses for different workload types
-- ETL warehouse: larger, runs during pipeline windows only
CREATE WAREHOUSE etl_warehouse
    WAREHOUSE_SIZE   = 'LARGE'
    AUTO_SUSPEND     = 120        -- suspend after 2 minutes idle
    AUTO_RESUME      = TRUE
    INITIALLY_SUSPENDED = TRUE;

-- BI warehouse: smaller, runs during business hours
CREATE WAREHOUSE bi_warehouse
    WAREHOUSE_SIZE   = 'SMALL'
    AUTO_SUSPEND     = 60
    AUTO_RESUME      = TRUE
    MIN_CLUSTER_COUNT = 1
    MAX_CLUSTER_COUNT = 3         -- multi-cluster: scales for concurrent BI users
    SCALING_POLICY   = 'ECONOMY';

-- Data science warehouse: medium, on-demand
CREATE WAREHOUSE ds_warehouse
    WAREHOUSE_SIZE   = 'MEDIUM'
    AUTO_SUSPEND     = 300
    AUTO_RESUME      = TRUE;

Clustering Keys — For Large Fact Tables

-- Snowflake automatically handles physical organization on small/medium tables
-- For large fact tables (100M+ rows) add explicit clustering keys
-- to improve micro-partition pruning on common filter columns

-- Add clustering key on the most common filter columns
ALTER TABLE gold.fct_order_line
CLUSTER BY (order_date_key, customer_key);

-- Check if clustering is providing benefit
SELECT SYSTEM$CLUSTERING_INFORMATION('gold.fct_order_line');
-- Look for: average_overlaps close to 0 = good clustering
-- average_depth close to 1 = excellent
-- average_depth > 6 = consider re-clustering

-- Automatic clustering: Snowflake re-clusters as new data arrives
ALTER TABLE gold.fct_order_line
CLUSTER BY (order_date_key, customer_key)
  COMMENT = 'Automatic re-clustering enabled';

-- Check clustering cost
SELECT *
FROM SNOWFLAKE.ACCOUNT_USAGE.AUTOMATIC_CLUSTERING_HISTORY
WHERE START_TIME >= DATEADD('day', -7, CURRENT_TIMESTAMP())
ORDER BY START_TIME DESC;

Gold Layer DDL — Snowflake

-- Snowflake: complete Gold dimension and fact table setup
-- Using dedicated Gold schema and warehouse-optimized DDL

CREATE SCHEMA IF NOT EXISTS gold;

-- Dimension: no explicit clustering needed (small table, auto-pruning handles it)
CREATE TABLE gold.dim_customer (
    customer_key         BIGINT         AUTOINCREMENT PRIMARY KEY,
    customer_natural_key VARCHAR(100)   NOT NULL,
    customer_name        VARCHAR(200),
    email                VARCHAR(200),
    city                 VARCHAR(100),
    state_province       VARCHAR(100),
    country              VARCHAR(100),
    loyalty_tier         VARCHAR(50),
    effective_from       DATE           NOT NULL,
    effective_to         DATE           NOT NULL,
    is_current           TINYINT        NOT NULL,
    CONSTRAINT uq_dim_customer_active
        UNIQUE (customer_natural_key, is_current)
)
DATA_RETENTION_TIME_IN_DAYS = 7;    -- time travel for 7 days

-- Fact table: clustering on most common filter columns
CREATE TABLE gold.fct_order_line (
    order_line_key   BIGINT         AUTOINCREMENT PRIMARY KEY,
    order_natural_key VARCHAR(100)  NOT NULL,
    order_date_key   INT            NOT NULL,
    customer_key     BIGINT         NOT NULL,
    product_key      BIGINT         NOT NULL,
    store_key        BIGINT         NOT NULL,
    quantity         INT            NOT NULL,
    unit_price       DECIMAL(18,2)  NOT NULL,
    discount_amount  DECIMAL(18,2)  NOT NULL DEFAULT 0,
    extended_amount  DECIMAL(18,2)  NOT NULL
)
CLUSTER BY (order_date_key, customer_key)
DATA_RETENTION_TIME_IN_DAYS = 7;

4 Microsoft Fabric Warehouse

Microsoft Fabric Intermediate

Microsoft Fabric Warehouse is a fully managed T-SQL warehouse built on the same Synapse engine but deeply integrated with OneLake and Power BI. It stores data as Delta Parquet files in OneLake, exposes a T-SQL interface for querying and loading, and integrates natively with Power BI through Direct Lake mode — allowing Power BI reports to query the warehouse data directly from OneLake without importing it into a separate semantic model cache.

Fabric Warehouse vs Fabric Lakehouse

Fabric provides two distinct SQL endpoints for analytics, and choosing the right one matters for your architecture.

Fabric WarehouseFabric Lakehouse SQL Endpoint
Write methodT-SQL INSERT, COPY INTO, pipelinesSpark (PySpark, SQL), notebooks
DDL supportFull T-SQL DDL — CREATE, ALTER, DROPRead-only SQL — tables managed by Spark
Transaction supportFull ACID transactionsACID via Delta, no explicit transactions
Best forGold dimensional models, SQL-first teams, BI workloadsSilver and Bronze, Spark-heavy pipelines, ML
Power BI Direct LakeSupportedSupported

Gold Layer DDL — Fabric Warehouse

-- Fabric Warehouse: T-SQL Gold layer setup
-- Familiar SQL Server / Synapse syntax

-- Create Gold schema
CREATE SCHEMA gold;
GO

-- Dimension table: standard T-SQL with IDENTITY surrogate key
CREATE TABLE gold.DimCustomer (
    CustomerKey         BIGINT          IDENTITY(1,1)  NOT NULL,
    CustomerNaturalKey  VARCHAR(100)    NOT NULL,
    CustomerName        VARCHAR(200),
    Email               VARCHAR(200),
    City                VARCHAR(100),
    StateProvince       VARCHAR(100),
    Country             VARCHAR(100),
    LoyaltyTier         VARCHAR(50),
    EffectiveFrom       DATE            NOT NULL,
    EffectiveTo         DATE            NOT NULL,
    IsCurrent           TINYINT         NOT NULL,
    CONSTRAINT PK_DimCustomer PRIMARY KEY NONCLUSTERED (CustomerKey)
);

-- Fact table
CREATE TABLE gold.FctOrderLine (
    OrderLineKey     BIGINT          IDENTITY(1,1)  NOT NULL,
    OrderNaturalKey  VARCHAR(100)    NOT NULL,
    OrderDateKey     INT             NOT NULL,
    CustomerKey      BIGINT          NOT NULL,
    ProductKey       BIGINT          NOT NULL,
    StoreKey         BIGINT          NOT NULL,
    Quantity         INT             NOT NULL,
    UnitPrice        DECIMAL(18,2)   NOT NULL,
    DiscountAmount   DECIMAL(18,2)   NOT NULL  DEFAULT 0,
    ExtendedAmount   DECIMAL(18,2)   NOT NULL,
    CONSTRAINT PK_FctOrderLine PRIMARY KEY NONCLUSTERED (OrderLineKey)
);

Loading Data — COPY INTO

-- Fabric Warehouse: COPY INTO from OneLake files (fastest bulk load method)
COPY INTO gold.DimCustomer (
    CustomerNaturalKey, CustomerName, Email,
    City, StateProvince, Country, LoyaltyTier,
    EffectiveFrom, EffectiveTo, IsCurrent
)
FROM 'https://onelake.dfs.fabric.microsoft.com/workspace/lakehouse.Lakehouse/Tables/gold_dim_customer/'
WITH (
    FILE_TYPE = 'PARQUET',
    CREDENTIAL = (IDENTITY = 'Managed Identity')
);

-- Verify load
SELECT COUNT(*) AS RowCount FROM gold.DimCustomer;
SELECT TOP 10 * FROM gold.DimCustomer ORDER BY CustomerKey DESC;

Connecting Power BI — Direct Lake Mode

-- Fabric Warehouse: Power BI Direct Lake connection
-- In Power BI Desktop or Fabric Portal:
-- 1. New report → OneLake data hub → Select your Fabric Warehouse
-- 2. Choose tables: gold.DimCustomer, gold.DimDate, gold.FctOrderLine
-- 3. Build relationships in model view (CustomerKey, DateKey)
-- 4. Create DAX measures:

-- Revenue measure (DAX):
-- Revenue = SUM(FctOrderLine[ExtendedAmount])

-- Direct Lake mode: Power BI reads from OneLake Delta files directly
-- No data import, no scheduled refresh required
-- Report always reflects current Gold layer data

-- Check Direct Lake mode is active:
-- In Power BI service: dataset settings → Storage mode should show "Direct Lake"

5 Google BigQuery

BigQuery Intermediate

BigQuery is a fully serverless columnar warehouse. There are no clusters, no virtual warehouses, and no infrastructure to configure. You create tables and run queries. BigQuery charges per query based on bytes scanned — making partitioning and clustering not just a performance optimization but a cost control mechanism. A query on an unpartitioned 10 TB table scans 10 TB and costs roughly $50. The same query on a date-partitioned table that filters to one day scans 28 GB and costs $0.14.

Gold Layer DDL — BigQuery

-- BigQuery: Gold layer with partitioning and clustering
-- Dataset (equivalent to schema) must be created first in the console or via API

-- Dimension table: no partitioning needed (small table)
CREATE TABLE `project.gold.dim_customer`
(
    customer_key         INT64,
    customer_natural_key STRING   NOT NULL,
    customer_name        STRING,
    email                STRING,
    city                 STRING,
    state_province       STRING,
    country              STRING,
    loyalty_tier         STRING,
    effective_from       DATE     NOT NULL,
    effective_to         DATE     NOT NULL,
    is_current           INT64    NOT NULL
)
OPTIONS (description = 'Customer dimension with SCD Type 2 history');

-- Fact table: partition by date, cluster by most common join columns
CREATE TABLE `project.gold.fct_order_line`
(
    order_line_key    INT64,
    order_natural_key STRING        NOT NULL,
    order_date        DATE          NOT NULL,
    order_date_key    INT64         NOT NULL,
    customer_key      INT64         NOT NULL,
    product_key       INT64         NOT NULL,
    store_key         INT64         NOT NULL,
    quantity          INT64         NOT NULL,
    unit_price        NUMERIC,
    discount_amount   NUMERIC,
    extended_amount   NUMERIC
)
PARTITION BY order_date                    -- partition pruning on date filters
CLUSTER BY customer_key, product_key       -- clustering reduces scan within partitions
OPTIONS (
    partition_expiration_days = NULL,      -- no expiration -- keep all history
    description = 'Order line fact table'
);

Cost Management — Query Validation

-- Always validate bytes scanned before running expensive queries
-- In BigQuery console: "This query will process X bytes" shown before execution
-- Use INFORMATION_SCHEMA to analyze table and partition sizes

-- Check partition sizes for the fact table
SELECT
    partition_id,
    total_rows,
    ROUND(total_logical_bytes / POW(1024,3), 2)   AS size_gb,
    last_modified_time
FROM `project.gold.INFORMATION_SCHEMA.PARTITIONS`
WHERE table_name = 'fct_order_line'
ORDER BY partition_id DESC
LIMIT 30;

-- Estimate query cost before running (use dry run)
-- In BigQuery CLI:
-- bq query --dry_run "SELECT SUM(extended_amount) FROM project.gold.fct_order_line
--   WHERE order_date BETWEEN '2025-01-01' AND '2025-12-31'"
-- Outputs: "Query successfully validated. Assuming the tables are not modified,
--   running this query will process 2.1 GB of data."

BigQuery BI Engine — Sub-Second BI Queries

-- BigQuery BI Engine caches frequently queried data in fast in-memory storage
-- Enables sub-second response times for Looker Studio and Looker dashboards
-- Enable via the BigQuery console: BI Engine → Create Reservation

-- After enabling BI Engine, queries from connected BI tools automatically
-- use the in-memory cache when the data fits
-- Monitor BI Engine usage:
SELECT
    job_id,
    bi_engine_statistics.bi_engine_mode,
    bi_engine_statistics.bi_engine_reasons,
    total_bytes_processed,
    total_slot_ms
FROM `region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE creation_time > TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)
AND bi_engine_statistics.bi_engine_mode != 'DISABLED'
ORDER BY creation_time DESC
LIMIT 20;

6 Azure Synapse Dedicated SQL Pool

Azure Synapse Intermediate

Azure Synapse Dedicated SQL Pool is a massively parallel processing (MPP) warehouse that distributes data and computation across multiple compute nodes. Unlike Snowflake or BigQuery which handle physical distribution automatically, Synapse requires you to explicitly define a distribution strategy for every table. Getting distribution right is the single most important performance decision in a Synapse implementation.

Synapse vs Fabric: Microsoft Fabric Warehouse is the strategic direction for new analytical workloads on Azure. Synapse Dedicated SQL Pool remains supported and is appropriate for organizations with existing Synapse investments or migrating large on-premises SQL data warehouses. For new projects on Azure, evaluate Fabric Warehouse first. Both products will coexist and be supported long-term.

Distribution Strategies

StrategyHow It WorksBest ForAvoid When
HASH(column)Rows distributed by hash of column value — same column value always goes to same nodeLarge fact tables, large dimensions with frequent joinsLow-cardinality columns, columns with data skew
ROUND_ROBINRows distributed evenly across all nodes regardless of valueStaging tables, tables with no obvious distribution keyTables that join to other tables frequently — causes data movement
REPLICATEFull copy of table on every compute node — no data movement on joinSmall dimension tables (under 2 GB)Large tables — replication overhead exceeds benefit

Gold Layer DDL — Synapse Dedicated SQL Pool

-- Synapse: Gold layer with optimal distribution and columnstore indexes
-- Distribution strategy is defined at CREATE TABLE time
-- Cannot be changed without dropping and recreating the table

-- Small dimension: REPLICATE for join-free dimension lookups
CREATE TABLE gold.DimCustomer
(
    CustomerKey         BIGINT          NOT NULL,
    CustomerNaturalKey  VARCHAR(100)    NOT NULL,
    CustomerName        VARCHAR(200),
    Email               VARCHAR(200),
    City                VARCHAR(100),
    StateProvince       VARCHAR(100),
    Country             VARCHAR(100),
    LoyaltyTier         VARCHAR(50),
    EffectiveFrom       DATE            NOT NULL,
    EffectiveTo         DATE            NOT NULL,
    IsCurrent           TINYINT         NOT NULL
)
WITH (
    DISTRIBUTION = REPLICATE,          -- full copy on every node -- fast dimension joins
    CLUSTERED COLUMNSTORE INDEX        -- columnar compression for analytics
);

-- Large fact table: HASH on most common join column
-- Match distribution key to the largest dimension you join to most frequently
CREATE TABLE gold.FctOrderLine
(
    OrderLineKey     BIGINT          NOT NULL,
    OrderNaturalKey  VARCHAR(100)    NOT NULL,
    OrderDateKey     INT             NOT NULL,
    CustomerKey      BIGINT          NOT NULL,     -- HASH on this column
    ProductKey       BIGINT          NOT NULL,
    StoreKey         BIGINT          NOT NULL,
    Quantity         INT             NOT NULL,
    UnitPrice        DECIMAL(18,2)   NOT NULL,
    DiscountAmount   DECIMAL(18,2)   NOT NULL,
    ExtendedAmount   DECIMAL(18,2)   NOT NULL
)
WITH (
    DISTRIBUTION = HASH(CustomerKey),  -- distribute by CustomerKey
    CLUSTERED COLUMNSTORE INDEX        -- columnar storage
);

Checking Distribution Quality

-- Verify distribution is even -- skew causes some nodes to do much more work
-- This query shows row count per distribution bucket (60 buckets in Synapse)
SELECT
    pdw_node_id,
    distribution_id,
    rows,
    data_space_used_mb
FROM sys.dm_pdw_nodes_db_partition_stats
WHERE object_id = OBJECT_ID('gold.FctOrderLine')
ORDER BY rows DESC;
-- Ideal: all distributions have similar row counts
-- Red flag: one distribution has 10x more rows than others = data skew
-- Fix: choose a different distribution key with better cardinality

-- Check for data movement in queries (data movement = performance problem)
EXPLAIN
SELECT d.LoyaltyTier, SUM(f.ExtendedAmount)
FROM gold.FctOrderLine f
JOIN gold.DimCustomer d ON f.CustomerKey = d.CustomerKey
GROUP BY d.LoyaltyTier;
-- Look for "BroadcastMoveOperation" or "ShuffleMoveOperation" in the plan
-- These indicate data is being moved between nodes -- expensive
-- Eliminate by ensuring joined tables share the same distribution key

7 Amazon Redshift

Amazon Redshift Intermediate

Amazon Redshift is AWS’s MPP columnar warehouse. RA3 nodes separate compute and storage — compute nodes can be scaled independently of the managed storage in Amazon S3 (via Redshift Managed Storage). Redshift Spectrum extends the warehouse by enabling direct SQL queries against S3 data without loading it into Redshift, creating a natural hybrid warehouse-plus-lake architecture.

Distribution and Sort Keys

Like Synapse, Redshift requires explicit distribution and sort key decisions at table creation. Distribution style controls how rows are spread across compute nodes. Sort key controls the physical ordering of rows on disk, which enables efficient range scans.

Gold Layer DDL — Redshift

-- Redshift: Gold layer with distribution and sort keys

-- Small dimension: ALL distribution (equivalent to Synapse REPLICATE)
CREATE TABLE gold.dim_customer (
    customer_key         BIGINT          NOT NULL ENCODE AZ64,
    customer_natural_key VARCHAR(100)    NOT NULL ENCODE ZSTD,
    customer_name        VARCHAR(200)    ENCODE ZSTD,
    email                VARCHAR(200)    ENCODE ZSTD,
    city                 VARCHAR(100)    ENCODE ZSTD,
    state_province       VARCHAR(100)    ENCODE ZSTD,
    country              VARCHAR(100)    ENCODE ZSTD,
    loyalty_tier         VARCHAR(50)     ENCODE ZSTD,
    effective_from       DATE            NOT NULL ENCODE AZ64,
    effective_to         DATE            NOT NULL ENCODE AZ64,
    is_current           SMALLINT        NOT NULL ENCODE AZ64
)
DISTSTYLE ALL                    -- copy to all nodes for fast joins
SORTKEY (customer_natural_key);  -- sorted for efficient natural key lookups

-- Large fact table: DISTKEY on highest-cardinality join column
CREATE TABLE gold.fct_order_line (
    order_line_key   BIGINT          NOT NULL ENCODE AZ64,
    order_natural_key VARCHAR(100)   NOT NULL ENCODE ZSTD,
    order_date_key   INT             NOT NULL ENCODE AZ64,
    customer_key     BIGINT          NOT NULL ENCODE AZ64,
    product_key      BIGINT          NOT NULL ENCODE AZ64,
    store_key        BIGINT          NOT NULL ENCODE AZ64,
    quantity         INT             NOT NULL ENCODE AZ64,
    unit_price       DECIMAL(18,2)   NOT NULL ENCODE AZ64,
    discount_amount  DECIMAL(18,2)   NOT NULL ENCODE AZ64,
    extended_amount  DECIMAL(18,2)   NOT NULL ENCODE AZ64
)
DISTKEY (customer_key)                         -- co-locate with dim_customer ALL distribution
COMPOUND SORTKEY (order_date_key, customer_key); -- sort for date-range + customer queries

Redshift Spectrum — Query S3 Bronze Directly

-- Redshift Spectrum: query S3 Bronze/Silver data without loading it into Redshift
-- Create an external schema pointing to your S3 data

CREATE EXTERNAL SCHEMA bronze_s3
FROM DATA CATALOG
DATABASE 'bronze'
IAM_ROLE 'arn:aws:iam::account:role/RedshiftSpectrumRole'
CREATE EXTERNAL DATABASE IF NOT EXISTS;

-- Create external table mapping to Bronze Parquet files on S3
CREATE EXTERNAL TABLE bronze_s3.crm_customer (
    customer_id     VARCHAR(100),
    customer_name   VARCHAR(200),
    email           VARCHAR(200),
    city            VARCHAR(100),
    updated_at      TIMESTAMP
)
STORED AS PARQUET
LOCATION 's3://your-bucket/bronze/crm/customer/'
TABLE PROPERTIES ('has_encrypted_data' = 'false');

-- Now join Gold (in Redshift) to Bronze (in S3) in a single query
-- Useful for backfill, audit, and historical analysis without data movement
SELECT
    g.customer_name    AS current_name,
    b.customer_name    AS original_name,
    g.loyalty_tier
FROM gold.dim_customer g
JOIN bronze_s3.crm_customer b
    ON g.customer_natural_key = b.customer_id
WHERE g.is_current = 1
AND g.customer_name <> b.customer_name;  -- find customers whose name changed

8 Warehouse Sizing and Concurrency Intermediate

Warehouse size affects query execution time linearly on most platforms — doubling the compute roughly halves execution time for CPU-bound queries. But sizing decisions also affect cost. The goal is the smallest warehouse that meets your SLA requirements, not the largest warehouse that makes queries as fast as possible.

PlatformCompute UnitScaling ModelConcurrency Approach
SnowflakeVirtual Warehouse (XS → 6XL)Manual or auto-scale per warehouseMulti-cluster warehouse scales horizontally for concurrent users
Fabric WarehouseCapacity Units (CUs)Fabric capacity auto-scales within purchased limitsWorkload groups manage resource allocation between pipelines and BI
BigQuerySlots (100 per job default)Serverless auto-scale (on-demand) or reserved slotsSlot reservations with workload management for enterprise
SynapseData Warehouse Units (DWUs)Manual scaling (pause/resume)Workload groups and classifiers manage concurrency
RedshiftNode type and countResize cluster (RA3 elastic resize)Concurrency scaling adds clusters automatically for burst workloads

Sizing Guidance

  • Start small and measure. Begin with the smallest size that completes your critical queries within SLA. Measure actual execution times with realistic data volumes before committing to a larger size.
  • Separate ETL and BI compute. On Snowflake, use separate virtual warehouses for ETL and BI. This prevents a large pipeline load from degrading dashboard query response times for users. The same principle applies on BigQuery with reservation assignments and on Synapse with workload groups.
  • Auto-suspend aggressively. On Snowflake, set auto-suspend to 60–120 seconds for most warehouses. A suspended warehouse resumes in 1–5 seconds. Running warehouses idle costs real money at no benefit.

9 Connecting BI Tools to the Warehouse Beginner

Every major warehouse exposes a standard ODBC/JDBC endpoint that all BI tools can connect to. The connection details vary by platform but the process is the same: install the driver, provide the endpoint, authenticate, and select tables.

PlatformConnection String FormatAuthentication
Snowflakeaccount.snowflakecomputing.comUsername/password, SSO, key-pair
Fabric Warehouseworkspace.sql.fabric.microsoft.comAzure Active Directory / Entra ID
BigQueryProject ID via BigQuery connectorGCP Service Account or OAuth
Synapseserver.sql.azuresynapse.netAzure Active Directory or SQL auth
Redshiftcluster.region.redshift.amazonaws.com:5439IAM or database username/password

Always connect BI tools to Gold, never to Bronze or Silver. BI tools connecting directly to Silver or Bronze bypass the dimensional model, the surrogate key structure, the SCD Type 2 history logic, and the conformed dimensions. Power BI, Looker, Tableau, and every other BI tool should connect to Gold tables exclusively. The semantic layer built in Part 9 sits on top of Gold and provides the final governed layer for BI tools.

10 Platform Comparison Matrix Beginner

Feature Snowflake Fabric Warehouse BigQuery Synapse Redshift
Storage model Micro-partitions (auto) Delta on OneLake Colossus (auto) Columnstore pages Columnar slices
Physical design effort Low — mostly automatic Low — auto columnstore Medium — partition/cluster required High — distribution keys manual High — dist + sort keys manual
Compute scaling On-demand, per warehouse Auto within capacity Fully serverless auto Manual pause/resume Manual resize + concurrency scaling
Pricing model Per compute-second + storage Capacity units (CUs) Per byte scanned or slots Per DWU-hour Per node-hour + managed storage
T-SQL compatibility Snowflake SQL dialect Full T-SQL Standard SQL dialect Full T-SQL PostgreSQL dialect
Power BI integration DirectQuery or import Native Direct Lake DirectQuery or import DirectQuery or import DirectQuery or import
Open format storage Iceberg (optional) Delta (native) BigLake external tables Proprietary Spectrum + S3 (external)

11 Workshops

Novice

Set Up Your Gold Schema

  • Choose one platform (Snowflake, Fabric, or BigQuery)
  • Create the Gold schema and dimension tables using the DDL from this part
  • Insert the unknown key row (-1) for each dimension
  • Load Silver data into Gold using INSERT SELECT
  • Connect SSMS or your BI tool to the Gold schema
  • Run a revenue by month query joining FctOrderLine to DimDate

Intermediate

Tune Physical Design

  • Load 10 million rows into your Gold fact table
  • Run a revenue by customer query and record execution time
  • On Snowflake: add a clustering key and re-run — compare times
  • On BigQuery: add a partition and clustering key — compare bytes scanned
  • On Synapse/Redshift: check distribution skew — redistribute if needed
  • Document the before/after performance improvement

Advanced

Separate ETL and BI Compute

  • Create two separate compute resources (warehouses, slots, or DWUs)
  • Run an ETL pipeline load on the ETL compute resource
  • Simultaneously run BI dashboard queries on the BI compute resource
  • Verify the ETL load does not affect BI query response times
  • On Snowflake: configure multi-cluster BI warehouse with auto-scale
  • Monitor cost and performance metrics for both workloads separately

References

Up next → Part 8: Implementing the Lakehouse: Fabric, Databricks, and AWS — Delta Lake ACID, Iceberg table design, Fabric Lakehouse SQL endpoint, Databricks Unity Catalog, OneLake, and multi-cloud lakehouse patterns.

Discover more from SQLYARD

Subscribe to get the latest posts sent to your email.

Leave a Reply

Discover more from SQLYARD

Subscribe now to keep reading and get access to the full archive.

Continue reading