Design analytics solutions for enterprise environments. Covers BI architecture, self-service analytics, data governance, semantic layers, and analytics engineering with dbt.
Enterprise Data Analytics Architecture enables organizations to unify, transform, and serve data from multiple operational systems—especially ERP and line-of-business platforms—into a coherent, actionable, and timely analytics foundation. It matters when a business must answer complex, cross-functional questions (e.g., "Why are margins declining in the EMEA region despite rising sales?") with data that spans procurement, production, sales, and finance, and when reports must be refreshed faster than daily, with low latency and high consistency.
Data flows into the analytics layer via ingestion pipelines that extract from source systems, transform data into a common model, and load it into a centralized store. The core mechanism is ETL (Extract, Transform, Load), but the most common failure is conflating *extract* with *sync*: a pipeline that pulls full tables nightly is not a real-time ingestion.
The ingestion process begins with a change data capture (CDC) strategy. Systems emit change events in a standard format: JSON over Kafka, or a stream of database log records. Each event includes:
source_table: string (e.g., SalesOrderHeader)operation: one of insert, update, deleterow_id: unique identifierpayload: JSON object with updated fieldsevent_timestamp: ISO 8601 with microsecond precisionA typical ingestion job runs as a stateful stream processor consuming from a Kafka topic. The processor maintains a checkpoint in a database or file system, tracking the latest offset and event_timestamp for each source table.
kafka-consumer-groups.sh \
--bootstrap-server kafka.example.com:9092 \
--describe \
--group analytics-ingest
Each consumer group processes events in parallel, with partitions mapped to worker instances. The processor reads event_timestamp to ensure idempotency and deduplication.
A common failure occurs when event_timestamp is not used to determine *processing order*. When a batch job writes to the same topic as a real-time stream, the ingestion pipeline may process events out of order. To correct this, the pipeline must use timestamp-based sequencing:
event_timestamp from the source.processed_at timestamp.(source_table, row_id, event_timestamp).If a record is already in the table, the pipeline skips processing. This prevents double-counting in fact tables.
Data from disparate systems often have conflicting schemas. A product ID in the ERP may be int, while the CRM uses varchar(10) with leading zeros. The ingestion pipeline must resolve these differences during transformation.
The standard approach uses star schema modeling in a data warehouse. A fact table (sales_fact) connects to dimension tables (dim_product, dim_customer, dim_date). The model is defined using SQL DDL with explicit primary and foreign keys.
-- Example: Define a fact table with partitioning and compression
CREATE TABLE sales_fact (
sales_id BIGINT PRIMARY KEY,
product_id INT NOT NULL,
customer_id INT NOT NULL,
order_date_id INT NOT NULL,
revenue DECIMAL(12, 2) NOT NULL,
quantity INT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL
) PARTITION BY RANGE (order_date_id);
-- Create partitions per month
CREATE TABLE sales_fact_2023_01
PARTITION OF sales_fact
FOR VALUES FROM (20230101) TO (20230201);
Each table uses columnar storage with compression (e.g., Snappy or ZSTD) and statistics for query optimization.
A frequent failure is schema drift: a new field appears in the source system but is not reflected in the warehouse. The solution is schema evolution via schema registry.
The schema registry stores a canonical schema for each table. Each record in the registry includes:
table_name: stringversion: integer (incremental)schema_json: JSON object of column definitionscreated_at: timestampdescription: optionalThe ingestion pipeline reads the latest schema from the registry and validates incoming events against it using jsonschema format.
{
"type": "object",
"properties": {
"sales_id": { "type": "integer" },
"product_id": { "type": "integer" },
"customer_id": { "type": "integer" },
"order_date_id": { "type": "integer" },
"revenue": { "type": "number" },
"quantity": { "type": "integer" }
},
"required": ["sales_id", "product_id", "customer_id", "order_date_id"]
}
When an incoming record fails schema validation, the pipeline logs an error:
{
"error": "schema_validation_failed",
"table": "sales_fact",
"version": 5,
"record": {
"sales_id": 1001,
"product_id": 205,
"customer_id": 3012,
"order_date_id": 20230101,
"revenue": 1499.99,
"quantity": 3,
"discount": 0.15 // new field, not in schema
},
"validation_errors": [
"Additional property 'discount' not allowed"
]
}
The pipeline can either:
After ingestion, data undergoes transformation to align formats, enrich with derived values, and prepare for consumption. The core unit is the transformation job, typically implemented as a SQL script or Python script executed in a batch or stream processing engine.
Transformation pipelines are orchestrated using dependency graphs. A job runs only after its inputs are available. The orchestration system tracks job state, execution time, and failure history.
The most common orchestration tool is Airflow, using DAG definitions in Python.
from airflow import DAG
from airflow.operators.python import PythonOperator
from airflow.operators.bash import BashOperator
from datetime import datetime, timedelta
default_args = {
'owner': 'analytics',
'retries': 3,
'retry_delay': timedelta(minutes=5),
'start_date': datetime(2023, 1, 1),
}
dag = DAG(
'sales_enrichment_pipeline',
default_args=default_args,
description='Enrich raw sales data with customer and product metadata',
schedule_interval=timedelta(days=1),
catchup=False,
)
# Extract raw sales data
extract_sales = BashOperator(
task_id='extract_raw_sales',
bash_command='psql -c "SELECT * FROM raw_sales WHERE date = current_date" -o /tmp/sales_raw.json',
dag=dag,
)
# Enrich with product and customer data
enrich_data = PythonOperator(
task_id='enrich_sales_with_metadata',
python_callable=lambda: enrich_sales(),
dag=dag,
)
# Load to warehouse
load_to_warehouse = BashOperator(
task_id='load_to_warehouse',
bash_command='psql -c "COPY sales_fact FROM '/tmp/sales_enriched.csv' WITH (FORMAT csv, HEADER true)"',
dag=dag,
)
# Define dependencies
extract_sales >> enrich_data >> load_to_warehouse
A key failure mode is missing dependencies: a job runs but the input file is not yet written. The solution is wait-for-file and wait-for-table operators.
# Wait for a file to appear in HDFS
hdfs dfs -test -f /data/sales/2023-10-01/sales.parquet
Or, in Airflow:
from airflow.operators.external_task import ExternalTaskSensor
wait_for_sales_raw = ExternalTaskSensor(
task_id='wait_for_sales_raw',
external_dag_id='sales_ingestion_pipeline',
external_task_id='load_raw_sales',
poke_interval=60,
timeout=3600,
dag=dag,
)
Another common issue is pipeline drift: a job that worked yesterday fails today because the source schema changed. The solution is versioned transformations.
Each transformation script includes a version field and is stored in a version control system (e.g., Git). The orchestration system runs the script with a specific version, and the output includes metadata:
{
"pipeline": "sales_enrichment_pipeline",
"version": "2.3.1",
"input_hash": "a3b5c7e9d1f2a3b4c5d6e7f8a9b0c1d2",
"run_id": "20231002-143022",
"duration_seconds": 142.3,
"status": "success",
"errors": []
}
This metadata enables pipeline lineage and reproducibility.
Once data is modeled and transformed, it must be served to users and applications. The query layer is built around SQL engines that execute analytical queries on large datasets.
The most common setup uses columnar storage with query optimization. Queries are issued via SQL endpoints using HTTP or JDBC.
POST /query
Content-Type: application/json
{
"query": "SELECT product_category, SUM(revenue) FROM sales_fact WHERE order_date_id BETWEEN 20230101 AND 20231231 GROUP BY product_category ORDER BY revenue DESC",
"parameters": {
"limit": 10,
"time_zone": "Europe/Paris"
}
}
The response is a JSON object with:
columns: array of column namesdata: array of rows, each an objectmetadata: query execution details{
"columns": ["product_category", "sum"],
"data": [
{ "product_category": "Electronics", "sum": 1245000.00 },
{ "product_category": "Clothing", "sum": 895000.00 }
],
"metadata": {
"execution_time_ms": 1245,
"rows_scanned": 458321,
"query_plan": "HashJoin(product, customer)"
}
}
Common failure modes:
To address these, the system uses materialized views:
CREATE MATERIALIZED VIEW sales_summary AS
SELECT
product_category,
SUM(revenue) AS total_revenue,
COUNT(*) AS order_count
FROM sales_fact
WHERE order_date_id >= 20230101
GROUP BY product_category
WITH DATA;
-- Refresh the view
REFRESH MATERIALIZED VIEW sales_summary;
Materialized views are refreshed periodically (e.g., daily) or triggered on data updates.
The final layer is dashboarding. Dashboards are built using predefined queries and dynamic parameters. A dashboard query may include:
date_range, region, product_categorysum, avg, count, percentileQueries are stored in a metadata catalog, with each query having:
name: stringdescription: stringsql: stringparameters: array of name, type, defaultlast_updated: timestampWhen a user changes a filter, the dashboard sends a new query with the updated parameters. The query engine returns results that are rendered in real time.
The analytics architecture is monitored via centralized logging, metrics, and alerting.
Each job emits structured logs in JSON format:
{
"job_id": "sales_enrichment_pipeline_20231002",
"task": "enrich_sales_with_metadata",
"status": "success",
"start_time": "2023-10-02T14:30:00Z",
"end_time": "2023-10-02T14:32:30Z",
"duration_ms": 15000,
"input_rows": 12400,
"output_rows": 12400,
"errors": [
{ "code": "schema_mismatch", "message": "Missing field 'discount' in sales record" }
]
}
Logs are ingested into ELK stack (Elasticsearch, Logstash, Kibana) or Loki.
Metrics are collected using Prometheus, with common metrics:
job_duration_seconds: histogramjob_success_rate: gaugedata_lag_hours: gauge (time since last data point was ingested)query_execution_time_ms: histogramAlerts are triggered using Alertmanager:
groups:
- name: analytics
rules:
- alert: DataLagExceedsThreshold
expr: data_lag_hours > 24
for: 30m
labels:
severity: warning
annotations:
summary: "Data lag exceeds 24 hours"
description: "Last data point ingested 25.3 hours ago"
Failure recovery is driven by retry policies and checkpointing. When a job fails, the orchestration system retries it with exponential backoff. If a job fails three times, it is marked as failed and an alert is sent.
Recovery involves:
The architecture is resilient, but checkpointing is the silent guardian: without it, a pipeline can lose progress across restarts. The most common failure is checkpointing not being persisted: the pipeline reads from Kafka but forgets where it left off after a crash.
Enterprise Data Analytics Architecture is a system of synchronized, versioned, and monitored data pipelines that ingest, transform, and serve data from operational systems. It relies on standard tools—Kafka, PostgreSQL, Airflow, Prometheus, and SQL—for predictable, repeatable, and maintainable workflows. The architecture is defined by event-driven ingestion, schema evolution, orchestrated transformations, and real-time serving. Its success hinges on precise timestamps, idempotent processing, and rich metadata—not vendor-specific features.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy