Hubs, links, and satellites: an auditable, scalable, parallel-loadable pattern for enterprise data warehouses.
⏱ 27 min readTopics chapter readerLevel · Dimensional & Warehouse
01 · Orientation
What You'll Master Here
Separate keys (hubs), relationships (links), and context (satellites) so each loads independently, insert-only, fully audited, and never needs rewriting when a source changes.
⏱ 4 min · Topic 1 of 11
Chapter 12 placed Data Vault beside Inmon and Kimball. This chapter is about how it actually works, because the mechanics are exact and most of what goes wrong with a vault is a mechanical mistake rather than a strategic one.
Step the walkthrough below through one row of an aircraft maintenance feed. Three business keys become hubs, then a link, then satellites — and the last step shows what it costs to put them back together again.
Core mental model
Separate keys (hubs), relationships (links), and context (satellites) so each loads independently, insert-only, fully audited, and never needs rewriting when a source changes.
Why it matters
Data Vault is increasingly common in regulated, multi-source enterprises and in modern ELT stacks. Knowing its mechanics lets you build, query, or evaluate one instead of treating it as a black box.
hub
A table of unique business keys plus a hash key, load date, and source.
link
A table recording a relationship/association between hubs.
satellite
A table of descriptive attributes and their history, attached to a hub or link.
hash key
A hash of the business key used as the surrogate, enabling parallel, lookup-free loads.
One source row, walked through the vaultHalewood Aviation fits a fuel pump to airframe MSN-31842. Follow that single row in, and back out.
One flat row from maint_core
maint_core.fitment_exportsourcesource extract — one row
msn
part_serial
part_desc
condition
cycles_ovh
wo_no
fitted_on
MSN-31842
SN-77-04412
Fuel pump, LH
SVC
1204
WO-2026-1188
2026-06-12
0 · The source rowSeven columns with four different lifetimes in them. The airframe outlives everything on the row. The part serial is stamped into metal and never changes. The condition changes at every inspection. The fitment itself ends the day the pump is pulled. Keep them in one row and the slowest-changing column gets rewritten every time the fastest one moves.
Common mistake
Using Data Vault as the layer business users query directly. It has many tables and joins; it is an integration core, not a consumption model, serve marts on top.
Better habit
Separate keys, relationships, and context into hubs/links/satellites.
Keep every table insert-only and source-stamped.
Build dimensional marts on top for consumption.
Where this chapter starts
Whether Data Vault is the right architecture at all is Chapter 12's question. This chapter assumes the decision is made and teaches the mechanics: what each object holds, what a real source change touches, and what it takes to get an answer back out.
Data Vault splits a model into hubs (keys), links (relationships), and satellites (history), loaded insert-only and source-stamped, for auditability and resilience to source change.
02 · Motivation
Why Data Vault Exists
Data Vault buys auditability, change-resilience, and parallel loading, at the cost of more tables and a required consumption layer.
⏱ 4 min · Topic 2 of 11
Three pressures justify the extra tables. You have to prove exactly what arrived, from where and when, and reproduce any past state. Sources change constantly. Many teams load at once. Where none of those bite, Data Vault is overhead.
The second pressure is worth feeling rather than reading about. Call each of the four changes below before you reveal it: three are absorbed by adding a table and touching nothing that already exists, and the fourth is the one no architecture absorbs cheaply.
Core mental model
Data Vault buys auditability, change-resilience, and parallel loading, at the cost of more tables and a required consumption layer.
Why it matters
Choosing Data Vault is justified by specific pressures (audit, volatile sources, parallel teams). Knowing them precisely keeps you from adopting its complexity without the matching need.
auditability
Proving and reproducing exactly what arrived, from where, and when.
source resilience
Adding/changing a source by extension (new satellite) rather than rewrite.
parallel loading
Loading hubs, links, and satellites independently and concurrently.
Common mistake
Adopting Data Vault without audit, change, or scale pressure. You pay for many extra tables and a consumption layer to solve problems you do not have.
Expecting Data Vault to be query-friendly on its own. Its agility comes from decomposition, which means more joins; consumption needs a mart layer.
Better habit
Adopt Data Vault when audit and source volatility are real.
Add sources as new satellites instead of rewriting tables.
Settle the business key before you build; it is the one change that is never cheap.
Production reality
Banks, insurers, and healthcare data platforms favor Data Vault precisely because regulators demand traceability and sources change constantly. Startups with one stable source rarely need it.
Remember this
Data Vault exists to deliver auditability, resilience to source change, and parallel loading; adopt it when those pressures are real, skip it when they are not, and remember that a change to the business key itself is expensive no matter what you build.
03 · Hubs
Hubs: The Business Keys
A hub = the unique list of one business key, plus its hash key, load date, and source. Identities only, nothing descriptive.
⏱ 4 min · Topic 3 of 11
A hub is the unique list of one business key and almost nothing else: the key, a hash of it, when it first appeared, and which system reported it. No descriptions, no relationships, nothing that can later change.
That austerity is the entire point, because the hub is where every source has to agree on identity. Change the key in the workbench below and watch two systems stop agreeing.
Core mental model
A hub = the unique list of one business key, plus its hash key, load date, and source. Identities only, nothing descriptive.
Why it matters
The hub is the integration point: it is where all sources agree on identity. Getting hubs right, one per business key, attributes excluded, is the foundation everything else hangs on.
business key
The durable, source-independent identifier of an entity (customer_id).
hash key (hub)
A hash of the business key, used as the surrogate across the vault.
record_source
The audit column naming which source a row came from.
NULL sentinel
An agreed literal substituted for NULL before hashing so composite keys stay deterministic — UNKNOWN in many DV2 teams, _dbt_utils_surrogate_key_null_ in dbt (Chapter 3).
A hub holds only business keysworked example
SQL
createtablehub_part(part_hkbyteaprimarykey,-- hash of part_serial (the surrogate)-- bytea = Postgres-native binary hash. CHAR(32) MD5 hex-- (as in Ch. 12's sketch) is equally standard.part_serialtextnotnullunique,-- the business keyload_dtstimestamptznotnull,-- when first seenrecord_sourcetextnotnull-- which system reported it);-- One row per distinct part_serial, ever. No description, no condition,-- no work order. Everything that can change lives elsewhere.insertintohub_partvalues(sha256('SN-77-04412'),'SN-77-04412',now(),'maint_core');
The hub is deliberately bare. Description lives in satellites, relationships in links — so a hub row, once written, never needs updating. Coalesce every key component to an agreed sentinel before hashing: two rows that are both missing a component would otherwise hash identically. Chapter 3 owns the hash-collision math.
Common mistake
Putting descriptive attributes (description, condition) on the hub. The hub stops being a pure key list, and a hub row that has to be updated is no longer a hub; attributes belong in a satellite where their history is tracked.
Creating a hub around a surrogate id from one source. It breaks integration; hubs must be built on the true business key all sources share.
Widening the business key with a column that describes a relationship, not the entity. The same physical thing gets a new identity per relationship, scattering its history across hub rows that can never be rejoined.
Hashing NULL inputs without coalescing first. Different business keys that both have NULL components hash to the same value, silently merging unrelated entities.
Better habit
One hub per business key; identities only.
Coalesce NULL key components to a sentinel before hashing.
Stamp every hub row with load_date and record_source.
Interview note
Interviewers probe: "what happens if you hash a NULL business key component?" The answer — NULL collision produces the same hash for unrelated rows — shows you know the pitfall. Always coalesce to a sentinel first.
A hub is the unique list of one business key with its hash key and audit columns; the key must be the one every source shares, must never change, and must be NULL-coalesced before hashing.
04 · Links
Links: The Relationships
A link = the hash keys of the hubs it connects, plus its own hash key and audit columns. Connections only; description and lifecycle go in a satellite on the link.
⏱ 4 min · Topic 4 of 11
A link records that two or more hubs are associated. Where a dimensional model puts a foreign key on a fact, Data Vault gives the relationship its own table: the hash keys of the participating hubs, its own hash key, and audit columns. Many-to-many needs no special handling, because a link is only ever a set of associations.
That last word does the damage. A link asserts that a pairing happened, never that it is still true — and since links are insert-only, an ended relationship cannot be expressed by deleting the row. Run the same question against the three designs below and watch two of them over-report without erroring.
Core mental model
A link = the hash keys of the hubs it connects, plus its own hash key and audit columns. Connections only; description and lifecycle go in a satellite on the link.
Why it matters
Modeling relationships as first-class, insert-only tables is what lets Data Vault absorb changing associations without restructuring, a key part of its agility, and it makes many-to-many trivial.
link
A table of associations between hubs, modeling a relationship as its own object.
many-to-many by default
Links naturally express M:N because they are just sets of associations.
link satellite
A satellite attached to a link, holding the relationship's attributes/measures.
effectivity satellite
A satellite on a link recording whether the association is currently live — the only way an insert-only link can express "this ended".
A link connects hubs; context and lifecycle live in satellites on the linkworked example
SQL
createtablelink_fitment(fitment_hkbyteaprimarykey,-- hash of the combined business keysairframe_hkbyteanotnullreferenceshub_airframe(airframe_hk),part_hkbyteanotnullreferenceshub_part(part_hk),workorder_hkbyteanotnullreferenceshub_workorder(workorder_hk),load_dtstimestamptznotnull,record_sourcetextnotnull);-- Attributes of the relationship go in a satellite on the link:createtablesat_link_fitment(fitment_hkbyteanotnullreferenceslink_fitment(fitment_hk),load_dtstimestamptznotnull,positiontext,-- 'LH', 'RH', 'APU bay'torque_nmnumeric(6,1),primarykey(fitment_hk,load_dts));-- ...and its lifecycle goes in an effectivity satellite, which is what-- makes "was this still fitted on 1 July?" answerable at all:createtablesat_link_fitment_eff(fitment_hkbyteanotnullreferenceslink_fitment(fitment_hk),load_dtstimestamptznotnull,is_fittedbooleannotnull,-- flips false on removalprimarykey(fitment_hk,load_dts));
The link stores only the connection. Position and torque describe the fitment, so they sit in a satellite; whether the fitment is still live is state, so it sits in an effectivity satellite. The link row itself is written once and never touched again, which is what lets it be loaded in parallel with everything else.
Common mistake
Storing relationship attributes (position, torque) directly on the link. It mixes connection with context; put attributes in a satellite on the link so history is tracked.
Reading a link as the current state of a relationship. A link says the pairing happened, not that it still holds; queries silently over-report until an effectivity satellite is joined.
Recording the relationship as a foreign key on one side's satellite. It can express "attached to" but never "detached from", and it makes the other side of the relationship unqueryable without a full scan.
Better habit
Model every relationship as its own link table.
Keep links to hub keys plus audit columns.
Add an effectivity satellite to any link that can end.
The over-reporting link
Querying a link without its effectivity satellite is the most common wrong answer a vault gives. It returns everything that was ever associated, runs without error, and looks plausible — the numbers are simply too high.
Remember this
A link models a relationship between hubs as its own insert-only table of keys; attributes go in a link satellite and the relationship's lifecycle in an effectivity satellite, because a link alone cannot say that something ended.
05 · Satellites
Satellites: Descriptive History
A satellite = parent hash key + load_dts + attributes + hash_diff. Payload changed → new dated row; payload identical → nothing written. Multi-active satellites add a discriminator to the key so several rows can be live at once.
⏱ 5 min · Topic 5 of 11
A satellite hangs off a hub or a link and holds the attributes that describe it, keyed on the parent hash key plus load_dts. Every change inserts a new dated row instead of overwriting, so a satellite is Type 2 history by construction — the same versioning you hand-built in Chapter 11, produced by the loading rule rather than by bespoke SCD code.
Whether a row is written at all is decided by the hash_diff: a hash of the whole payload, compared against the previous row. Two settings control that machinery, and the replay below shows what each one costs when a file is delivered twice and when a part changes twice in one day.
Core mental model
A satellite = parent hash key + load_dts + attributes + hash_diff. Payload changed → new dated row; payload identical → nothing written. Multi-active satellites add a discriminator to the key so several rows can be live at once.
Why it matters
Satellites are where Data Vault's history and auditability physically live. The hash_diff plus insert-only pattern gives you full, automatic change tracking without bespoke SCD code.
satellite
A table of an entity's descriptive attributes with full, dated history.
hash_diff
A hash of all attribute values, compared to detect whether a change occurred.
insert-only history
Each attribute change is a new dated row; nothing is updated or deleted.
multi-active satellite
A satellite whose PK includes a discriminator so multiple rows are active simultaneously.
idempotent load
A load that can be re-run without changing the result — what hash_diff buys you when an extract is delivered twice.
A satellite stores attribute history, insert-onlyworked example
SQL
createtablesat_part_maint(part_hkbyteanotnullreferenceshub_part(part_hk),load_dtstimestamptznotnull,-- timestamp, not date: a part can-- change twice in one dayconditiontext,cycles_ovhint,hash_diffbyteanotnull,-- hash of (condition, cycles_ovh)record_sourcetextnotnull,primarykey(part_hk,load_dts)-- one row per detected change);
The primary key is what forces load_dts to be a timestamp: make it a date and the satellite can hold only one version per parent per day, which fails the first time a part is inspected twice. hash_diff covers exactly the payload columns — add a column to the payload and every stored hash_diff becomes incomparable.
Multi-active satellite: multiple simultaneous valid rowsworked example
SQL
Input data
sat_airframe_ad (MSN-31842, three directives open at once)3 rows
airframe_hk
load_dts
ad_ref
status
3f1a…
2026-06-12 04:10
AD-2026-11-03
open
3f1a…
2026-06-12 04:10
AD-2025-04-88
deferred
3f1a…
2026-06-12 04:10
AD-2024-09-12
closed
Three rows share one parent and one load_dts. Only ad_ref distinguishes them, and it does so because it is part of the primary key.
-- Some facts are genuinely plural. An airframe holds several airworthiness-- directives at once, so a standard satellite (one row per parent per-- load_dts) cannot represent them. Adding a discriminator to the PK can.createtablesat_airframe_ad(airframe_hkbyteanotnullreferenceshub_airframe(airframe_hk),load_dtstimestamptznotnull,ad_reftextnotnull,-- 'AD-2026-11-03' — part of the PKstatustext,-- 'open', 'closed', 'deferred'hash_diffbyteanotnull,record_sourcetextnotnull,primarykey(airframe_hk,load_dts,ad_ref));
A multi-active satellite is the answer when the relationship between parent and payload is one-to-many at a single instant. Pick the discriminator from the source's own natural key for the repeating group — inventing a row number instead makes the hash_diff comparison unstable when the source reorders its rows.
Common mistake
Updating a satellite row in place when an attribute changes. You destroy history and break auditability; insert a new dated row instead.
Loading without a hash_diff comparison. A re-delivered file inserts a row identical to the last one, so the vault records a change that never happened and "when did this last change?" is wrong.
Declaring load_dts as a date rather than a timestamp. The satellite caps at one version per day, so the second change on a busy day is rejected by the primary key and the observation is lost.
Mixing fast- and slow-changing attributes in one satellite. A volatile field forces a new row for the stable ones too; split satellites by source and by rate of change.
Better habit
Insert a new dated satellite row on every attribute change.
Use a hash_diff to detect changes efficiently.
Split satellites by source and by rate of change.
SCD2 for free
A satellite is automatic Type 2 history. The insert-only load with a hash_diff produces the same versioned history you hand-built in Chapter 11, without bespoke SCD logic.
Production reality
Teams split satellites by source system and by change frequency, so a chatty source or a fast-changing attribute does not bloat history for stable ones.
Remember this
Satellites hold descriptive attributes with insert-only, hash_diff-driven history, giving automatic Type 2 versioning; hash_diff is what makes a re-run harmless, and a timestamped load_dts is what lets a thing change twice in one day.
06 · Query helpers
PIT Tables and Bridge Tables
A PIT table maps "snapshot date + entity" to the right satellite row keys. A Bridge table maps "entity" to reachable entities through links. Both are pre-computed navigation indexes — and every pre-computed thing can be out of date.
⏱ 4 min · Topic 6 of 11
Here is the bill for all that decomposition. A perfectly ordinary question — what is fitted to this aircraft, and in what condition — touches six objects and needs a correlated subquery per satellite to pick the row that was current. Run the three routes below and watch the cost move.
A Point-in-Time table stores, per snapshot date and entity, the load_dts of the row that was current in each satellite, turning every one of those subqueries into an equality join. A Bridge table does the same job across hubs, pre-materialising a multi-hop link path so a query need not re-walk the chain. Both are rebuilt on a schedule, which is where they get dangerous.
Core mental model
A PIT table maps "snapshot date + entity" to the right satellite row keys. A Bridge table maps "entity" to reachable entities through links. Both are pre-computed navigation indexes — and every pre-computed thing can be out of date.
Why it matters
PIT and Bridge tables are what make a raw vault queryable at production speed. Without them, multi-satellite joins are expensive and error-prone. Senior engineers and architects are routinely asked how they would optimize vault queries; knowing PIT/Bridge is the expected answer.
PIT table
A pre-computed table mapping each snapshot date and entity to the correct satellite row load_dates.
Bridge table
A pre-computed table materializing multi-hop link paths between hub entities.
PIT staleness
The gap when a satellite row arrives after the PIT was last rebuilt, making the PIT pointer stale.
snapshot date
The business date at which a PIT query resolves satellite row versions.
PIT table: pre-computing satellite row pointers per snapshotworked example
SQL
-- One PIT per hub, covering every satellite on that hub.createtablepit_part(part_hkbyteanotnull,snapshot_datedatenotnull,-- Per satellite: the load_dts of the most recent row whose-- load_dts <= snapshot_date. NULL if no row existed yet — which is-- why vaults insert a zero-key "ghost" row into each satellite, so-- the equi-joins above stay inner joins instead of becoming outer ones.sat_part_maint_ldtstimestamptz,sat_part_ref_ldtstimestamptz,sat_eff_ldtstimestamptz,primarykey(part_hk,snapshot_date));-- If a PIT must never be stale, a view is always current...createviewpit_part_liveasselectp.part_hk,current_dateassnapshot_date,max(m.load_dts)assat_part_maint_ldtsfromhub_partpjoinsat_part_maintmonm.part_hk=p.part_hkgroupbyp.part_hk;-- ...but it recomputes the very scan the PIT existed to avoid.-- Pick one: pre-computed and scheduled, or live and expensive.
A PIT row says "on this date, use these load_dts values to fetch the current row from each satellite". It is rebuilt per load cycle and can be extended incrementally by appending snapshot rows. The ghost-row detail matters in practice: without it, a satellite that has no row yet turns every PIT join into an outer join and quietly changes the result.
PIT table vs Bridge table
Feature
PIT table
Bridge table
Purpose
Navigate within one hub's sats
Navigate across hubs via links
Input
hub_hk + snapshot_date
Starting hub_hk
Output
sat load_date pointers
Reachable hub_hks at far end
Rebuilt
Every load cycle
Every load cycle
Query type
Point-in-time attribute lookup
Multi-hop path traversal
Common mistake
Rebuilding the PIT before satellite loads complete for the current cycle. The PIT will point at the previous satellite row for that cycle's changes, silently returning stale data.
Omitting PIT tables and querying satellites with correlated subqueries. Query plans degrade rapidly as satellite row counts grow; execution time scales with satellite cardinality instead of staying flat.
Expecting a PIT table to reduce the number of joins. It usually adds one. The win is eliminating a correlated subquery per satellite, not flattening the model — if you want few joins, that is what the mart layer is for.
Better habit
Always rebuild PIT tables as the last step in each load batch.
Build one PIT per hub; include every satellite for that hub.
Use a Bridge table whenever a mart requires multi-hop link traversal.
Interview note
"How do you make vault queries fast?" is a senior DV2 probe. The answer is PIT tables for within-hub multi-satellite joins and Bridge tables for cross-hub traversal. Naming both with a one-line explanation of each is the expected senior response.
Stale PIT data
Rebuilding the PIT mid-cycle, before all satellites finish loading, is the most common PIT correctness bug. Always sequence PIT rebuild after the last satellite load in the same batch.
Remember this
PIT tables pre-compute satellite row pointers per snapshot date, turning expensive correlated subqueries into fast equality joins; Bridge tables materialize multi-hop link paths; both are rebuilt at the end of each load cycle.
07 · Loading & serving
Hash Keys, Parallel Loading & Serving Marts
Hash keys remove the lookup, and the lookup was the thing that forced an order. What remains is insert ordering, which matters only as far as your warehouse enforces foreign keys.
⏱ 5 min · Topic 7 of 11
A surrogate key you have to look up forces an order: the hub must finish loading before anything that references it can start, because the loader has to read the hub to learn the key. A hash of the business key removes that entirely — every loader derives the same key from the source data it already holds, so nothing waits for anything.
Schedule the six jobs below and see what that buys. Then switch to surrogate lookups and watch the same schedule fall apart into a dependency chain.
One hard rule protects all of it: no business logic in the raw vault. Derivations belong in the business vault or the marts, because a rule applied on the way in cannot be un-applied when it turns out to be wrong, and reprocessing then diverges from what was originally recorded.
Core mental model
Hash keys remove the lookup, and the lookup was the thing that forced an order. What remains is insert ordering, which matters only as far as your warehouse enforces foreign keys.
Why it matters
The hash-key, insert-only, hash-diff mechanics are exactly what deliver Data Vault's parallel loading and audit guarantees, and knowing to serve marts on top is what makes a vault usable in practice.
hash key loading
Computing surrogates as hashes so tables load in parallel without lookups.
business vault
A layer of derived vault objects applying business logic atop the raw vault.
consumption layer
The dimensional marts built on the vault for users to query.
orphan record
A link or satellite row loaded before its parent hub row exists — the early-arriving load-order failure. (Distinct from a DV2 ghost record: the zero-key placeholder row inserted into satellites so PIT equi-joins always resolve.)
Flatten a hub + satellite into a dimensional view (the consumption layer)worked example
SQL
Input data
sat_part_maint (SN-77-04412)2 rows
part_hk
load_dts
condition
cycles_ovh
c204…
2026-03-02
OVH
0
c204…
2026-06-12
SVC
1204
Two satellite rows: the overhaul, then 1,204 cycles later.
-- A Type-2 dimension is just hub + satellite history, with the end date-- derived rather than stored. Nothing in the vault records "valid_to".createviewdim_partasselects.part_hkaspart_key,-- surrogate from the hash keyh.part_serial,-- business keys.condition,s.cycles_ovh,s.load_dtsasvalid_from,lead(s.load_dts)over(-- the next change closes this versionpartitionbys.part_hkorderbys.load_dts)asvalid_tofromhub_parthjoinsat_part_maintsons.part_hk=h.part_hk;
dim_part output (SN-77-04412)
part_serial
condition
cycles_ovh
valid_from
valid_to
SN-77-04412
OVH
0
2026-03-02
2026-06-12
SN-77-04412
SVC
1204
2026-06-12
NULL
LEAD() derives valid_to from the next row's load_dts; NULL means still current. This is half-open semantics (valid_to = next load_dts), the convention Chapter 15 uses. Marts often coalesce the NULL to 9999-12-31 so closed-interval BETWEEN joins work, the convention Chapter 11 uses. Pick one per mart and document it.
The mart is derived, never authoritative: the hub supplies the key, the satellite supplies the attributes and — through load_dts — the effective dating. Rebuild it and you get the same answer, which is the property that lets you throw a mart away and rebuild it differently.
The three layers of a Data Vault warehouse
Layer
Contains
Who uses it
Raw Vault
Hubs, links, satellites, exactly as sourced
Loaders, auditors
Business Vault
Derived hubs/links/sats: business rules applied
Engineers
Marts (consumption)
Dimensional stars / wide tables
Analysts, BI
Common mistake
Pointing BI tools at the raw vault. Queries are slow and join-heavy; always serve dimensional marts as the consumption layer.
Skipping hash keys and using lookups. You reintroduce load ordering and lose the parallel, idempotent loading that is the point of DV2.0.
Assuming hash keys make load order irrelevant. They remove the lookup, not the referential dependency; where foreign keys are enforced the insert order still binds, and where they are not, a failed hub job leaves permanent orphans.
Embedding business logic (derivations, rule applications) in the raw vault. The raw vault no longer faithfully reflects source data; audit traces become unreliable and reprocessing produces different results than the original load.
Better habit
Use hash keys so all tables load in parallel.
Keep loads insert-only, idempotent, and source-stamped.
Know whether your warehouse enforces foreign keys before you decide the load order matters.
Interview note
The mechanic that impresses: "hash keys let satellites load without waiting for hubs, so everything loads in parallel and idempotently." That shows you understand why DV2.0 scales.
Model the consumption star
Practice drawing the dim_customer and fact_order that this vault feeds.
Hash keys remove the surrogate lookup, which is what forced hubs to load before everything else; what remains is an insert dependency that binds only where foreign keys are enforced. Keep loads insert-only and source-stamped, keep business logic out of the raw vault, and flatten into marts for users to query.
08 · Reference & tooling
Reference Tables and Automation
Reference tables = lookup codes with audit columns, outside hubs/links/satellites. Automation = YAML metadata drives generated SQL, so the loading conventions live in one macro instead of in every engineer's memory.
⏱ 5 min · Topic 8 of 11
Reference tables are the vault's home for code and lookup data: condition codes, status enums, currency codes. They sit outside the hub/link/satellite structure because they are neither business entities nor relationships — just stable lookups with a code, a description and the same audit columns, versioned insert-only so a code whose meaning changed can still be read as it was.
The bigger problem at scale is repetition. A vault with fifty sources and four hundred satellites cannot be hand-written and stay consistent, so teams generate it: AutomateDV (formerly dbtvault) is a dbt macro library that emits hub, link, satellite, PIT and Bridge loads from YAML, and Roelant Vos's DV Accelerator follows the same pattern. What they really provide is not typing saved but conventions that cannot be forgotten — switch them off below and read what each one was holding up.
Core mental model
Reference tables = lookup codes with audit columns, outside hubs/links/satellites. Automation = YAML metadata drives generated SQL, so the loading conventions live in one macro instead of in every engineer's memory.
Why it matters
Reference tables handle the common case of lookup data without distorting the hub/link/satellite structure. Automation via dbt macros is how real teams build vaults without drowning in repetitive SQL, and it enforces conventions that manual SQL frequently gets wrong.
reference table
A versioned lookup table of code/description pairs with audit columns, outside the hub/link/sat structure.
AutomateDV
A dbt macro library (formerly dbtvault) that generates DV2 hub, link, satellite, PIT, and Bridge SQL from YAML metadata.
dbt macro
A Jinja template in dbt that generates SQL at compile time, used by AutomateDV to enforce DV2 conventions.
A reference table for part condition codesworked example
SQL
Input data
ref_part_condition5 rows
condition_code
description
load_dts
NEW
Never installed, factory packed
2026-01-01
SVC
Serviceable — cleared for installation
2026-01-01
OVH
Overhauled to zero-time condition
2026-01-01
REP
Repaired, awaiting recertification
2026-01-01
SCR
Scrapped — must not be installed
2026-01-01
These five codes are the values sat_part_maint.condition is drawn from. If a definition is later tightened, a new insert preserves the old wording so a 2026 audit still reads the way it did in 2026.
-- Reference tables sit outside hub/link/sat but carry the same-- audit columns and are loaded insert-only.createtableref_part_condition(condition_codetextprimarykey,descriptiontextnotnull,load_dtstimestamptznotnull,record_sourcetextnotnull);insertintoref_part_conditionvalues('NEW','Never installed, factory packed',now(),'maint_core'),('SVC','Serviceable — cleared for installation',now(),'maint_core'),('OVH','Overhauled to zero-time condition',now(),'maint_core'),('REP','Repaired, awaiting recertification',now(),'maint_core'),('SCR','Scrapped — must not be installed',now(),'maint_core');
Reference tables are simpler than satellites — no hash_diff, no parent hash key — but share the audit columns and the insert-only rule. Do not promote one to a hub: a condition code is a shared vocabulary, not a business entity with a history of its own.
AutomateDV (dbtvault): the YAML that generates the loader aboveworked example
Note what is absent: no hashing, no coalesce, no change-detection predicate. Those live in the macro, which is exactly why they are applied identically to the four hundredth satellite and the first.
Common mistake
Modeling country codes or status enums as hubs. Hubs are for enterprise business keys with satellite history; lookup codes are simpler reference tables.
Hand-writing all hub/link/satellite SQL in a large vault. Conventions (NULL sentinel, hash_diff, load ordering) diverge across tables; automation enforces them uniformly.
Hashing a business key without trimming and case-folding it first. Two spellings of one key produce two hub rows and two separate histories for the same physical thing, and nothing reports an error.
Better habit
Use reference tables for code/lookup data; keep them insert-only with audit columns.
Adopt AutomateDV or a similar dbt framework to generate vault SQL.
Encode NULL sentinel and hash_diff logic in the framework, not per-engineer.
Production reality
Many production teams adopt AutomateDV or an equivalent dbt package. The loading rules are too easy to forget or misapply by hand. Framework-generated SQL means a new engineer cannot accidentally skip the NULL sentinel or the hash_diff.
Interview note
Mentioning AutomateDV or dbtvault by name, and explaining that it enforces DV2 conventions via dbt macros rather than requiring every engineer to remember them, signals practical experience rather than textbook knowledge.
Remember this
Reference tables store code/lookup data with audit columns outside the hub/link/sat structure; automation frameworks such as AutomateDV generate consistent DV2-compliant SQL from YAML metadata, enforcing conventions at scale.
09 · Recap
The Vault, Consolidated
Hubs = who/what. Links = how they relate. Satellites = what changed. PIT/Bridge = how you query it fast. Reference = shared vocabulary.
⏱ 3 min · Topic 9 of 11
Six object types carry the whole methodology, and knowing them is not the same as knowing which one answers a given question. Route the eight below — two of them are the exact distinctions this chapter watched go wrong.
Core mental model
Hubs = who/what. Links = how they relate. Satellites = what changed. PIT/Bridge = how you query it fast. Reference = shared vocabulary.
Why it matters
Interviews and production incidents both test the same thing: do you know which object owns which job, and which load rule protects it.
Common mistake
Loading links or satellites before their hubs. Orphan records that silently break referential integrity in warehouses without FK enforcement.
Applying business rules in the raw vault. The audit trail is corrupted and reprocessing diverges from history.
Skipping PIT tables on a satellite-heavy vault. Every dashboard query pays for correlated subqueries the PIT would have precomputed.
Better habit
Name the object type before writing DDL.
State the load order out loud in design reviews.
Schedule PIT rebuilds with the load, not after complaints.
The 60-second vault answer
Hubs for keys, links for relationships, satellites for history — hashed for parallel, insert-only loads; PIT and bridge tables make querying practical; marts serve analysts. Deliver that and you have named every senior signal in one breath.
Remember this
Data Vault is six object types plus three load rules; everything else in this chapter is those two lists applied.
10 · Practice
Practice Lab
Say what one row means before you draw the second table. Everything else in a review follows from that sentence.
⏱ 3 min · Topic 10 of 11
Four design scenarios for this chapter, on the ERD canvas. Each one is graded against a real rubric, seeded with real rows, and each has one fault planted in the data rather than in the diagram.
Build them with the chapter closed. If one goes wrong, come back to the section it belongs to rather than re-reading the whole thing.
Core mental model
Say what one row means before you draw the second table. Everything else in a review follows from that sentence.
Why it matters
Reading about a grain mistake and watching one inflate a number you produced are different memories. The second is the one still there under interview pressure.
Common mistake
Revealing the reference model before your own review comes back. You see what correct looks like without finding out what your version got wrong, and your version is the one you will draw again under pressure.
Better habit
Write the three questions the model must answer before drawing a single table.
State the grain of every table out loud. If the sentence needs an "and", you have found a composite key.
Run the review, fix what it finds, and run it again. The second score is the one that means something.
Each scenario compiles your canvas to DDL, seeds correlated rows, and runs acceptance and anomaly checks against them. A finding comes with the query that produced it and the rows it returned.
Remember this
A model you have defended against seeded data is worth more than three you have only drawn.
11 · Next Chapter
Next Chapter
Inmon, Kimball, and Data Vault are the classical architectures. The cloud era added new patterns layered on top of them: ELT, medallion layers, wide tables, dbt, and the semantic layer.
⏱ 3 min · Topic 11 of 11
Next chapter
Modern Warehouse Modeling
Inmon, Kimball, and Data Vault are the classical architectures. The cloud era added new patterns layered on top of them: ELT, medallion layers, wide tables, dbt, and the semantic layer.
Chapter 14 covers how modern warehouses actually organize and transform data today, and how these patterns relate to the dimensional and vault ideas you now know.