Data engineers reviewing BigQuery identity-column schemas, surrogate-key mappings, MERGE tests, lineage, and pipeline compatibility in a data platform workspace

Identity Columns Arrive in BigQuery: Migrate Surrogate Keys Without Breaking MERGE, Replay, or Lineage

Thu, Sep 10, 2026

A warehouse migration can look trivial in a design review: add an INT64 identity column and let BigQuery assign the surrogate. The risk appears when the table is a production contract. A direct streaming writer may no longer be compatible, a replay can allocate a different key, and a dashboard that sorts by the largest ID can quietly turn a sparse, loosely ordered generator into false chronology.

Google Cloud announced the feature on September 2, 2026 in its BigQuery identity columns launch post. As rechecked on September 9, 2026, the feature-specific BigQuery identity-column documentation still marks the capability as Preview under Pre-GA terms. That status matters operationally: a production adoption decision should include an exit criterion for support maturity, not only a syntax test.

This guide treats BigQuery identity columns as a key-contract change. It separates source identity from warehouse surrogate identity, inventories producers and consumers, chooses between GENERATED ALWAYS and BY DEFAULT, proves supported DML behavior, redesigns unsupported streaming paths, and tests slowly changing dimensions, replay, lineage, parallel migration, and rollback. The goal is to decide where database-generated keys improve the contract and where they weaken determinism or recoverability.

What BigQuery Identity Columns Change and What They Do Not

An identity column is an INT64 column generated by BigQuery when a row is written. System-generated values are unique within the table, but they are loosely ordered and sparse rather than strictly monotonic or gap-free. START WITH chooses the first generated value, while INCREMENT BY is a nonzero INT64 whose multiples separate generated values. Both defaults are 1.

The GoogleSQL data definition language reference documents GENERATED ALWAYS AS IDENTITY and GENERATED BY DEFAULT AS IDENTITY, including START WITH and INCREMENT BY. It also states that converting an existing INT64 column affects future inserts only. Existing rows are not rewritten or backfilled automatically.

The generated number is not a business identifier merely because it is unique. It does not encode event time, load time, source provenance, or cross-table identity. BigQuery constraint declarations also do not replace data-quality controls, so referential integrity still needs explicit tests.

Documented behavior and contract implication

Behavior

Documented boundary

Production implication

Type

INT64 identity column

Efficient warehouse join key, but not automatically an external identifier.

Generated uniqueness

System-generated values are unique within the table

Uniqueness does not extend to manual values supplied under BY DEFAULT.

Ordering

Loosely ordered

Never infer event time, load order, or freshness from key magnitude.

Density

Sparse; gaps can occur

Pagination, completeness checks, and range counts must tolerate missing values.

Per-table limit

At most one identity column

A table cannot use multiple independent generators for different key domains.

Physical design

Identity column cannot be partitioning or clustering column

Partition and cluster on event, ingestion, tenant, or other analytical fields.

Preview status

Pre-GA as rechecked September 9, 2026

Require explicit risk acceptance and a maturity gate before broad standardization.

The BigQuery primary and foreign key documentation is a useful reminder that warehouse key declarations and generated values are not a substitute for enforced application-level integrity. Treat identity generation as one mechanism inside a broader contract.

Decide Whether an Identity Column Fits the Table’s Contract

The first decision is whether the table should own key generation at all. A database-generated integer fits best as an internal warehouse surrogate with narrow scope: it supports compact joins, stays opaque to consumers, and can be reconstructed through a retained source-to-surrogate mapping. It is weaker when external applications cache it indefinitely, producers must generate IDs outside BigQuery, or replay must deterministically reproduce the same key without a stored mapping.

A useful decision record borrows the discipline of the Open Data Contract Standard: name the semantic owner, write paths, consumers, invariants, and compatibility expectations before changing the schema. The identity column should be described as a generated warehouse surrogate, an externally durable identifier, or a physical convenience. Do not leave the role implicit.

Identity-column decision record

Question

Accept identity when...

Reject or defer when...

Table purpose

Dimension or internal entity table needs compact surrogate joins

Identifier is part of an external API or cross-platform contract

Key consumers

Consumers can treat key as opaque

Consumers sort, paginate, watermark, or infer age from key magnitude

Write paths

Batch DML or supported load/query paths dominate

Direct streaming to the target is mandatory

Replay

Source key remains stable and MERGE matches on it

Replay requires deterministic key regeneration without stored mapping

Copies/deployments

Deployment can avoid documented unsupported copy dispositions

Table replacement automation depends on unsupported copy behavior

Physical design

Partition/cluster keys are separate fields

Identity itself must be partitioning or clustering field

Portability

Warehouse-specific surrogate is acceptable

The same identifier must be generated consistently across engines or regions

Rollback

Old key and crosswalk can remain authoritative during migration

Cutover would destroy the old mapping or require immediate key renumbering

Compare Identity Integers, UUIDs, Hashes, and Natural Keys

No key strategy wins every dimension. Natural keys carry business meaning but can be mutable or wide. UUIDs support distributed generation but are larger and opaque. Deterministic hashes replay cleanly but depend on canonicalization and collision policy. Identity integers are compact and easy to join, but their assignment is table-local and can change across rebuilds.

Key-strategy comparison matrix

Criterion

Identity INT64

UUID

Deterministic hash

Natural key

Storage and joins

Compact; efficient

Larger string/bytes footprint

Depends on representation

Varies; often wider/composite

Determinism

No across rebuilds unless mapping retained

No unless source UUID is preserved

Yes if canonical input and algorithm stay fixed

Yes while business key is stable

Distributed generation

Table-local

Strong fit

Strong fit

Source-owned

Portability

Warehouse-specific assignment

High

High with fixed algorithm

High if source semantics are shared

Collision control

Generator protects generated values in table

Probability-based uniqueness

Requires algorithm and collision policy

Requires source uniqueness controls

Debugging

Simple integer

Opaque

Opaque but reproducible

Business-readable

External exposure

Usually avoid

Often acceptable

Sometimes acceptable

Usually already exposed

Inventory Every Producer, Consumer, and Existing Key Assumption

Identity migrations fail when teams inventory tables but not behavior. Start with every producer: SQL jobs, load jobs, Dataform or dbt models, notebooks, application services, exports that round-trip data, vendor connectors, recovery scripts, and one-off administrative loads. Then map consumers: BI semantic models, dashboards, APIs, extracts, machine-learning features, downstream warehouses, quality checks, retention jobs, pagination logic, and operational support tools.

The inventory should also identify pipeline simplifications that hide write-path assumptions. The zero-ETL architecture lessons are relevant here: removing transformation hops can reduce latency, but direct writes create tighter coupling to target-table capabilities. An identity column can reintroduce the need for a landing layer if a direct stream currently targets the production table.

Producer and consumer inventory

Surface

Evidence to collect

Identity-specific question

SQL and orchestration

Queries, schedules, service accounts, write dispositions

Does generated column need to be omitted or set to DEFAULT?

Streaming

Storage Write API clients, insertAll callers, connector configs

Does any producer stream directly to the target?

Batch loads

Load job schema, column order, source files

Is the identity column absent from source data when ALWAYS is used?

dbt/Dataform

Compiled SQL, incremental keys, assertions

Does MERGE match on source identity rather than surrogate?

BI and APIs

Sorts, filters, pagination, exported fields

Does any consumer infer chronology from the integer?

ML and features

Feature definitions, point-in-time joins

Is key magnitude used as a numeric feature or recency proxy?

Recovery tooling

Backfill, replay, restore, copy, clone scripts

Would a rebuild allocate a second key system?

Downstream warehouses

Replication and export contracts

Is the warehouse surrogate expected to remain stable after migration?

Acceptance gate: no table enters a pilot until every known producer has an owner and write method, every material consumer has a compatibility status, and every sequence-like assumption has either been removed or explicitly accepted. Unknown consumers are a stop condition for externally exposed keys.

Choose GENERATED ALWAYS or GENERATED BY DEFAULT

GENERATED ALWAYS is the safer default when the identity value is purely system-owned. Ordinary manual insertion is blocked, which reduces accidental key injection and makes the contract easier to explain: producers omit the column or request DEFAULT, and BigQuery generates the value. GENERATED BY DEFAULT permits a producer to supply a value. That flexibility is useful for controlled historical migrations, sentinel compatibility, or preserving an approved legacy mapping, but it expands the integrity boundary.

BY DEFAULT does not turn manual values into generator-managed values. The generator's uniqueness guarantee applies to automatically generated values. Manually supplied values can duplicate another manual value or collide with a generated value unless your process prevents it. Therefore, choosing BY DEFAULT is not a syntax preference. It is an authorization and validation design.

Mode selection rule

Use case

Preferred mode

Reason

Normal new warehouse rows

ALWAYS

Keeps surrogate assignment system-owned and consistent.

Historical migration with preserved numeric keys

BY DEFAULT, temporarily or permanently

Allows explicit values, but requires duplicate and collision controls.

Unknown-member fixed key such as 0

BY DEFAULT only if fixed value is truly required

ALWAYS cannot accept ordinary manual sentinel insertion.

External application supplies identity

Usually reject identity design

Source identity should remain source-owned rather than smuggled into warehouse surrogate.

Emergency recovery

ALWAYS plus source-to-key crosswalk when possible

Avoids opening manual assignment as a routine recovery shortcut.

Model Manual Overrides and Uniqueness Controls

If BY DEFAULT is allowed, create a separate control plane for manual values. Limit the service accounts or roles that can execute override loads, require an override reason and change ticket, and write every override into a reconciliation table with source system, source key, requested warehouse key, operator identity, ingestion run, and approval evidence. Do not rely on tribal knowledge that only the migration team will use the feature.

Pre-commit validation for BY DEFAULT

-- Duplicate keys already present in the target
SELECT warehouse_key, COUNT(*) AS row_count
FROM mart.customer_dim
GROUP BY warehouse_key
HAVING COUNT(*) > 1;

-- Manual keys proposed by a migration that already exist
SELECT m.warehouse_key
FROM migration.manual_key_batch AS m
JOIN mart.customer_dim AS t
USING (warehouse_key);

Plan Existing-Table Conversion Without Assuming Backfill

BigQuery can change an existing top-level INT64 column into an identity column, but the change affects future inserts. Existing values remain as they are. Existing NULLs remain NULL unless you backfill them explicitly. That behavior is useful because it avoids an implicit rewrite, but it means the schema can enter a mixed state where historical rows and new rows follow different assignment rules.

For an existing table, choose deliberately between in-place conversion, a shadow table, retaining both legacy and new keys, or a full replacement. In-place change is simplest when the existing INT64 values remain authoritative; a shadow table offers cleaner parallel validation; keeping both keys protects durable consumers; full replacement carries the highest rollback and copy risk.

Historical-key migration patterns

Pattern

When it fits

Primary risk

Rollback evidence

In-place ALTER

Existing INT64 keys remain valid and table write paths are compatible

Historical nulls or consumer assumptions remain hidden

Schema snapshot, pre-change counts, old key profile

Shadow table

Need clean identity semantics and parallel validation

Dual pipelines and consumer drift

Old table retained read-only; crosswalk and parity reports

Preserve old key plus new surrogate

External consumers still need legacy key

Two-key confusion if names are vague

Explicit contract naming and source-to-surrogate mapping

Full replacement

Schema must be re-authored and cutover can be frozen

Unsupported copy/replacement automation and key reassignment

Verified backup path and rehearsed restore

A practical schema keeps the legacy key if it has any durable consumer. Rename semantics rather than forcing one field to serve both purposes: legacy_customer_key for the old contract, customer_sk for the new warehouse surrogate, source_system and source_customer_id for source identity, and migration_run_id for traceability. The crosswalk should be treated as migration evidence, not disposable staging data.

ALTER TABLE mart.customer_dim
ALTER COLUMN customer_sk
SET GENERATED ALWAYS AS IDENTITY (
  START WITH 1000000
  INCREMENT BY 1
);

The example syntax is documented in the GoogleSQL DDL reference, but the start value must be chosen from your own data profile. Do not pick a large number merely to make new rows look newer. Its only safe meaning is generator configuration. If consumers use the numeric range to distinguish historical from new data, you have created a second chronology contract by accident.

Preserve MERGE, Replay, and Idempotency Semantics

Identity generation changes the moment at which the warehouse key becomes known, but it should not change the logical match condition of an idempotent pipeline. A replay-safe MERGE matches source records to existing target rows using stable source identity and the business rules that define a row version. The generated surrogate belongs in the target row after the match decision; it should not be the field that decides whether the source entity already exists.

The BigQuery DML reference for INSERT, UPDATE, and MERGE allows DEFAULT to request a default or system-generated identity value. That means generation can participate in supported DML paths. The operational concern is not whether MERGE can insert an identity row. It is whether the MERGE condition remains stable under retries, duplicate source events, late arrivals, and backfills.

A common anti-pattern is to replay a staged batch and use a generated key in the matching logic. The replay cannot know the original generated value until it looks it up, so a missed lookup produces a second row with a second surrogate. Another failure is MERGE ON source_customer_id alone for a Type 2 dimension, which collapses historical versions. The correct match must reflect the table grain.

Replay-safe MERGE pattern

MERGE mart.customer_dim AS t
USING stage.customer_changes AS s
ON  t.source_system = s.source_system
AND t.source_customer_id = s.source_customer_id
AND t.valid_from = s.valid_from
WHEN MATCHED THEN
  UPDATE SET
    customer_name = s.customer_name,
    valid_to = s.valid_to,
    is_current = s.is_current,
    ingestion_run_id = s.ingestion_run_id
WHEN NOT MATCHED THEN
  INSERT (source_system, source_customer_id, customer_name,
          valid_from, valid_to, is_current, ingestion_run_id)
  VALUES (s.source_system, s.source_customer_id, s.customer_name,
          s.valid_from, s.valid_to, s.is_current, s.ingestion_run_id);
The broader discipline is the same one described in reliable data-pipeline foundations: retries should reproduce intended state, not merely rerun code. For identity tables, rerun stability must be a named acceptance test.

Separate Source Identity From Warehouse Surrogate Identity

Model identity explicitly enough that a reviewer can tell why a row exists. At minimum, store source_system, source_key, warehouse_key, ingestion_run_id, and ingestion timestamp. Slowly changing dimensions add valid_from, valid_to, and a current-row marker. Event-oriented tables may need source_event_id or an idempotency token. These fields serve different purposes and should not be collapsed into one generated number.

Recommended identity and lineage fields

Field

Role

Replay rule

source_system

Namespace for upstream identity

Stable across retries

source_key

Upstream entity or event identifier

Primary idempotency input

warehouse_key

Identity-generated INT64 surrogate

Opaque; reuse mapping, do not regenerate intentionally

valid_from / valid_to

Business-valid version interval

Defines SCD version grain

ingestion_run_id

Pipeline execution evidence

Changes by run; never used as business identity

ingested_at

Warehouse arrival time

Supports operations and late-arrival analysis

override_reason

Why a manual key was supplied

Required only for controlled BY DEFAULT paths

This model lets a replay find the row through source identity, then preserve the warehouse surrogate already assigned. If a full rebuild is intentionally allowed to generate different surrogates, that must be documented as a breaking migration with downstream key remapping. Do not call that behavior idempotent simply because row counts match.

Redesign Streaming Pipelines That Cannot Write to Identity Tables

This is the most important architectural constraint for teams with real-time ingestion. BigQuery documents that tables with identity columns cannot receive streaming data through the Storage Write API (gRPC) or tabledata.insertAll. If a production service streams directly into the target table, adding an identity column makes the existing writer incompatible. That is a stop condition, not a post-deployment tuning issue.

Read the restriction together with the general BigQuery Storage Write API guidance. The API supports streaming patterns and exactly-once techniques for compatible tables, but the identity-column limitation means the target architecture must change. A practitioner design is to keep the stream landing table free of identity columns and promote data into the identity table through supported query or load paths.

This is where cloud-native data engineering principles become concrete: the managed service removes infrastructure work, but platform capabilities still shape application boundaries. Do not preserve a direct-to-target architecture merely because it was simpler before the schema changed.

Streaming alternatives for an identity target

Pattern

Latency

Strength

Failure mode to test

Reconciliation

Stream to landing, scheduled MERGE

Seconds to minutes plus schedule

Clear idempotency boundary

Backlog or duplicate stage rows

Stage vs target source-key parity

Stream to landing, frequent micro-batch DML

Low minutes or sub-minute if orchestrated carefully

Near-real-time target with explicit promotion

Concurrent MERGE pressure and late events

Run-level counts and duplicate-source checks

Batch/load files to identity table

Batch-dependent

Simple, supported load path

File replay or partial batch

Manifest and row-count reconciliation

Keep streaming table without identity

Original streaming latency

No architectural change

Foregoes identity feature on that table

Existing streaming controls remain

Do not label the landing-table pattern as a vendor workaround or a guarantee. It is an architecture choice that uses documented supported boundaries. Measure added latency, DML cost, concurrency behavior, stage retention, and operational complexity before acceptance. If the business requirement is direct low-latency streaming into the final table, the correct decision may be not to adopt identity columns there.

Rework Batch Loads, Copies, CTAS, and Table Replacement

Batch pipelines usually have more compatible paths, but they often contain hidden copy semantics. BigQuery documents unsupported table-copy operations when a source or destination has an identity column: table copy with WRITE_APPEND, table copy with WRITE_TRUNCATE, and multi-source table copy. Existing deployment scripts that stage a table and then overwrite production with a copy can therefore fail even when ordinary query jobs succeed.

Review the feature limitation together with BigQuery guidance for managing table data and copy jobs. The goal is not to memorize a single forbidden command. It is to classify every job by operation type, source, destination, and write disposition so CI can stop incompatible changes before they reach production.

Load jobs can generate identity values when the identity column is omitted from the source. With GENERATED ALWAYS, the identity column must not be supplied as ordinary source data. Query append paths can also generate values when the destination identity column is omitted. Use explicit column lists so a schema reorder or newly added column does not accidentally feed a value into the generated field.

Batch and deployment preflight

Operation

Identity risk

Preflight action

INSERT / query append

Implicit column order can write wrong fields

Require explicit destination column list; omit identity or use DEFAULT intentionally

Load job

Source schema may include generated column

Verify identity omitted for ALWAYS; validate source field mapping

WRITE_APPEND table copy

Unsupported copy path

Fail CI when source or destination contains identity

WRITE_TRUNCATE table copy

Unsupported copy path

Replace deployment method or defer identity adoption

Multi-source copy

Unsupported copy path

Use an explicitly tested query-based consolidation design

CTAS / table replacement

Identity metadata may not match intended target contract

Inspect resulting schema and identity metadata; do not assume propagation

Snapshot / clone workflow

Recovery behavior may differ from copy assumptions

Rehearse exact restore path in nonproduction before relying on it

Test Unsupported Copy and Write Dispositions

Turn the preflight into an automated deployment check. Query INFORMATION_SCHEMA to discover identity columns, parse job configuration for copy dispositions, and reject a release when an unsupported copy would touch an identity table. For infrastructure-as-code or orchestration repositories, lint the compiled job definitions rather than only source templates because variables can change the final destination.

Example CI discovery query

SELECT table_schema, table_name, column_name,
       identity_generation, identity_start, identity_increment
FROM project.region-us.INFORMATION_SCHEMA.COLUMNS
WHERE is_identity = 'YES';

Pair that inventory with negative tests. A good pilot deliberately runs the old WRITE_APPEND copy, old WRITE_TRUNCATE copy, and multi-source copy in a disposable dataset and records the failure. That evidence is stronger than a checklist item because it proves your preflight is protecting a real unsupported path. Stop deployment if CI cannot distinguish query append from table copy or if production automation can bypass the check.

Protect Slowly Changing Dimensions and Fact-to-Dimension Joins

Slowly changing dimensions (SCDs) are where a generated integer can be valuable and dangerous at the same time. In a Type 1 dimension, one surrogate often represents the current entity row, and attributes are updated in place. In a Type 2 dimension, each historical version typically needs its own surrogate, while source business identity remains constant across versions. An identity column can generate the version surrogate efficiently, but only if the MERGE logic defines version boundaries correctly.

For Type 2, the fact table should join to the dimension version valid at the fact's business timestamp, not the dimension row with the largest surrogate. Late-arriving facts may need to resolve to an older version. Backfills may create historical versions after newer ones already exist. Generated identity values are loosely ordered, so the largest number cannot safely identify the latest or current version.

SCD key model and controls

Concern

Required field or rule

Acceptance test

Entity identity

source_system + source_business_key

One logical entity maps consistently across versions

Version identity

identity-generated warehouse surrogate per Type 2 row

Each version has a unique warehouse key

Validity

valid_from, valid_to, is_current

No unintended overlaps or gaps for the same business key

Fact lookup

Join on source key plus event time to valid interval

Late fact resolves to historically correct version

Unknown member

Explicit policy for sentinel or generated unknown row

No fact silently points to NULL because fixed sentinel disappeared

Key reuse

Never reuse retired surrogate for a different version

Historical fact joins remain stable after backfill

Unknown-member handling deserves an explicit decision. Many warehouses reserve 0 or -1 for an unknown dimension row. GENERATED ALWAYS cannot accept an ordinary manually assigned sentinel, while BY DEFAULT can. If a fixed sentinel is only a legacy convention, consider generating the unknown member normally and storing its surrogate in configuration or lookup logic. If a fixed number is contractually required, BY DEFAULT introduces the manual uniqueness controls described earlier.

For fact loads, retain the source business key long enough to re-resolve dimension mappings during reconciliation. A fact table that stores only the surrogate cannot prove whether an orphan came from a missing dimension row, an incorrect historical interval, or a changed key mapping. During migration, keep both old and new surrogate mappings until consumer parity is accepted.

Validate Ordering, Sparsity, and Gap-Tolerant Consumers

The most subtle migration defect is a consumer that continues to run successfully while interpreting identity values incorrectly. Generated values can be sparse and loosely ordered. Therefore, MAX(identity_id) is not the number of rows, a missing ID is not evidence of lost data, and a greater ID is not guaranteed to represent a later event, later load, or newer dimension version.

Inventory ORDER BY clauses, pagination tokens, watermarks, retention jobs, dashboards, and support queries that use the key. Replace surrogate-based recency with event_time, ingested_at, valid_from, or another explicit field. When timestamps tie, use a semantic tie-breaker such as source_event_id rather than assuming identity magnitude represents order.

Sequence-assumption test set

Consumer pattern

Unsafe assumption

Replacement

ORDER BY id DESC

Largest ID is newest

ORDER BY event_time DESC, ingested_at DESC, source_event_id

WHERE id > last_id

IDs are a complete watermark

Use ingestion watermark plus idempotency token

COUNT expected from MAX(id)

No gaps

COUNT(*) or partition-specific counts

ID ranges for retention

ID maps to age

Explicit event or ingestion date predicate

Latest SCD row = MAX(id)

Key order equals version order

is_current or MAX(valid_from) with overlap checks

Pagination by numeric id

Monotonic insertion order

Stable application sort key with continuation token

Eliminate Sequence-Order Business Logic

Build a canary dataset that intentionally violates sequence assumptions. Insert rows concurrently, use a nondefault increment in a test table, replay a batch, and load late events whose event time predates existing rows. The test passes only if reports, APIs, and orchestration still return correct business order without depending on identity magnitude.

A useful static-analysis rule flags SQL patterns such as MAX(identity_column), MIN(identity_column) used as lifecycle markers, BETWEEN ranges tied to dates, and identity predicates used as watermarks. Not every occurrence is wrong. For example, MAX can be a debugging statistic. The rule should force review, not automatically rewrite logic. The reviewer must name the semantic field that actually represents chronology or completeness.

Stop the migration if a consumer cannot be changed and its contract requires a monotonic sequence. An identity column is not a documented sequence service. If strict order is a real requirement, model order explicitly with source sequence numbers, event versions, or timestamps and define late-arrival semantics rather than asking the surrogate to carry meaning it does not guarantee.

Update dbt, Dataform, Orchestration, and Data Quality Tests

Transformation frameworks can hide generated SQL, which makes identity adoption a compile-time problem as well as a database problem. In dbt, incremental models on BigQuery commonly use MERGE when configured appropriately. The model's unique_key should represent the logical source or row-version identity used for matching, not the generated warehouse surrogate. In Dataform, incremental unique keys play a similar role. Identity generation belongs in the destination insert, not in the framework's definition of source uniqueness.

Review the dbt guidance for incremental models and dbt BigQuery configuration documentation alongside the compiled SQL your project actually emits. Refonte Learning's discussion of modern analytics with dbt and Snowflake is useful background for transformation ownership, but the identity-specific control is to inspect the target SQL, not assume the framework abstracts the feature correctly.

For Dataform, use Dataform assertions and the Dataform core reference to implement uniqueness, non-null, and incremental-key checks where applicable. The exact assertion should match the table contract: generated surrogate uniqueness, source-key uniqueness at the current-row grain, and no duplicate source-version keys.

Framework and orchestration checklist

Control

Required change

Explicit columns

Never rely on SELECT * or positional INSERT into an identity target.

Incremental match key

Use stable source identity or source-plus-version grain, not warehouse identity.

Compiled SQL review

Confirm identity column is omitted or DEFAULT is used intentionally.

Data tests

Check surrogate duplicates, source-key duplicates, unexpected nulls, and orphans.

Deployment ordering

Create/alter target before models that depend on identity metadata; canary before consumer switch.

Environment parity

Use the same generation mode and schema behavior in test and production.

Failure handling

Do not auto-retry a non-idempotent insert path without source-key deduplication.

-- Contract tests for a current-row dimension
SELECT source_system, source_customer_id, COUNT(*)
FROM mart.customer_dim
WHERE is_current
GROUP BY source_system, source_customer_id
HAVING COUNT(*) != 1;

SELECT customer_sk, COUNT(*)
FROM mart.customer_dim
GROUP BY customer_sk
HAVING COUNT(*) > 1;

Maintain Lineage, Auditability, and Key Ownership

Generated keys reduce code only if ownership becomes clearer. Data engineering owns source identity and replay semantics; platform or database teams own DDL guardrails; producer teams own idempotent inputs; BI and application owners own consumer compatibility; security owns override permissions; operations owns recovery; and a risk approver owns any time-bounded exception for using a Preview feature on critical data.

The division of responsibility resembles the boundary described in database administrator versus data engineer: schema mechanics and pipeline semantics overlap, but they are not interchangeable. An identity migration needs both database-level control and data-engineering ownership of source keys, replay, and lineage.

Use the BigQuery COLUMNS INFORMATION_SCHEMA view to discover is_identity, identity_generation, identity_start, and identity_increment. Capture those fields in schema inventory and deployment evidence. Also monitor the BigQuery release notes before broad rollout so Preview status or limitations are rechecked at each promotion gate.

Ownership model

Role

Owns

Required evidence

Data engineering

Source identity, MERGE grain, replay, crosswalk

Rerun stability tests and source-key parity

Warehouse/platform

DDL, permissions, supported operations, CI guardrails

Schema inventory and unsupported-path tests

Security

BY DEFAULT override access and service accounts

Least-privilege review and override audit trail

Operations/SRE

Monitoring, incident response, rollback execution

Runbooks, cutover logs, recovery rehearsal

BI/application owners

Ordering, pagination, API and semantic-model compatibility

Consumer canary results

Business validator

Record-level and metric-level correctness

Reconciliation sign-off

Risk approver

Preview adoption for critical workloads

Exception and exit criteria

Retain reconciliation evidence long enough to support post-cutover investigations. At minimum, keep source-to-surrogate mapping snapshots, migration run identifiers, duplicate and orphan checks, manual override records, cutover timestamps, and the exact schema generation mode. Without that history, an identity incident quickly becomes unanswerable: teams can see that a key changed but cannot prove which run assigned it or whether the change was expected.

Migrate in Parallel With Reconciliation and Rollback

A reversible migration uses a shadow table or dual model long enough to prove behavior without destroying the old key system. Use source fixtures for normal rows, duplicates, late arrivals, historical versions, unknown members, and replays. Populate the identity target through the intended production path, then build the old-to-new crosswalk from source identity rather than row position or generated-key order.

Then run consumers in canary mode. BI models can compare aggregate and record-level results. Fact loads can resolve both old and new dimension keys and compare orphan rates. Exports can carry both keys temporarily if the receiving system supports additive fields. Do not cut over a consumer that has not demonstrated it can ignore identity order and preserve its own stable identifiers.

Worked example: customer dimension migration

A customer dimension currently uses a deterministic transformed surrogate. Discovery finds three important dependencies: an event service streams customer changes directly into the production table, the nightly SCD Type 2 MERGE matches on source customer ID plus valid_from, and an operations dashboard sorts by surrogate descending to show what it calls the latest customer records. The proposed identity column initially appears to simplify the transformation model.

The first prototype fails because the event service streams through the Storage Write API directly to the identity target. Production remains unchanged. The revised design streams into a non-identity landing table and promotes rows through micro-batch MERGE, adding run-level backlog and source-key parity checks.

Replay then exposes a second defect: an early MERGE looked up the generated surrogate and inserted when the lookup missed, so the same fixture could receive a second identity. The fix removes warehouse identity from the match and uses source_system, source_customer_id, and valid_from. The repeated run updates the same row and preserves its surrogate.

During canary cutover, a late-arriving fact becomes an orphan because its historical dimension version has not yet been promoted. The orphan gate stops the switch, facts continue using the old mapping, and the team reorders dimension promotion before fact resolution. After replay, the canary resumes only when the intended orphan count returns to zero and dashboard order uses valid_from rather than surrogate magnitude.

Cutover and rollback state model

State

Writes

Reads

Rollback action

Baseline

Old table only

Old contract

No action

Shadow

Old authoritative; new populated in parallel

Canary reads new

Stop shadow writes; discard/rebuild new table

Dual-validation

Both paths receive controlled promotion

Selected consumers compare both

Return canaries to old; retain crosswalk evidence

Cutover

New identity path authoritative after freeze/gate

Consumers switch in waves

Route writes/reads back to old mapping before divergent new assignments grow

Stabilization

New only for approved scope

New contract

Use preserved old snapshot/crosswalk if rollback criteria trigger

Cleanup

Legacy writes disabled

Legacy key removed only after retention period

No easy rollback; requires explicit risk sign-off before cleanup

Define Key-Parity and Referential-Integrity Gates

Promotion gates should be numeric and explainable. Proposed practitioner thresholds can be stricter than the platform's documented behavior. For a surrogate-key migration, exact equality is appropriate for source-identity coverage and unintended duplicate counts. Metric differences may have a documented tolerance only when the underlying business logic already permits one. Identity sparsity is never an accepted explanation for missing source records.

Promotion gates

Gate

Pass condition

Failure response

Source-key coverage

Every expected source identity/version exists in target

Stop; quarantine missing identities

Generated-key uniqueness

No duplicate warehouse surrogate

Stop; investigate manual values or corruption

Source-version uniqueness

No duplicate source_system + source_key + version grain

Stop; fix MERGE/idempotency logic

Fact orphans

Zero unintended orphans

Stop consumer cutover; repair dimension timing or mapping

Replay stability

Second run does not create new logical rows or remap existing keys

Rollback pilot; correct match condition

Consumer parity

Named reports/APIs pass record and metric comparisons

Keep old consumer path

Unsupported paths

No production job uses blocked streaming/copy operation

Reject deployment

Rollback conditions include any unsupported production writer discovered after deployment, growth in unintended orphans, duplicate source-version keys, consumer reliance on identity order that cannot be corrected quickly, or inability to reproduce source-to-surrogate mappings. Recovery evidence is not a statement that the rollback succeeded. It is the restored row counts, stable old-key joins, disabled new write path, preserved crosswalk, and incident record showing which assignments occurred before rollback.

Build a Production Adoption Checklist for Identity Columns

A 60-day adoption plan should end with a decision, not assume production rollout is the destination. Preview is an acceptable outcome for a contained pilot if the risk is understood. The team can also decide that identity columns fit batch dimensions but not streaming facts, or that a critical table should wait for broader support. Standardize only the use cases that pass deterministic replay, supported-write, referential-integrity, and rollback gates.

Production checklist

  • Table purpose and key role are documented: internal surrogate, external identifier, or physical convenience.

  • Source identity remains separate from warehouse identity and is retained for reconciliation.

  • All producers are classified by INSERT, MERGE, UPDATE, load, streaming, copy, and replacement behavior.

  • All consumers have been checked for ordering, pagination, watermark, retention, and numeric-feature assumptions.

  • Generation mode is approved, and BY DEFAULT override permissions are least-privilege and audited if used.

  • Streaming writers have been redesigned or identity adoption has been rejected for that target.

  • Unsupported WRITE_APPEND, WRITE_TRUNCATE, and multi-source copy paths are blocked by CI.

  • Existing-row migration has an explicit plan for historical values, nulls, and key crosswalks.

  • MERGE and replay tests prove the same source identity does not acquire a second surrogate unintentionally.

  • SCD Type 1/2, unknown members, late facts, and backfills have been validated.

  • Lineage captures source key, warehouse key, valid time, ingestion run, and override evidence.

  • Parallel reconciliation, cutover, stop conditions, and rollback have named owners and rehearsed evidence.

60-day adoption plan

Days

Focus

Deliverables and gate

1-10

Discovery and contract

Inventory tables, producers, consumers, old key semantics, streaming/copy paths. Gate: no unknown critical producer.

11-20

Decision and prototype

Key strategy record, ALWAYS/BY DEFAULT decision, test table, start/increment profile, information-schema inventory.

21-30

Unsupported-path redesign

Landing or micro-batch design for streaming, CI rules for copy restrictions, explicit column-list changes.

31-40

Historical migration and replay

Shadow table, crosswalk, SCD fixtures, duplicate/late/replay tests. Gate: deterministic logical replay.

41-50

Downstream validation

BI/API canaries, fact-dimension reconciliation, ordering fixes, dbt/Dataform assertions, rollback rehearsal.

51-60

Rollout and stabilization

Wave cutover, monitoring, retained old mapping, risk sign-off, Preview exit criteria and cleanup date.

After cutover, monitor promotion lag, duplicate source-version count, unintended orphans, unexpected NULL warehouse keys, manual overrides, blocked writes, and consumer canary errors. Alert on contract breaches, not gaps in identity numbers: sparse keys are expected, missing source identities are not.

A production-ready decision is conditional. Adopt identity columns when the INT64 is an opaque internal surrogate, every write path is supported, MERGE matches stable source identity, replay preserves existing mappings, SCD joins use valid time, consumers tolerate gaps and loose ordering, and rollback can return to the old mapping. Defer when final-table streaming is mandatory, external systems need deterministic regeneration, or crosswalk and rollback evidence cannot be preserved.

For practitioners building the broader skills behind these controls, the Refonte Learning Data Engineering Program covers verified foundations in data storage, real-time processing, batch and streaming ingestion, data pipelines, transformation, security, data protection, and governance. Those foundations are relevant to evaluating warehouse key migrations, but the program page does not claim product-specific instruction in BigQuery identity columns, dbt, Dataform, surrogate-key migration, or slowly changing dimensions.

A generated integer must never silently become business identity or event order. The contract still has to state what the number means, who may override it, how replay recognizes the same source record, how facts retain referential integrity, and how rollback prevents a second key system. Database-generated keys help only when those responsibilities become more explicit.