🎉 75% of content is free forever — Unlock Premium from $10/mo →
CW
💼 Servicesℹ️ About✉️ ContactView Pricing Plansfrom $10

Snowflake + Airflow + Kafka + PySpark — Complete Architecture Visualization

Data Engineering PipelinesModern Data Stack Architecture🟢 Free Lesson

Advertisement

Snowflake + Airflow + Kafka + PySpark — Complete Architecture

The modern data stack is built on four foundational technologies that work together to create end-to-end data pipelines. This guide provides comprehensive visualizations, architecture diagrams, and practical Q&A to master these technologies.

Modern Data Stack — Four Pillars ArchitectureApache KafkaStreaming Ingestion• Topics & Partitions• Consumer Groups• Event-Driven• Exactly-OnceApache AirflowOrchestration Engine• DAG-based Workflows• Task Scheduling• Dependency Management• Monitoring & AlertsApache SparkDistributed Processing• RDD / DataFrame• Spark SQL & Streaming• In-Memory Computing• MLlib IntegrationSnowflakeCloud Data Warehouse• Separate Compute/Storage• Auto-scaling Warehouses• Zero-Copy Cloning• Time TravelData Flow PipelineData SourcesAPIs, DBs, LogsKafkaIngest & BufferAirflowOrchestratePySparkTransformSnowflakeStore & QuerySource → Kafka (Ingest) → Airflow (Orchestrate) → PySpark (Transform) → Snowflake (Store)Airflow triggers Spark jobs; Kafka buffers real-time data; Snowflake serves analytics

1. Apache Kafka — Streaming Ingestion Layer

Kafka acts as the central nervous system of the modern data stack, ingesting data from multiple sources in real-time and buffering it for downstream processing.

Kafka Cluster ArchitectureProducersWeb Apps, IoT, DBsKafka ClusterBroker 1Partition 0,1Broker 2Partition 2,3Broker 3Partition 4,5ZooKeeperCluster MgmtSchema Registry + Kafka ConnectConsumer GroupsSpark, Flink, AppsKafka StreamsStream ProcessingKafka ConnectSource/Sink ConnectorsKafka Topicsorders, clicks, eventsPartitions + ReplicationSerializationAvro, Protobuf, JSONSchema Evolution

Kafka Key Concepts

ConceptDescriptionExample
TopicLogical channel for messagesuser-events, order-updates
PartitionTopic subdivision for parallelismTopic A → Partition 0, 1, 2
BrokerServer storing topic partitions3-broker cluster for HA
ProducerApplication publishing messagesWeb app logging clicks
ConsumerApplication reading messagesSpark job processing events
Consumer GroupSet of consumers reading a topicspark-consumer-group
OffsetPosition of consumer in partitionOffset 1042 = 1042nd message
ReplicationCopies of partitions across brokersReplication factor = 3

Kafka Data Flow

# Kafka Producer Example
from kafka import KafkaProducer
import json

producer = KafkaProducer(
    bootstrap_servers=['broker1:9092', 'broker2:9092'],
    value_serializer=lambda v: json.dumps(v).encode('utf-8'),
    acks='all',                          # Wait for all replicas
    retries=3,                           # Retry on failure
    max_in_flight_requests_per_connection=1  # Ordering guarantee
)

# Send event
producer.send(
    topic='user-events',
    key=b'user-123',                     # Partition key
    value={
        'user_id': 'user-123',
        'action': 'page_view',
        'page': '/products',
        'timestamp': '2024-01-15T10:30:00Z'
    }
)
producer.flush()
```python
# Kafka Consumer Example (PySpark Structured Streaming)
spark.readStream \
    .format("kafka") \
    .option("kafka.bootstrap.servers", "broker1:9092,broker2:9092") \
    .option("subscribe", "user-events") \
    .option("startingOffsets", "latest") \
    .option("group.id", "spark-consumer-group") \
    .load()

2. Apache Airflow — Orchestration Engine

Airflow orchestrates the entire pipeline — scheduling Spark jobs, monitoring Kafka consumption, loading data to Snowflake, and managing dependencies.

Airflow Architecture & ComponentsWeb Server• DAG Browser UI• Task Monitoring• Manual Triggers• Historical RunsScheduler• DAG Parsing• Task Queuing• Dependency Resolution• Retry LogicMetadata DB• DAG States• Task History• Variables• ConnectionsWorkers• Execute Tasks• Celery/K8s/Kafka• Horizontal Scaling• Pool ManagementOperators (Task Types)SparkSubmitOperator — Launch PySpark jobsSnowflakeOperator — Execute SQLKafkaConsumerOperator — Monitor topicsBashOperator — Run shell commandsHooks (Connections)SparkHook — EMR/Dataproc connectionSnowflakeHook — Snowflake connectionKafkaHook — Kafka cluster connectionS3Hook — Object storage accessSensors (Watchers)S3KeySensor — Wait for file arrivalExternalTaskSensor — Wait for DAGTimeSensor — Wait for timeSqlSensor — Wait for SQL condition

Airflow DAG Example

from datetime import datetime, timedelta
from airflow import DAG
from airflow.operators.python import PythonOperator
from airflow.providers.apache.spark.operators.spark_submit import SparkSubmitOperator
from airflow.providers.snowflake.operators.snowflake import SnowflakeOperator
from airflow.operators.empty import EmptyOperator

default_args = {
    'owner': 'data-engineering',
    'retries': 2,
    'retry_delay': timedelta(minutes=5),
    'email_on_failure': True,
    'email': ['alerts@company.com'],
}

with DAG(
    dag_id='kafka_spark_snowflake_pipeline',
    default_args=default_args,
    schedule_interval='@hourly',
    start_date=datetime(2024, 1, 1),
    catchup=False,
    tags=['kafka', 'spark', 'snowflake'],
) as dag:

    start = EmptyOperator(task_id='start')

    # Step 1: Spark job reads from Kafka, transforms, writes to S3
    transform = SparkSubmitOperator(
        task_id='kafka_to_s3_transform',
        application='s3://jobs/kafka_transform.py',
        conn_id='spark_default',
        conf={
            'spark.streaming.kafka.maxRatePerPartition': '1000',
            'spark.sql.shuffle.partitions': '200',
        },
        application_args=[
            '--bootstrap-servers', 'broker1:9092,broker2:9092',
            '--topic', 'user-events',
            '--output-path', 's3://lake/silver/user-events/',
        ],
    )

    # Step 2: Snowflake loads from S3 stage
    load_snowflake = SnowflakeOperator(
        task_id='load_snowflake',
        sql="""
            COPY INTO analytics.user_events
            FROM @s3_stage/user-events/
            FILE_FORMAT = (TYPE = PARQUET)
            ON_ERROR = 'CONTINUE';
        """,
        snowflake_conn_id='snowflake_default',
        warehouse='COMPUTE_WH',
        database='ANALYTICS',
        schema='PUBLIC',
    )

    # Step 3: Data quality check
    quality_check = SnowflakeOperator(
        task_id='quality_check',
        sql="""
            SELECT COUNT(*) as row_count
            FROM analytics.user_events
            WHERE event_date = CURRENT_DATE();
        """,
        snowflake_conn_id='snowflake_default',
    )

    end = EmptyOperator(task_id='end')

    start >> transform >> load_snowflake >> quality_check >> end

3. Apache Spark (PySpark) — Distributed Processing

Spark handles the heavy lifting — transforming data from Kafka, aggregating, cleaning, and preparing it for Snowflake.

PySpark Architecture — SparkSession & ExecutionSparkSession• Entry Point• SQL Context• Streaming ContextDataFrame API• Schema Enforcement• Catalyst Optimizer• Lazy EvaluationExecution Engine• DAG Execution• Stage-based Parallelism• Tungsten OptimizationsCluster Manager• YARN / Mesos• Kubernetes• StandaloneSpark Core ConceptsRDD → Resilient Distributed Dataset (immutable, partitioned)DataFrame → Distributed table with schemaDataset → Type-safe DataFrame (Scala/Java)Shuffle & Partitioningshuffle.partitions = 200 (default)Repartition vs Coalesce (shuffle vs no shuffle)Partition by key for partition pruningStreaming ModesMicro-batch (Structured Streaming)Continuous processing (experimental)Trigger: ProcessingTime, Once, AvailableNow

PySpark Kafka Integration Example

from pyspark.sql import SparkSession
from pyspark.sql import functions as F
from pyspark.sql.types import StructType, StructField, StringType, TimestampType, DoubleType

spark = SparkSession.builder \
    .appName("KafkaToSnowflake") \
    .config("spark.jars.packages", 
            "org.apache.spark:spark-sql-kafka-0-10_2.12:3.5.0,"
            "net.snowflake:spark-snowflake_2.12:3.0.0") \
    .config("spark.sql.shuffle.partitions", "200") \
    .getOrCreate()

# Define schema for incoming JSON
schema = StructType([
    StructField("user_id", StringType(), False),
    StructField("action", StringType(), False),
    StructField("page", StringType(), True),
    StructField("amount", DoubleType(), True),
    StructField("timestamp", TimestampType(), False),
])

# Read stream from Kafka
raw_stream = spark.readStream \
    .format("kafka") \
    .option("kafka.bootstrap.servers", "broker1:9092,broker2:9092") \
    .option("subscribe", "user-events") \
    .option("startingOffsets", "latest") \
    .option("failOnDataLoss", "false") \
    .load()

# Parse JSON from Kafka value column
parsed_stream = raw_stream \
    .select(
        F.col("key").cast("string").alias("event_key"),
        F.from_json(F.col("value").cast("string"), schema).alias("data"),
        F.col("timestamp").alias("kafka_timestamp"),
        F.col("partition"),
        F.col("offset")
    ) \
    .select("event_key", "data.*", "kafka_timestamp", "partition", "offset")

# Transform: windowed aggregation
windowed_counts = parsed_stream \
    .withWatermark("timestamp", "10 minutes") \
    .groupBy(
        F.window("timestamp", "5 minutes", "1 minute"),
        "action",
        "page"
    ) \
    .agg(
        F.count("*").alias("event_count"),
        F.countDistinct("user_id").alias("unique_users"),
        F.sum("amount").alias("total_amount"),
        F.avg("amount").alias("avg_amount")
    )

# Write to Snowflake (batch sink via foreachBatch)
def write_to_snowflake(batch_df, batch_id):
    batch_df.write \
        .format("snowflake") \
        .option("url", "https://account.snowflakecomputing.com") \
        .option("db", "ANALYTICS") \
        .option("schema", "EVENTS") \
        .option("warehouse", "COMPUTE_WH") \
        .option("table", "USER_EVENTS_WINDOWED") \
        .option("user", "svc_account") \
        .option("password", sf_password) \
        .mode("append") \
        .save()

query = windowed_counts.writeStream \
    .foreachBatch(write_to_snowflake) \
    .outputMode("update") \
    .trigger(processingTime="1 minute") \
    .option("checkpointLocation", "s3://checkpoints/kafka-snowflake/") \
    .start()

4. Snowflake — Cloud Data Warehouse

Snowflake provides the analytics-ready storage layer with separated compute/storage, auto-scaling, and zero-copy cloning.

Snowflake Multi-Cluster ArchitectureCloud Services Layer• Authentication & Security• Query Parsing & Optimization• Metadata Management• Transaction Management• Resource Management• Data Sharing Services• Replication & Failover• Directory Table ServicesAll layers are serverlessQuery Processing Layer• Virtual Warehouses (XS / S / M / L / XL / 2XL)• Multi-Cluster Warehouses• Auto-scale (min/max clusters)• Results Caching• Query Spillage to Disk• Materialized Views• Search OptimizationCompute is separate from storageStorage Layer• Micro-partitions (columnar)• Automatic Clustering• Time Travel (90 days)• Zero-Copy Cloning• Fail-safe (7 days)• Data Encryption (AES-256)• External Tables• Iceberg / Delta Lake SupportStorage on cloud provider (S3/GCS/ADLS)

Snowflake Key Features

FeatureDescriptionBenefit
Multi-Cluster WarehousesAuto-scaling compute clustersHandle varying query loads
Time TravelQuery data at any point in historyDebug, audit, recover data
Zero-Copy CloningCreate table copies without duplicating dataSave storage costs
SnowpipeContinuous data ingestionReal-time loading
Data SharingShare data across accounts without copyingCross-org collaboration
Streams & TasksCDC and scheduling within SnowflakeReduce external tools

5. End-to-End Integration Architecture

End-to-End Data Pipeline ArchitectureData Sources• Web Apps (REST)• Databases (CDC)• IoT / Log FilesKafkaIngest Layer• Buffer & Buffer• Schema RegistryAirflowOrchestration• Schedule DAGs• Monitor JobsPySparkProcessing• Transform Data• Clean & EnrichSnowflakeAnalytics Store• Query Data• Share DataDetailed Data Flow Steps1. ProduceApps send eventsto Kafka topicsJSON/Avro/Protobuf2. BufferKafka stores eventswith retention (7d)Replication factor=33. TriggerAirflow schedulesSparkSubmitOperatorPasses Kafka config4. TransformPySpark reads KafkaApplies business logicWrites to S3/Parquet5. LoadCOPY INTO Snowflakefrom S3 stageAuto-clusteringBatch Pipeline FlowAirflow triggers Spark every hourSpark reads from Kafka (last hour)Transforms, writes Parquet to S3COPY INTO Snowflake from stageStreaming Pipeline FlowPySpark reads Kafka continuouslyWindowed aggregations (5 min)foreachBatch writes to SnowflakeCheckpoint in S3 for fault toleranceMonitoring & QualityAirflow: task success/failure alertsSpark: SparkUI metrics, stage durationKafka: consumer lag monitoringSnowflake: query history, cost tracking

6. Technology Comparison Matrix

AspectKafkaAirflowPySparkSnowflake
Primary RoleIngestionOrchestrationProcessingStorage/Analytics
Data HandlingStreamingBatch schedulingBatch + StreamingBatch (Snowpipe for streaming)
ScalabilityHorizontal (brokers)Horizontal (workers)Horizontal (executors)Auto-scale (clusters)
State ManagementConsumer offsetsMetadata DBRDD lineageTransaction logs
Fault ToleranceReplicationTask retriesRDD fault toleranceTime travel + replication
Cost ModelPer brokerPer workerPer cluster hourPer compute + storage
LatencyMillisecondsMinutes (schedule)Seconds to minutesSeconds to minutes
LanguageJava/ScalaPython/JavaPython/Scala/JavaSQL

7. Integration Patterns

Pattern 1: Batch ETL (Kafka → Spark → S3 → Snowflake)

# Airflow DAG for batch ETL
from airflow import DAG
from airflow.providers.apache.spark.operators.spark_submit import SparkSubmitOperator
from airflow.providers.snowflake.operators.snowflake import SnowflakeOperator

with DAG('batch_etl', schedule_interval='0 * * * *') as dag:
    
    spark_job = SparkSubmitOperator(
        task_id='spark_kafka_to_s3',
        application='s3://jobs/kafka_batch_extract.py',
        conn_id='spark_default',
        conf={'spark.executor.memory': '4g'},
    )
    
    snowflake_load = SnowflakeOperator(
        task_id='snowflake_copy',
        sql="""
            COPY INTO raw_events
            FROM @s3_stage/events/
            FILE_FORMAT = (TYPE = PARQUET)
            PATTERN = '.*\\.parquet'
            ON_ERROR = 'CONTINUE';
        """,
        snowflake_conn_id='snowflake',
    )
    
    spark_job >> snowflake_load

Pattern 2: Streaming (Kafka → Spark Streaming → Snowflake)

# PySpark Structured Streaming with foreachBatch
def process_batch(batch_df, batch_id):
    """Write each micro-batch to Snowflake."""
    if batch_df.count() > 0:
        batch_df.write \
            .format("snowflake") \
            .option("url", SNOWFLAKE_URL) \
            .option("db", "RAW") \
            .option("table", "events") \
            .option("warehouse", "STREAMING_WH") \
            .mode("append") \
            .save()

stream = spark.readStream \
    .format("kafka") \
    .option("subscribe", "events") \
    .load() \
    .select(F.from_json(F.col("value").cast("string"), schema).alias("data")) \
    .select("data.*") \
    .writeStream \
    .foreachBatch(process_batch) \
    .trigger(processingTime="30 seconds") \
    .start()

Pattern 3: CDC (Kafka Connect → Kafka → Spark → Snowflake)

# Kafka Connect source connector config (JSON)
{
    "name": "postgres-cdc-source",
    "config": {
        "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
        "database.hostname": "source-db",
        "database.port": "5432",
        "database.user": "debezium",
        "database.dbname": "production",
        "topic.prefix": "cdc",
        "table.include.list": "public.orders,public.customers",
        "plugin.name": "pgoutput",
        "slot.name": "debezium_slot",
        "publication.name": "dbz_publication"
    }
}

8. Complete Project — E-Commerce Real-Time Analytics

Real-Time E-Commerce Analytics — Full Project ArchitectureUser clicks flow through Kafka, Airflow, PySpark, and Snowflake to business dashboardsWeb AppReact FrontendREST APIUser EventsMobile AppiOS / AndroidPush EventsLocation DataIoT SensorsWarehouse IoTGPS TrackersTemp SensorsKafkaEvent Streaming6 partitions, RF=3AirflowOrchestration EngineDAG: hourly_schedulePySparkData ProcessingTransform, Clean, EnrichS3 / GCSSilver LayerParquet FilesSnowflakeAnalytics StoreCOPY INTO tableDashboardsLooker, TableauReal-time KPIsKafka Topicsuser-events (6 partitions)order-updates (4 partitions)payment-events (8 partitions)Data LayersBronze: Raw (S3)Silver: Cleaned (S3)Gold: Aggregated (Snowflake)Airflow DAG Steps1. extract_from_kafka2. spark_transform3. load_to_s3_silver4. copy_into_snowflake5. data_quality_check6. notify_dashboard7. cleanup_checkpointsMonitoring StackAirflow UISpark UIKafka UISnowflake UIPagerDuty Alerts

Project Configuration Summary

ComponentTechnologyConfig
IngestionApache Kafka3 brokers, 6 partitions, RF=3, Avro schema
OrchestrationApache AirflowCelery executor, @hourly schedule, 2 retries
ProcessingPySpark4g executor memory, 200 shuffle partitions, AQE enabled
StorageSnowflakeX-Small warehouse, ANALYTICS DB, COPY INTO
MonitoringAirflow UI + Spark UI + Kafka UIPagerDuty alerts for failures

9. Interview Q&A Examples

🎯

Question 1: "Explain how you would design a real-time analytics pipeline using Kafka, Spark, and Snowflake."

Answer: The architecture follows a three-tier approach:

  1. Ingestion: Kafka receives events from microservices with topics partitioned by customer_id for ordering guarantees. Use Avro with Schema Registry for schema evolution.
  2. Processing: Spark Structured Streaming reads from Kafka with readStream, applies windowed aggregations using withWatermark, and writes micro-batches to Snowflake via foreachBatch. Checkpoint location in S3 provides fault tolerance.
  3. Storage: Snowflake's COPY INTO loads Parquet files from S3 staging, or Snowpipe handles continuous ingestion. Virtual warehouses auto-scale based on query load.
  4. Orchestration: Airflow triggers batch aggregation jobs hourly and monitors streaming job health via sensors.

🎯

Question 2: "How do you handle schema evolution when data format changes in a Kafka-to-Snowflake pipeline?"

Answer: Schema evolution is managed at multiple levels:

  • Kafka: Use Confluent Schema Registry with Avro/Protobuf. Backward/forward compatibility modes ensure old consumers can read new schemas.
  • Spark: Use mergeSchema option when reading/writing Parquet. Define explicit schemas instead of inferring to catch changes early.
  • Snowflake: Use ON_ERROR = 'CONTINUE' for partial loads. Snowflake automatically handles Parquet schema evolution with CHANGE_TRACKING. For CSV, use FORCE = TRUE to reload.
  • Monitoring: Airflow sensors check schema registry for breaking changes before processing.

🎯

Question 3: "Compare batch vs streaming processing for the Kafka-Spark-Snowflake stack."

Answer:

AspectBatchStreaming
Latency15 min - 1 hourSeconds - minutes
ThroughputHigher per runLower per micro-batch
ComplexitySimpler error handlingWatermarking, checkpointing
CostScheduled computeAlways-on compute
Use CaseHistorical reports, aggregationsReal-time dashboards, alerts
Failure RecoveryRe-run entire batchReplay from checkpoint

Choose batch for cost efficiency and simplicity; streaming when sub-hour latency is required.

🎯

Question 4: "How would you debug a pipeline where Spark jobs are reading from Kafka but Snowflake tables are empty?"

Answer: Systematic debugging approach:

  1. Check Kafka: Verify consumer lag with kafka-consumer-groups.sh. Confirm data exists in topic with kafka-console-consumer.
  2. Check Spark: Inspect SparkUI for failed stages. Check if foreachBatch function is writing data. Verify writeStream trigger is active.
  3. Check S3: If Spark writes to S3 first, verify Parquet files exist with correct timestamps.
  4. Check Snowflake: Run SHOW STAGES and LIST @s3_stage to see staged files. Check COPY INTO history with COPY_HISTORY table function. Verify file format and table schema match.
  5. Check Airflow: Review task logs for connection errors, SQL failures, or timeout issues.

🎯

Question 5: "Explain the role of Airflow in the modern data stack. Can you replace it with Spark scheduling alone?"

Answer: Airflow provides capabilities Spark lacks:

  • Cross-system orchestration: Airflow coordinates Kafka monitoring, Spark jobs, Snowflake loads, and external API calls. Spark only handles processing.
  • Dependency management: Airflow manages inter-task dependencies with DAGs. Spark only manages within-job stage dependencies.
  • Monitoring & alerting: Airflow provides UI, alerts, and SLA tracking across the entire pipeline.
  • Retries & error handling: Airflow retries failed tasks with exponential backoff. Spark restarts entire jobs.

You cannot replace Airflow with Spark scheduling alone. Spark is a processing engine; Airflow is an orchestration engine. They solve different problems.

🎯

Question 6: "How do you optimize a PySpark job reading from Kafka and writing to Snowflake?"

Answer: Key optimizations:

  • Kafka side: Increase maxRatePerPartition for throughput, use minPartitions for parallelism.
  • Spark side: Set spark.sql.shuffle.partitions to 2-3x executor cores. Use coalesce before writing to reduce small files. Enable AQE (spark.sql.adaptive.enabled=true) for dynamic optimization.
  • Snowflake side: Use AUTO_REFRESH = TRUE on external stages. Set appropriate WAREHOUSE size. Use FILE_FORMAT with compression. Batch COPY INTO every 15-30 minutes.
  • Data side: Filter early (push down predicates). Use columnar formats (Parquet). Partition by date for partition pruning.

9. Knowledge Check


10. Key Takeaways

  1. Kafka is the ingestion backbone — it buffers real-time events, decouples producers from consumers, and ensures fault-tolerant delivery with replication.

  2. Airflow is the orchestration brain — it schedules, monitors, and manages dependencies across Kafka, Spark, and Snowflake with DAG-based workflows.

  3. PySpark is the processing engine — it reads from Kafka, applies transformations using DataFrame API, and writes to Snowflake or intermediate storage.

  4. Snowflake is the analytics store — it provides separated compute/storage, auto-scaling warehouses, and SQL-based analytics on ingested data.

  5. Integration patterns include batch ETL (Spark writes Parquet, COPY INTO Snowflake), streaming (Spark Structured Streaming with foreachBatch), and CDC (Kafka Connect with Debezium).

  6. Airflow cannot be replaced by Spark scheduling — they solve different problems (orchestration vs. processing).


See Also

Need Expert Data Engineering Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement