Why Data Quality and Governance?#

A pipeline that delivers wrong data on time is worse than one that delivers nothing — consumers make decisions based on the output. Data governance ensures data is correct, discoverable, secure, and compliant. Without it, trust erodes and data becomes a liability instead of an asset.


Data Quality Frameworks#

Great Expectations#

Great Expectations (GX) is a Python framework for defining, running, and documenting data quality checks.

import great_expectations as gx

context = gx.get_context()

# Connect to a data source
datasource = context.data_sources.add_pandas("orders_source")
data_asset = datasource.add_dataframe_asset("orders")
batch = data_asset.add_batch_definition_whole_dataframe("full").get_batch(
    batch_parameters={"dataframe": orders_df}
)

# Define expectations
expectations = context.suites.add(gx.ExpectationSuite(name="orders_suite"))
expectations.add_expectation(gx.expectations.ExpectColumnValuesToNotBeNull(column="order_id"))
expectations.add_expectation(gx.expectations.ExpectColumnValuesToBeUnique(column="order_id"))
expectations.add_expectation(gx.expectations.ExpectColumnValuesToBeBetween(
    column="amount", min_value=0, max_value=1_000_000
))
expectations.add_expectation(gx.expectations.ExpectColumnValuesToBeInSet(
    column="status", value_set=["pending", "shipped", "delivered", "cancelled"]
))

# Validate
validation_result = batch.validate(expectations)
print(f"Success: {validation_result.success}")
print(f"Results: {len(validation_result.results)} expectations checked")

Soda#

Soda uses a YAML-based DSL called SodaCL for defining data quality checks.

# checks/orders.yml
checks for orders:
  # Row count
  - row_count > 0

  # Freshness
  - freshness(created_at) < 24h

  # Completeness
  - missing_count(order_id) = 0
  - missing_percent(email) < 5%

  # Validity
  - invalid_count(amount) = 0:
      valid min: 0
  - invalid_percent(status) = 0:
      valid values: ["pending", "shipped", "delivered", "cancelled"]

  # Uniqueness
  - duplicate_count(order_id) = 0

  # Cross-check
  - row_count same as orders in source_db

  # Schema
  - schema:
      fail:
        when required column missing: [order_id, customer_id, amount]
        when wrong type:
          order_id: varchar
          amount: decimal
# Run Soda checks
soda scan -d warehouse -c configuration.yml checks/orders.yml

Comparison#

FeatureGreat ExpectationsSodadbt Tests
LanguagePythonYAML (SodaCL)SQL + YAML
IntegrationPython scripts, AirflowCLI, Airflow, CI/CDBuilt into dbt
Data profilingYesYesNo
DocumentationAuto-generated HTMLSoda Cloud dashboardsdbt docs site
AlertingCustom + integrationsSoda CloudCI/CD failures
Best forPython-heavy pipelinesOps-friendly, YAML-firstdbt-centric workflows

Data Contracts#

A data contract is a formal agreement between a data producer and consumer specifying the schema, quality guarantees, and SLAs of a dataset.

Contract Structure#

# contracts/orders.contract.yml
apiVersion: v1
kind: DataContract
metadata:
  name: orders
  owner: order-service-team
  domain: commerce
  version: "2.1.0"

schema:
  type: table
  columns:
    - name: order_id
      type: STRING
      required: true
      unique: true
      description: Unique order identifier
    - name: customer_id
      type: STRING
      required: true
      description: FK to customers
    - name: amount
      type: DECIMAL(10,2)
      required: true
      constraints:
        - min: 0
    - name: status
      type: STRING
      required: true
      allowed_values: [pending, shipped, delivered, cancelled]
    - name: order_date
      type: DATE
      required: true

quality:
  freshness:
    max_delay: 2h
  completeness:
    order_id: 100%
    customer_id: 100%
    amount: 100%
  uniqueness:
    order_id: 100%

sla:
  availability: 99.5%
  update_frequency: hourly

Why Contracts Matter#

Without ContractsWith Contracts
Schema changes break downstream silentlyBreaking changes require version bump and consumer notification
No ownership — “who owns this table?”Clear owner per dataset
Quality is discovered after breakageQuality is guaranteed and monitored
Producers don’t know who consumes their dataConsumers are registered, impact is assessable

Schema Registries#

A schema registry stores and manages schemas for data in transit (Kafka topics, event streams). It enforces compatibility rules to prevent breaking changes.

Confluent Schema Registry (Kafka)#

# Register an Avro schema
curl -X POST -H "Content-Type: application/vnd.schemaregistry.v1+json" \
  --data '{
    "schema": "{\"type\":\"record\",\"name\":\"Order\",\"fields\":[{\"name\":\"order_id\",\"type\":\"string\"},{\"name\":\"amount\",\"type\":\"double\"},{\"name\":\"status\",\"type\":\"string\"}]}"
  }' \
  http://schema-registry:8081/subjects/orders-value/versions

# Get the latest schema
curl http://schema-registry:8081/subjects/orders-value/versions/latest

Compatibility Modes#

ModeRuleSafe Changes
BACKWARDNew schema can read old dataAdd optional fields, remove fields
FORWARDOld schema can read new dataAdd fields, remove optional fields
FULLBoth backward and forwardAdd/remove optional fields only
NONENo compatibility checkAny change (dangerous)

Default recommendation: BACKWARD compatibility. Consumers upgrade before producers.


Data Lineage#

Lineage tracks where data comes from, how it is transformed, and where it goes. It answers: “if this source changes, what is affected?”

Lineage Sources#

ToolHow It Captures Lineage
dbtParses SQL ref() and source() calls
Apache AtlasHooks into Hive, Spark, Kafka
OpenLineageOpen standard — integrates with Airflow, Spark, dbt
DatahubIngests metadata from multiple sources
Unity CatalogNative lineage for Databricks

OpenLineage Example (Airflow)#

# OpenLineage integration captures lineage automatically
# when using supported operators (SQLOperator, SparkOperator, etc.)

# In airflow.cfg or environment:
# OPENLINEAGE_URL=http://marquez:5000
# OPENLINEAGE_NAMESPACE=production

# Lineage is emitted as events:
{
    "eventType": "COMPLETE",
    "job": {"namespace": "production", "name": "daily_orders_pipeline.transform_orders"},
    "inputs": [{"namespace": "warehouse", "name": "staging.stg_orders"}],
    "outputs": [{"namespace": "warehouse", "name": "marts.fct_orders"}]
}

Data Catalogues#

A data catalogue is a searchable inventory of all data assets, their schemas, owners, descriptions, and lineage. It enables data discovery — finding the right dataset without asking someone.

CatalogueTypeKey Feature
AWS Glue Data CatalogManaged (AWS)Integrated with Athena, Redshift, EMR
DatahubOpen sourceRich lineage, metadata search
Apache AtlasOpen sourceHive/Hadoop ecosystem
Unity CatalogDatabricksUnified governance for Databricks
AlationCommercialAI-driven discovery
Google Data CatalogManaged (GCP)Integrated with BigQuery

What a Good Catalogue Provides#

CapabilityBenefit
SearchFind datasets by name, tag, or description
Schema browsingView columns, types, and sample data
OwnershipKnow who to contact for questions
LineageUnderstand upstream dependencies and downstream impact
DocumentationRich descriptions, business glossary
Quality metricsFreshness, completeness, test results
Access controlRequest and manage permissions

Compliance (GDPR)#

Data engineers must build systems that comply with privacy regulations. GDPR is the most impactful.

Key GDPR Requirements for Data Engineers#

RequirementData Engineering Implication
Right to erasureAbility to delete all data for a given user
Right to accessAbility to export all data for a given user
Data minimisationDon’t collect more than needed
Purpose limitationData used only for declared purposes
PseudonymisationReplace PII with pseudonyms where possible
Retention limitsAutomated deletion after retention period
Breach notificationLogging and monitoring for unauthorised access

Practical Patterns#

-- Pseudonymisation: hash PII in silver/gold layers
SELECT
    order_id,
    SHA2(customer_email, 256) AS customer_email_hash,
    country,  -- non-PII, kept as-is
    amount
FROM bronze.orders;

-- Right to erasure: delete by customer_id across all tables
DELETE FROM silver.orders WHERE customer_id = 'CUST-123';
DELETE FROM gold.fct_orders WHERE customer_id = 'CUST-123';
DELETE FROM gold.dim_customers WHERE customer_id = 'CUST-123';

Tip: maintain a PII registry — a document listing every table, column, and pipeline that contains personal data. This makes compliance audits and erasure requests manageable.


Key Takeaways#

  1. Data quality tools (Great Expectations, Soda, dbt tests) automate validation at every pipeline stage — catching issues before they reach consumers is far cheaper than fixing them after
  2. Data contracts formalise the agreement between producers and consumers — schema, quality guarantees, and SLAs — preventing silent breakage
  3. Schema registries enforce compatibility rules on streaming data, preventing producers from publishing breaking schema changes
  4. Data lineage (OpenLineage, dbt lineage, Datahub) tracks the flow of data from source to consumption, enabling impact analysis and debugging
  5. Data catalogues make data discoverable — without one, engineers waste time asking “who owns this table?” and “where does this data come from?”
  6. GDPR compliance requires data engineers to build systems that support erasure, access requests, pseudonymisation, and retention policies from day one — retrofitting is expensive