Concept 01
Indexing Strategy for Data Vault
🧠 ELI5 — Explain Like I’m Five
Data Vault tables have predictable access patterns. You always look up by hash key. You always filter by load date. You often scan for NULL load_end_dates. If you design indexes around these patterns — not randomly — your queries will be fast even on billions of rows.
Hub Table Indexes
Hubs are the simplest case. Every Hub has exactly two index needs:
- Primary key index on HUB_{ENTITY}_HK — created automatically by the PK constraint. Supports all join operations from Links and Satellites back to the Hub.
- Index on {ENTITY}_BK — the business key lookup. When a staging row arrives with a raw business key rather than a pre-hashed HK, this index supports the “does this BK already exist?” check without a full Hub scan.
Link Table Indexes
Links participate in multi-directional traversal queries. You need:
- PK index on LINK_{NAME}_HK — the link’s own surrogate key, created automatically.
- Individual index on each Hub FK (HUB_A_HK, HUB_B_HK) — when a query starts from one Hub and needs to find all linked entities, the FK index enables an efficient nested-loop join without scanning the entire Link table.
- Composite index on (HUB_A_HK, HUB_B_HK) — for bi-directional traversal queries that filter on both keys simultaneously.
Satellite Table Indexes
Satellites carry the most data and the most query traffic. Three index types are essential:
- Composite PK on (HUB_{ENTITY}_HK, LOAD_DATE) — the standard satellite primary key. Enables point-in-time lookups by entity and exact timestamp.
- Filtered/partial index on (HUB_{ENTITY}_HK) WHERE LOAD_END_DATE IS NULL — the single most impactful index in any vault. Current-record queries are the dominant workload, and a filtered index covering only IS NULL rows is dramatically smaller than a full index and serves this exact pattern exclusively.
- Index on HASH_DIFF — supports the satellite loader’s change-detection query, which compares incoming HashDiff values against the current satellite HashDiff to determine whether an insert is needed.
-- ── HUB INDEXES ───────────────────────────────────────────── CREATE UNIQUE INDEX idx_hub_customer_pk ON HUB_CUSTOMER (HUB_CUSTOMER_HK); CREATE INDEX idx_hub_customer_bk ON HUB_CUSTOMER (CUSTOMER_BK); -- ── LINK INDEXES ───────────────────────────────────────────── CREATE UNIQUE INDEX idx_link_co_pk ON LINK_CUSTOMER_ORDER (LINK_CUSTOMER_ORDER_HK); CREATE INDEX idx_link_co_customer ON LINK_CUSTOMER_ORDER (HUB_CUSTOMER_HK); CREATE INDEX idx_link_co_order ON LINK_CUSTOMER_ORDER (HUB_ORDER_HK); CREATE INDEX idx_link_co_composite ON LINK_CUSTOMER_ORDER (HUB_CUSTOMER_HK, HUB_ORDER_HK); -- ── SATELLITE INDEXES ──────────────────────────────────────── -- Filtered index: covers the dominant "current record" query pattern CREATE INDEX idx_sat_cd_current ON SAT_CUSTOMER_DEMOGRAPHICS (HUB_CUSTOMER_HK) WHERE LOAD_END_DATE IS NULL; -- partial index: current records only -- HashDiff index: change detection during satellite loader CREATE INDEX idx_sat_cd_hdiff ON SAT_CUSTOMER_DEMOGRAPHICS (HUB_CUSTOMER_HK, HASH_DIFF); -- ── CLOUD PLATFORMS (Snowflake / BigQuery) ─────────────────── -- Traditional B-tree indexes replaced by clustering keys ALTER TABLE SAT_CUSTOMER_DEMOGRAPHICS CLUSTER BY (HUB_CUSTOMER_HK); -- co-locate all rows for the same entity
Cloud platforms (Snowflake, BigQuery, Redshift): Traditional row-level B-tree indexes don’t exist. Instead use clustering keys on Snowflake or partition & cluster columns on BigQuery. The effect is equivalent — data for the same hash key is physically co-located in the same micro-partition, enabling micro-partition pruning that cuts I/O dramatically. On Snowflake, automatic clustering maintenance runs in the background and requires no manual index rebuilds.