Building NYC Taxi Data Pipeline with Spark and Kafka

🎯 Project Overview & Big Data Challenges

📋 The Complex World of Urban Transportation Data

New York City’s taxi system generates massive amounts of data every day - millions of trip records containing pickup locations, drop-off points, fare amounts, trip durations, and passenger counts. Processing this data at scale presents numerous technical challenges:

  • Volume: Millions of trip records per month (the public TLC monthly trip-record files are on the order of hundreds of MB each)
  • Velocity: Real-time trip events requiring sub-second processing for operational insights
  • Variety: Mixed data types from GPS coordinates to payment methods and traffic patterns
  • Veracity: Data quality issues from sensor errors, GPS drift, and missing records
  • Value: Extracting actionable insights for city planning, traffic optimization, and business intelligence

🎯 Solution Architecture & Design Philosophy

This project demonstrates a modern, hybrid data processing pipeline that addresses these challenges through:

  1. Batch Processing: Historical data analysis using Apache Spark and Hadoop ecosystem
  2. Stream Processing: Real-time event processing with Kafka and Spark Streaming
  3. Multi-Modal Storage: MySQL for transactional data, Hive for analytics, Elasticsearch for search
  4. Cloud-Native Design: AWS infrastructure with EMR, S3, Kinesis, and DynamoDB
  5. Observability: Comprehensive monitoring with ELK stack and custom metrics

💡 Core Philosophy: “Building scalable data pipelines that handle both historical analysis and real-time insights while maintaining data quality and operational reliability”

🤔 Why This Technology Stack?

Apache Spark Selection:

  • Unified Engine: Single framework for batch and stream processing
  • In-Memory Computing: 100x faster than Hadoop MapReduce for iterative algorithms
  • Scala Integration: Type-safe, functional programming for complex data transformations
  • SQL Interface: Familiar SQL queries for data analysts and business users

Kafka Streaming Benefits:

  • High Throughput: Millions of events per second with low latency
  • Fault Tolerance: Distributed, replicated log architecture
  • Exactly-Once Processing: Guarantees for critical business operations
  • Ecosystem Integration: Native connectors for databases, cloud services, and analytics tools

AWS Cloud Advantages:

  • Elastic Scaling: Auto-scaling clusters based on data volume and processing requirements
  • Managed Services: Reduced operational overhead with EMR, Kinesis, and DynamoDB
  • Cost Optimization: Pay-per-use model with spot instances and reserved capacity
  • Global Availability: Multi-region deployment for disaster recovery and performance

🏗️ System Architecture Overview

🔧 Technology Stack Deep Dive

 1Data Sources
 2├── NYC TLC Trip Records (Batch)
 3├── Real-time Taxi Events (Stream, simulated: TLC publishes no real-time feed)
 4├── Weather Data (External API)
 5└── Traffic Patterns (IoT Sensors)
 6
 7Batch Processing Layer
 8├── Apache Spark 3.1 (ships with EMR 6.3.0, used below)
 9├── Hadoop HDFS 3.x
10├── Apache Hive 3.x
11├── Python 3.8 / Scala 2.12
12└── AWS EMR Clusters
13
14Stream Processing Layer
15├── Apache Kafka 2.8
16├── Spark Structured Streaming 3.1
17├── Apache Zookeeper 3.7
18├── AWS Kinesis Data Streams
19└── Flink (Alternative Processing)
20
21Storage Layer
22├── Amazon S3 (Data Lake)
23├── MySQL 8.0 (Transactional)
24├── Apache Hive (Analytics)
25├── AWS DynamoDB (NoSQL)
26└── Elasticsearch 7.x (Search)
27
28Monitoring & Analytics
29├── ELK Stack (Elasticsearch, Logstash, Kibana)
30├── Prometheus (Metrics Collection)
31├── Grafana (Visualization)
32├── AWS CloudWatch (Infrastructure)
33└── Custom Dashboards
34
35Infrastructure
36├── AWS EMR (Managed Hadoop)
37├── EC2 Instances (Compute)
38├── Auto Scaling Groups
39├── VPC Networking
40└── IAM Security

🗺️ Data Pipeline Architecture

graph TB
    subgraph "Data Sources"
        A[NYC TLC Website]
        B[Real-time Events]
        C[External APIs]
    end

    subgraph "Ingestion Layer"
        D[S3 Data Lake]
        E[Kafka Cluster]
        F[Kinesis Streams]
    end

    subgraph "Processing Layer"
        G[Spark Batch Jobs]
        H[Spark Streaming]
        I[Flink Processing]
    end

    subgraph "Storage Layer"
        J[MySQL Database]
        K[Hive Data Warehouse]
        L[DynamoDB]
        M[Elasticsearch]
    end

    subgraph "Analytics Layer"
        N[Kibana Dashboards]
        O[Grafana Metrics]
        P[Custom APIs]
    end

    A --> D
    B --> E
    C --> F

    D --> G
    E --> H
    F --> I

    G --> J
    G --> K
    H --> L
    I --> M

    J --> N
    K --> O
    M --> P

    style D fill:#ff9800
    style G fill:#4caf50
    style H fill:#2196f3
    style J fill:#9c27b0
    style N fill:#f44336

🎨 Architecture Design Decisions & Rationale

1. Lambda Architecture Pattern

  • Why: Combines batch and stream processing for comprehensive data coverage
  • Benefits: Historical accuracy with real-time insights, fault tolerance, reprocessing capabilities
  • Implementation: Batch layer for accuracy, stream layer for speed, serving layer for queries

2. Multi-Storage Strategy

  • MySQL: ACID compliance for financial transactions and critical business data
  • Hive: Columnar storage for analytical queries and data warehousing
  • Elasticsearch: Full-text search, geospatial queries, and real-time analytics
  • DynamoDB: Low-latency NoSQL for session data and real-time lookups

3. Event-Driven Architecture

  • Kafka Topics: Partitioned by geographic zones for parallel processing
  • Schema Registry: Avro schemas for data evolution and compatibility
  • Exactly-Once Semantics: Critical for financial data and billing accuracy

4. Cloud-Native Design

  • EMR Clusters: Automatic scaling based on queue depth and processing time
  • S3 Tiering: Intelligent tiering for cost optimization (Standard → IA → Glacier)
  • Multi-AZ Deployment: High availability across availability zones

⭐ Core Features & Data Processing Capabilities

📊 1. Batch Data Processing Pipeline

Comprehensive Historical Analysis:

  • Daily Trip Aggregations: Total rides, revenue, popular routes, peak hours
  • Driver Performance Analytics: Top drivers by earnings, efficiency ratings, customer ratings
  • Geographic Insights: Hotspot analysis, traffic pattern identification, demand forecasting
  • Financial Reporting: Revenue analysis, fare distribution, payment method trends

🚀 2. Real-Time Stream Processing

Live Event Analytics:

  • Trip Monitoring: Real-time trip tracking, ETA calculations, route optimization
  • Surge Pricing: Dynamic fare adjustment based on supply/demand patterns
  • Fraud Detection: Anomaly detection for suspicious trip patterns or pricing
  • Operational Dashboards: Live metrics for fleet management and dispatch optimization

📍 3. Geospatial Analytics

Location Intelligence:

  • Zone Analysis: Borough-level and neighborhood-level trip distribution
  • Route Optimization: Shortest path calculations and traffic-aware routing
  • Demand Prediction: ML models for predicting ride demand by location and time
  • Heat Maps: Visual representation of pickup/dropoff density patterns

🔍 4. Advanced Analytics & ML

Machine Learning Integration:

  • Demand Forecasting: Time series models for predicting future ride demand
  • Price Optimization: ML algorithms for dynamic pricing strategies
  • Customer Segmentation: Clustering analysis for targeted marketing campaigns
  • Operational Optimization: Resource allocation and fleet management insights

🖥️ Backend Implementation Deep Dive

⚡ Spark Batch Processing Engine

Core Batch Processing Implementation:

  1import org.apache.spark.sql.{SparkSession, DataFrame}
  2import org.apache.spark.sql.functions._
  3import org.apache.spark.sql.types._
  4import java.time.LocalDateTime
  5
  6object TaxiDataProcessor {
  7
  8  case class TripRecord(
  9    vendorId: Int,
 10    pickupDateTime: String,
 11    dropoffDateTime: String,
 12    passengerCount: Int,
 13    tripDistance: Double,
 14    pickupLongitude: Double,
 15    pickupLatitude: Double,
 16    dropoffLongitude: Double,
 17    dropoffLatitude: Double,
 18    paymentType: Int,
 19    fareAmount: Double,
 20    extra: Double,
 21    mtaTax: Double,
 22    tipAmount: Double,
 23    tollsAmount: Double,
 24    totalAmount: Double
 25  )
 26
 27  def main(args: Array[String]): Unit = {
 28    val spark = createSparkSession()
 29
 30    try {
 31      // Process daily batch
 32      val inputPath = args(0)  // S3 path to raw data
 33      val outputPath = args(1) // S3 path for processed data
 34
 35      val tripData = loadTripData(spark, inputPath)
 36      val cleanedData = cleanAndValidateData(tripData)
 37      val aggregatedData = performAggregations(cleanedData)
 38
 39      // Save to multiple destinations
 40      saveToHive(spark, aggregatedData)
 41      saveToMySQL(aggregatedData)
 42      saveToS3(aggregatedData, outputPath)
 43
 44      // Generate analytics reports
 45      generateDailyReport(spark, cleanedData)
 46      updateDriverRankings(spark, cleanedData)
 47
 48      logger.info("Batch processing completed successfully")
 49
 50    } catch {
 51      case e: Exception =>
 52        logger.error(s"Batch processing failed: ${e.getMessage}", e)
 53        throw e
 54    } finally {
 55      spark.stop()
 56    }
 57  }
 58
 59  /**
 60   * Create optimized Spark session for batch processing
 61   */
 62  def createSparkSession(): SparkSession = {
 63    SparkSession.builder()
 64      .appName("NYC-Taxi-Batch-Pipeline")
 65      .config("spark.sql.adaptive.enabled", "true")
 66      .config("spark.sql.adaptive.coalescePartitions.enabled", "true")
 67      .config("spark.sql.adaptive.skewJoin.enabled", "true")
 68      .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
 69      .config("spark.sql.execution.arrow.pyspark.enabled", "true")
 70      .config("spark.dynamicAllocation.enabled", "true")
 71      .config("spark.dynamicAllocation.minExecutors", "2")
 72      .config("spark.dynamicAllocation.maxExecutors", "50")
 73      .config("spark.dynamicAllocation.initialExecutors", "10")
 74      .getOrCreate()
 75  }
 76
 77  /**
 78   * Load and parse taxi trip data from S3
 79   */
 80  def loadTripData(spark: SparkSession, inputPath: String): DataFrame = {
 81    spark.read
 82      .option("header", "true")
 83      .option("inferSchema", "true")
 84      .option("timestampFormat", "yyyy-MM-dd HH:mm:ss")
 85      .csv(inputPath)
 86      .filter(col("pickup_datetime").isNotNull)
 87      .filter(col("dropoff_datetime").isNotNull)
 88      .repartition(200, col("pickup_datetime"))  // Optimize partitioning
 89  }
 90
 91  /**
 92   * Data cleaning and validation pipeline
 93   */
 94  def cleanAndValidateData(df: DataFrame): DataFrame = {
 95    df
 96      .filter(col("trip_distance") > 0 && col("trip_distance") < 100)  // Reasonable distance
 97      .filter(col("fare_amount") > 0 && col("fare_amount") < 500)      // Reasonable fare
 98      .filter(col("passenger_count") > 0 && col("passenger_count") <= 9) // Valid passenger count
 99      .filter(col("pickup_latitude").between(40.5, 40.9))             // NYC bounds
100      .filter(col("pickup_longitude").between(-74.3, -73.7))
101      .filter(col("dropoff_latitude").between(40.5, 40.9))
102      .filter(col("dropoff_longitude").between(-74.3, -73.7))
103      .withColumn("trip_duration",
104        (unix_timestamp(col("dropoff_datetime")) -
105         unix_timestamp(col("pickup_datetime"))) / 60)  // Duration in minutes
106      .filter(col("trip_duration") > 1 && col("trip_duration") < 480)  // 1 min to 8 hours
107      .withColumn("hour_of_day", hour(col("pickup_datetime")))
108      .withColumn("day_of_week", dayofweek(col("pickup_datetime")))
109      .withColumn("pickup_zone", getPickupZone(col("pickup_latitude"), col("pickup_longitude")))
110      .withColumn("dropoff_zone", getDropoffZone(col("dropoff_latitude"), col("dropoff_longitude")))
111  }
112
113  /**
114   * Comprehensive data aggregations
115   */
116  def performAggregations(df: DataFrame): DataFrame = {
117    val dailyAggregations = df
118      .withColumn("date", to_date(col("pickup_datetime")))
119      .groupBy("date", "pickup_zone")
120      .agg(
121        count("*").alias("total_trips"),
122        sum("fare_amount").alias("total_revenue"),
123        avg("fare_amount").alias("avg_fare"),
124        avg("trip_distance").alias("avg_distance"),
125        avg("trip_duration").alias("avg_duration"),
126        sum("tip_amount").alias("total_tips"),
127        countDistinct("vendor_id").alias("active_vendors")
128      )
129      .orderBy("date", "pickup_zone")
130
131    // Hourly patterns
132    val hourlyPatterns = df
133      .withColumn("date", to_date(col("pickup_datetime")))
134      .groupBy("date", "hour_of_day", "pickup_zone")
135      .agg(
136        count("*").alias("hourly_trips"),
137        avg("fare_amount").alias("hourly_avg_fare")
138      )
139
140    // Driver performance (using vendor_id as proxy)
141    val driverMetrics = df
142      .groupBy("vendor_id", to_date(col("pickup_datetime")).alias("date"))
143      .agg(
144        count("*").alias("trips_completed"),
145        sum("fare_amount").alias("daily_earnings"),
146        avg("trip_duration").alias("avg_trip_time"),
147        (sum("tip_amount") / sum("fare_amount") * 100).alias("tip_percentage")
148      )
149      .withColumn("efficiency_score",
150        col("trips_completed") * 0.4 +
151        col("daily_earnings") * 0.3 +
152        (1 / col("avg_trip_time")) * 0.3)
153
154    dailyAggregations
155  }
156
157  /**
158   * Save processed data to Hive for analytics
159   */
160  def saveToHive(spark: SparkSession, df: DataFrame): Unit = {
161    df.write
162      .mode("append")
163      .partitionBy("date")
164      .option("compression", "snappy")
165      .saveAsTable("taxi_analytics.daily_trip_summary")
166  }
167
168  /**
169   * Save aggregated metrics to MySQL for applications
170   */
171  def saveToMySQL(df: DataFrame): Unit = {
172    val connectionProperties = new java.util.Properties()
173    connectionProperties.put("user", sys.env("MYSQL_USER"))
174    connectionProperties.put("password", sys.env("MYSQL_PASSWORD"))
175    connectionProperties.put("driver", "com.mysql.cj.jdbc.Driver")
176    connectionProperties.put("rewriteBatchedStatements", "true")
177    connectionProperties.put("useSSL", "false")
178
179    df.write
180      .mode("append")
181      .option("batchsize", "10000")
182      .option("truncate", "true")
183      .jdbc(
184        url = sys.env("MYSQL_URL"),
185        table = "trip_analytics",
186        connectionProperties = connectionProperties
187      )
188  }
189
190  /**
191   * Save to S3 for data lake storage
192   */
193  def saveToS3(df: DataFrame, outputPath: String): Unit = {
194    df.write
195      .mode("overwrite")
196      .partitionBy("date", "pickup_zone")
197      .option("compression", "gzip")
198      .parquet(outputPath)
199  }
200
201  /**
202   * Generate comprehensive daily reports
203   */
204  def generateDailyReport(spark: SparkSession, df: DataFrame): Unit = {
205    import spark.implicits._
206
207    // Top performing zones
208    val topZones = df
209      .groupBy("pickup_zone")
210      .agg(
211        count("*").alias("total_trips"),
212        sum("fare_amount").alias("total_revenue")
213      )
214      .orderBy(desc("total_revenue"))
215      .limit(10)
216
217    // Peak hour analysis
218    val peakHours = df
219      .groupBy("hour_of_day")
220      .agg(
221        count("*").alias("trips"),
222        avg("fare_amount").alias("avg_fare")
223      )
224      .orderBy(desc("trips"))
225
226    // Revenue trends
227    val revenueByHour = df
228      .withColumn("date_hour", date_format(col("pickup_datetime"), "yyyy-MM-dd HH"))
229      .groupBy("date_hour")
230      .agg(
231        sum("fare_amount").alias("hourly_revenue"),
232        count("*").alias("hourly_trips")
233      )
234      .orderBy("date_hour")
235
236    // Save reports
237    topZones.coalesce(1).write.mode("overwrite").csv("s3://taxi-reports/top-zones/")
238    peakHours.coalesce(1).write.mode("overwrite").csv("s3://taxi-reports/peak-hours/")
239    revenueByHour.write.mode("overwrite").partitionBy("date_hour").parquet("s3://taxi-reports/revenue-trends/")
240  }
241
242  /**
243   * Calculate pickup zone based on coordinates
244   */
245  def getPickupZone = udf((lat: Double, lon: Double) => {
246    (lat, lon) match {
247      case (l, o) if l >= 40.75 && l <= 40.80 && o >= -74.0 && o <= -73.95 => "Manhattan_Midtown"
248      case (l, o) if l >= 40.70 && l <= 40.75 && o >= -74.0 && o <= -73.95 => "Manhattan_Downtown"
249      case (l, o) if l >= 40.80 && l <= 40.85 && o >= -74.0 && o <= -73.95 => "Manhattan_Uptown"
250      case (l, o) if l >= 40.65 && l <= 40.72 && o >= -74.0 && o <= -73.80 => "Brooklyn"
251      case (l, o) if l >= 40.72 && l <= 40.80 && o >= -73.95 && o <= -73.75 => "Queens"
252      case (l, o) if l >= 40.80 && l <= 40.90 && o >= -73.90 && o <= -73.80 => "Bronx"
253      case _ => "Other"
254    }
255  })
256
257  /**
258   * Calculate dropoff zone based on coordinates
259   */
260  def getDropoffZone = udf((lat: Double, lon: Double) => {
261    // Same logic as pickup zone
262    getPickupZone.apply(lat, lon)
263  })
264}
265
266/**
267 * Configuration and utilities
268 */
269object TaxiDataConfig {
270
271  case class ProcessingConfig(
272    inputPath: String,
273    outputPath: String,
274    checkpointLocation: String,
275    batchInterval: String,
276    maxRecordsPerTrigger: Long
277  )
278
279  def loadConfig(): ProcessingConfig = {
280    ProcessingConfig(
281      inputPath = sys.env.getOrElse("INPUT_PATH", "s3a://nyc-tlc/trip-data/"),
282      outputPath = sys.env.getOrElse("OUTPUT_PATH", "s3a://processed-taxi-data/"),
283      checkpointLocation = sys.env.getOrElse("CHECKPOINT_PATH", "s3a://taxi-checkpoints/"),
284      batchInterval = sys.env.getOrElse("BATCH_INTERVAL", "10 minutes"),
285      maxRecordsPerTrigger = sys.env.getOrElse("MAX_RECORDS_PER_TRIGGER", "100000").toLong
286    )
287  }
288}

🌊 Kafka Stream Processing Engine

Real-Time Event Processing Implementation:

  1import org.apache.spark.sql.{SparkSession, DataFrame}
  2import org.apache.spark.sql.streaming.{StreamingQuery, Trigger}
  3import org.apache.spark.sql.functions._
  4import org.apache.spark.sql.types._
  5import org.apache.kafka.clients.producer.{KafkaProducer, ProducerRecord}
  6import java.util.Properties
  7
  8object TaxiStreamProcessor {
  9
 10  case class TaxiEvent(
 11    tripId: String,
 12    vendorId: Int,
 13    eventType: String,  // "pickup", "dropoff", "location_update"
 14    timestamp: Long,
 15    latitude: Double,
 16    longitude: Double,
 17    passengerCount: Int,
 18    fareAmount: Option[Double] = None,
 19    paymentType: Option[String] = None
 20  )
 21
 22  case class TripMetrics(
 23    zone: String,
 24    hour: Int,
 25    totalTrips: Long,
 26    avgFare: Double,
 27    avgDuration: Double,
 28    lastUpdate: Long
 29  )
 30
 31  def main(args: Array[String]): Unit = {
 32    val spark = createSparkSession()
 33    val config = TaxiDataConfig.loadConfig()
 34
 35    try {
 36      // Start real-time streaming pipelines
 37      val tripEventStream = processTripEvents(spark, config)
 38      val analyticsStream = processRealTimeAnalytics(spark, config)
 39      val anomalyStream = processAnomalyDetection(spark, config)
 40
 41      // Start all streaming queries
 42      val queries = List(tripEventStream, analyticsStream, anomalyStream)
 43
 44      // Wait for termination
 45      queries.foreach(_.awaitTermination())
 46
 47    } catch {
 48      case e: Exception =>
 49        logger.error(s"Stream processing failed: ${e.getMessage}", e)
 50        throw e
 51    } finally {
 52      spark.stop()
 53    }
 54  }
 55
 56  /**
 57   * Create optimized Spark session for streaming
 58   */
 59  def createSparkSession(): SparkSession = {
 60    SparkSession.builder()
 61      .appName("NYC-Taxi-Stream-Pipeline")
 62      .config("spark.sql.streaming.checkpointLocation", "/tmp/checkpoints")
 63      .config("spark.sql.streaming.forceDeleteTempCheckpointLocation", "true")
 64      .config("spark.sql.adaptive.enabled", "true")
 65      .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
 66      .config("spark.sql.streaming.stateStore.maintenanceInterval", "600s")
 67      .config("spark.sql.streaming.statefulOperator.checkCorrectness.enabled", "false")
 68      .getOrCreate()
 69  }
 70
 71  /**
 72   * Process individual trip events from Kafka
 73   */
 74  def processTripEvents(spark: SparkSession, config: ProcessingConfig): StreamingQuery = {
 75    import spark.implicits._
 76
 77    // Define schema for taxi events
 78    val taxiEventSchema = StructType(Array(
 79      StructField("trip_id", StringType, true),
 80      StructField("vendor_id", IntegerType, true),
 81      StructField("event_type", StringType, true),
 82      StructField("timestamp", LongType, true),
 83      StructField("latitude", DoubleType, true),
 84      StructField("longitude", DoubleType, true),
 85      StructField("passenger_count", IntegerType, true),
 86      StructField("fare_amount", DoubleType, true),
 87      StructField("payment_type", StringType, true)
 88    ))
 89
 90    // Read from Kafka topic
 91    val kafkaStream = spark
 92      .readStream
 93      .format("kafka")
 94      .option("kafka.bootstrap.servers", sys.env("KAFKA_BROKERS"))
 95      .option("subscribe", "taxi-events")
 96      .option("startingOffsets", "latest")
 97      .option("maxOffsetsPerTrigger", config.maxRecordsPerTrigger)
 98      .load()
 99
100    // Parse and process events
101    val parsedStream = kafkaStream
102      .select(from_json(col("value").cast("string"), taxiEventSchema).alias("data"))
103      .select("data.*")
104      .withColumn("processing_time", current_timestamp())
105      .withColumn("zone", getZoneFromCoordinates($"latitude", $"longitude"))
106      .withColumn("hour_of_day", hour(from_unixtime($"timestamp" / 1000)))
107
108    // Enrich with real-time data
109    val enrichedStream = parsedStream
110      .withColumn("weather_condition", getWeatherCondition($"latitude", $"longitude", $"timestamp"))
111      .withColumn("traffic_level", getTrafficLevel($"zone", $"hour_of_day"))
112      .filter($"latitude".between(40.5, 40.9) && $"longitude".between(-74.3, -73.7))
113
114    // Write to multiple sinks
115    enrichedStream.writeStream
116      .outputMode("append")
117      .format("console")  // For debugging
118      .trigger(Trigger.ProcessingTime("10 seconds"))
119      .start()
120
121    // Save to DynamoDB for real-time lookups
122    enrichedStream.writeStream
123      .outputMode("append")
124      .foreachBatch { (batchDF: DataFrame, batchId: Long) =>
125        saveToDynamoDB(batchDF, "taxi_events_realtime")
126      }
127      .trigger(Trigger.ProcessingTime("30 seconds"))
128      .start()
129
130    // Save to Elasticsearch for search and analytics
131    enrichedStream.writeStream
132      .outputMode("append")
133      .foreachBatch { (batchDF: DataFrame, batchId: Long) =>
134        saveToElasticsearch(batchDF, "taxi-events-live")
135      }
136      .trigger(Trigger.ProcessingTime("60 seconds"))
137      .start()
138  }
139
140  /**
141   * Process real-time analytics and aggregations
142   */
143  def processRealTimeAnalytics(spark: SparkSession, config: ProcessingConfig): StreamingQuery = {
144    import spark.implicits._
145
146    val kafkaStream = spark
147      .readStream
148      .format("kafka")
149      .option("kafka.bootstrap.servers", sys.env("KAFKA_BROKERS"))
150      .option("subscribe", "taxi-events")
151      .option("startingOffsets", "latest")
152      .load()
153
154    val taxiEvents = kafkaStream
155      .select(from_json(col("value").cast("string"), getTaxiEventSchema()).alias("data"))
156      .select("data.*")
157      .withColumn("zone", getZoneFromCoordinates($"latitude", $"longitude"))
158      .withColumn("event_time", to_timestamp(from_unixtime($"timestamp" / 1000)))
159      .withWatermark("event_time", "5 minutes")
160
161    // Real-time trip counts by zone (5-minute windows)
162    val tripCounts = taxiEvents
163      .filter($"event_type" === "pickup")
164      .groupBy(
165        window($"event_time", "5 minutes", "1 minute"),
166        $"zone"
167      )
168      .agg(
169        count("*").alias("trip_count"),
170        avg("passenger_count").alias("avg_passengers"),
171        approx_count_distinct("vendor_id").alias("active_drivers")
172      )
173      .select(
174        $"window.start".alias("window_start"),
175        $"window.end".alias("window_end"),
176        $"zone",
177        $"trip_count",
178        $"avg_passengers",
179        $"active_drivers"
180      )
181
182    // Real-time revenue tracking (10-minute windows)
183    val revenueMetrics = taxiEvents
184      .filter($"event_type" === "dropoff" && $"fare_amount".isNotNull)
185      .groupBy(
186        window($"event_time", "10 minutes", "2 minutes"),
187        $"zone"
188      )
189      .agg(
190        sum("fare_amount").alias("total_revenue"),
191        avg("fare_amount").alias("avg_fare"),
192        count("*").alias("completed_trips"),
193        stddev("fare_amount").alias("fare_stddev")
194      )
195
196    // Demand forecasting (15-minute windows)
197    val demandForecast = taxiEvents
198      .filter($"event_type" === "pickup")
199      .withColumn("hour_of_day", hour($"event_time"))
200      .withColumn("day_of_week", dayofweek($"event_time"))
201      .groupBy(
202        window($"event_time", "15 minutes"),
203        $"zone",
204        $"hour_of_day",
205        $"day_of_week"
206      )
207      .agg(
208        count("*").alias("current_demand"),
209        lag(count("*"), 1).over(
210          Window.partitionBy($"zone", $"hour_of_day", $"day_of_week")
211                .orderBy($"window.start")
212        ).alias("previous_demand")
213      )
214      .withColumn("demand_trend",
215        ($"current_demand" - coalesce($"previous_demand", lit(0))) /
216        coalesce($"previous_demand", lit(1))
217      )
218
219    // Write analytics to Kafka for downstream consumption
220    tripCounts.selectExpr("CAST(zone AS STRING) AS key", "to_json(struct(*)) AS value")
221      .writeStream
222      .format("kafka")
223      .option("kafka.bootstrap.servers", sys.env("KAFKA_BROKERS"))
224      .option("topic", "trip-counts-realtime")
225      .trigger(Trigger.ProcessingTime("30 seconds"))
226      .start()
227
228    // Write to Redis for real-time dashboards
229    revenueMetrics.writeStream
230      .outputMode("update")
231      .foreachBatch { (batchDF: DataFrame, batchId: Long) =>
232        saveToRedis(batchDF, "revenue_metrics")
233      }
234      .trigger(Trigger.ProcessingTime("60 seconds"))
235      .start()
236  }
237
238  /**
239   * Real-time anomaly detection
240   */
241  def processAnomalyDetection(spark: SparkSession, config: ProcessingConfig): StreamingQuery = {
242    import spark.implicits._
243
244    val kafkaStream = spark
245      .readStream
246      .format("kafka")
247      .option("kafka.bootstrap.servers", sys.env("KAFKA_BROKERS"))
248      .option("subscribe", "taxi-events")
249      .option("startingOffsets", "latest")
250      .load()
251
252    val taxiEvents = kafkaStream
253      .select(from_json(col("value").cast("string"), getTaxiEventSchema()).alias("data"))
254      .select("data.*")
255      .withColumn("event_time", to_timestamp(from_unixtime($"timestamp" / 1000)))
256      .withWatermark("event_time", "2 minutes")
257
258    // Detect unusual fare amounts (statistical outliers)
259    val fareAnomalies = taxiEvents
260      .filter($"event_type" === "dropoff" && $"fare_amount".isNotNull)
261      .withColumn("z_score",
262        (col("fare_amount") - mean("fare_amount").over(Window.partitionBy("zone"))) /
263        stddev("fare_amount").over(Window.partitionBy("zone"))
264      )
265      .filter(abs($"z_score") > 3.0)  // Outliers beyond 3 standard deviations
266      .select(
267        $"trip_id",
268        $"vendor_id",
269        $"zone",
270        $"fare_amount",
271        $"z_score",
272        $"event_time",
273        lit("FARE_ANOMALY").alias("anomaly_type")
274      )
275
276    // Detect suspicious trip patterns (too fast/slow)
277    val speedAnomalies = taxiEvents
278      .filter($"event_type" === "dropoff")
279      .withColumn("trip_distance_km", $"trip_distance" * 1.609344)  // Miles to km
280      .withColumn("trip_duration_hours", $"trip_duration" / 60.0)    // Minutes to hours
281      .withColumn("avg_speed_kmh", $"trip_distance_km" / $"trip_duration_hours")
282      .filter($"avg_speed_kmh" > 100 || $"avg_speed_kmh" < 2)  // Unrealistic speeds
283      .select(
284        $"trip_id",
285        $"vendor_id",
286        $"avg_speed_kmh",
287        $"trip_distance_km",
288        $"trip_duration_hours",
289        $"event_time",
290        lit("SPEED_ANOMALY").alias("anomaly_type")
291      )
292
293    // Combine all anomalies
294    val allAnomalies = fareAnomalies.unionByName(speedAnomalies)
295
296    // Send alerts to monitoring system
297    allAnomalies.writeStream
298      .outputMode("append")
299      .foreachBatch { (batchDF: DataFrame, batchId: Long) =>
300        sendAnomalyAlerts(batchDF)
301        saveAnomaliesForInvestigation(batchDF)
302      }
303      .trigger(Trigger.ProcessingTime("30 seconds"))
304      .start()
305  }
306
307  /**
308   * Save processed data to DynamoDB for real-time access
309   */
310  def saveToDynamoDB(df: DataFrame, tableName: String): Unit = {
311    df.foreachPartition { partition =>
312      val dynamoClient = createDynamoDBClient()
313
314      partition.foreach { row =>
315        val item = Map(
316          "trip_id" -> AttributeValue.builder().s(row.getAs[String]("trip_id")).build(),
317          "timestamp" -> AttributeValue.builder().n(row.getAs[Long]("timestamp").toString).build(),
318          "zone" -> AttributeValue.builder().s(row.getAs[String]("zone")).build(),
319          "event_type" -> AttributeValue.builder().s(row.getAs[String]("event_type")).build(),
320          "latitude" -> AttributeValue.builder().n(row.getAs[Double]("latitude").toString).build(),
321          "longitude" -> AttributeValue.builder().n(row.getAs[Double]("longitude").toString).build()
322        )
323
324        val putRequest = PutItemRequest.builder()
325          .tableName(tableName)
326          .item(item.asJava)
327          .build()
328
329        try {
330          dynamoClient.putItem(putRequest)
331        } catch {
332          case e: Exception =>
333            logger.error(s"Failed to save item to DynamoDB: ${e.getMessage}")
334        }
335      }
336    }
337  }
338
339  /**
340   * Save data to Elasticsearch for search and visualization
341   */
342  def saveToElasticsearch(df: DataFrame, indexName: String): Unit = {
343    df.write
344      .format("org.elasticsearch.spark.sql")
345      .option("es.resource", s"$indexName/_doc")
346      .option("es.nodes", sys.env("ELASTICSEARCH_NODES"))
347      .option("es.port", sys.env.getOrElse("ELASTICSEARCH_PORT", "9200"))
348      .option("es.index.auto.create", "true")
349      .option("es.write.operation", "create")
350      .mode("append")
351      .save()
352  }
353
354  /**
355   * Send anomaly alerts to monitoring system
356   */
357  def sendAnomalyAlerts(anomalies: DataFrame): Unit = {
358    anomalies.collect().foreach { anomaly =>
359      val alert = AnomalyAlert(
360        tripId = anomaly.getAs[String]("trip_id"),
361        anomalyType = anomaly.getAs[String]("anomaly_type"),
362        severity = "HIGH",
363        timestamp = anomaly.getAs[java.sql.Timestamp]("event_time"),
364        details = anomaly.toJson
365      )
366
367      // Send to alerting system (Kafka, SNS, etc.)
368      publishAlert(alert)
369    }
370  }
371
372  /**
373   * Get zone from coordinates using UDF
374   */
375  def getZoneFromCoordinates = udf((lat: Double, lon: Double) => {
376    // Implementation similar to batch processing
377    determineZone(lat, lon)
378  })
379
380  /**
381   * Helper functions for data enrichment
382   */
383  def getWeatherCondition = udf((lat: Double, lon: Double, timestamp: Long) => {
384    // Call external weather API or use cached weather data
385    "clear"  // Simplified for example
386  })
387
388  def getTrafficLevel = udf((zone: String, hour: Int) => {
389    // Determine traffic level based on historical patterns
390    if (hour >= 7 && hour <= 10 || hour >= 17 && hour <= 20) "high" else "normal"
391  })
392}
393
394/**
395 * Real-time event producer for testing
396 */
397object TaxiEventProducer {
398
399  def main(args: Array[String]): Unit = {
400    val producer = createKafkaProducer()
401    val eventGenerator = new TaxiEventGenerator()
402
403    try {
404      while (true) {
405        val event = eventGenerator.generateRandomEvent()
406        val record = new ProducerRecord[String, String](
407          "taxi-events",
408          event.tripId,
409          event.toJson
410        )
411
412        producer.send(record)
413        Thread.sleep(100)  // Generate event every 100ms
414      }
415    } finally {
416      producer.close()
417    }
418  }
419
420  def createKafkaProducer(): KafkaProducer[String, String] = {
421    val props = new Properties()
422    props.put("bootstrap.servers", sys.env("KAFKA_BROKERS"))
423    props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer")
424    props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer")
425    props.put("acks", "all")
426    props.put("retries", "3")
427    props.put("batch.size", "16384")
428    props.put("linger.ms", "1")
429
430    new KafkaProducer[String, String](props)
431  }
432}
433
434/**
435 * Random taxi event generator for testing
436 */
437class TaxiEventGenerator {
438  private val random = new scala.util.Random()
439  private val nycBounds = (40.5, 40.9, -74.3, -73.7)  // lat_min, lat_max, lon_min, lon_max
440  private val zones = List("Manhattan_Midtown", "Manhattan_Downtown", "Brooklyn", "Queens", "Bronx")
441
442  def generateRandomEvent(): TaxiEvent = {
443    TaxiEvent(
444      tripId = java.util.UUID.randomUUID().toString,
445      vendorId = random.nextInt(3) + 1,
446      eventType = if (random.nextBoolean()) "pickup" else "dropoff",
447      timestamp = System.currentTimeMillis(),
448      latitude = nycBounds._1 + random.nextDouble() * (nycBounds._2 - nycBounds._1),
449      longitude = nycBounds._3 + random.nextDouble() * (nycBounds._4 - nycBounds._3),
450      passengerCount = random.nextInt(6) + 1,
451      fareAmount = if (random.nextBoolean()) Some(5.0 + random.nextDouble() * 50.0) else None,
452      paymentType = if (random.nextBoolean()) Some(List("card", "cash", "mobile").apply(random.nextInt(3))) else None
453    )
454  }
455}

🗄️ Data Storage Integration

Multi-Modal Storage Strategy:

  1import os
  2import boto3
  3import pymysql
  4import redis
  5from elasticsearch import Elasticsearch
  6from datetime import datetime, timedelta
  7import json
  8import logging
  9
 10class TaxiDataStorage:
 11    """
 12    Unified interface for multiple storage systems in the taxi pipeline
 13    """
 14
 15    def __init__(self):
 16        self.mysql_client = self._create_mysql_client()
 17        self.dynamodb_client = self._create_dynamodb_client()
 18        self.s3_client = self._create_s3_client()
 19        self.redis_client = self._create_redis_client()
 20        self.es_client = self._create_elasticsearch_client()
 21        self.logger = self._setup_logging()
 22
 23    def _create_mysql_client(self):
 24        """Create MySQL connection for transactional data"""
 25        return pymysql.connect(
 26            host=os.environ['MYSQL_HOST'],
 27            port=int(os.environ.get('MYSQL_PORT', 3306)),
 28            user=os.environ['MYSQL_USER'],
 29            password=os.environ['MYSQL_PASSWORD'],
 30            database=os.environ['MYSQL_DATABASE'],
 31            charset='utf8mb4',
 32            autocommit=True,
 33            connect_timeout=30,
 34            read_timeout=30,
 35            write_timeout=30
 36        )
 37
 38    def _create_dynamodb_client(self):
 39        """Create DynamoDB client for real-time data"""
 40        return boto3.client(
 41            'dynamodb',
 42            region_name=os.environ.get('AWS_REGION', 'us-east-1'),
 43            aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
 44            aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY']
 45        )
 46
 47    def _create_s3_client(self):
 48        """Create S3 client for data lake storage"""
 49        return boto3.client(
 50            's3',
 51            region_name=os.environ.get('AWS_REGION', 'us-east-1'),
 52            aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
 53            aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY']
 54        )
 55
 56    def _create_redis_client(self):
 57        """Create Redis client for caching and real-time metrics"""
 58        return redis.Redis(
 59            host=os.environ['REDIS_HOST'],
 60            port=int(os.environ.get('REDIS_PORT', 6379)),
 61            password=os.environ.get('REDIS_PASSWORD'),
 62            decode_responses=True,
 63            socket_connect_timeout=10,
 64            socket_timeout=10,
 65            retry_on_timeout=True,
 66            health_check_interval=30
 67        )
 68
 69    def _create_elasticsearch_client(self):
 70        """Create Elasticsearch client for search and analytics"""
 71        return Elasticsearch(
 72            hosts=[{'host': os.environ['ES_HOST'], 'port': int(os.environ.get('ES_PORT', 9200))}],
 73            http_auth=(os.environ.get('ES_USERNAME'), os.environ.get('ES_PASSWORD')),
 74            use_ssl=os.environ.get('ES_USE_SSL', 'false').lower() == 'true',
 75            verify_certs=False,
 76            timeout=30,
 77            max_retries=3,
 78            retry_on_timeout=True
 79        )
 80
 81    def save_batch_analytics(self, analytics_data):
 82        """Save batch processing results to MySQL and Hive"""
 83        try:
 84            with self.mysql_client.cursor() as cursor:
 85                # Insert daily trip summary
 86                for record in analytics_data:
 87                    sql = """
 88                    INSERT INTO daily_trip_summary
 89                    (date, pickup_zone, total_trips, total_revenue, avg_fare,
 90                     avg_distance, avg_duration, total_tips, active_vendors)
 91                    VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
 92                    ON DUPLICATE KEY UPDATE
 93                    total_trips = VALUES(total_trips),
 94                    total_revenue = VALUES(total_revenue),
 95                    avg_fare = VALUES(avg_fare),
 96                    avg_distance = VALUES(avg_distance),
 97                    avg_duration = VALUES(avg_duration),
 98                    total_tips = VALUES(total_tips),
 99                    active_vendors = VALUES(active_vendors),
100                    updated_at = CURRENT_TIMESTAMP
101                    """
102
103                    cursor.execute(sql, (
104                        record['date'],
105                        record['pickup_zone'],
106                        record['total_trips'],
107                        record['total_revenue'],
108                        record['avg_fare'],
109                        record['avg_distance'],
110                        record['avg_duration'],
111                        record['total_tips'],
112                        record['active_vendors']
113                    ))
114
115            self.logger.info(f"Saved {len(analytics_data)} batch analytics records to MySQL")
116
117        except Exception as e:
118            self.logger.error(f"Failed to save batch analytics: {str(e)}")
119            raise
120
121    def save_realtime_event(self, event_data):
122        """Save real-time taxi events to DynamoDB"""
123        try:
124            item = {
125                'trip_id': {'S': event_data['trip_id']},
126                'timestamp': {'N': str(event_data['timestamp'])},
127                'event_type': {'S': event_data['event_type']},
128                'vendor_id': {'N': str(event_data['vendor_id'])},
129                'latitude': {'N': str(event_data['latitude'])},
130                'longitude': {'N': str(event_data['longitude'])},
131                'zone': {'S': event_data.get('zone', 'unknown')},
132                'passenger_count': {'N': str(event_data['passenger_count'])},
133                'processing_time': {'S': datetime.utcnow().isoformat()}
134            }
135
136            if event_data.get('fare_amount'):
137                item['fare_amount'] = {'N': str(event_data['fare_amount'])}
138
139            if event_data.get('payment_type'):
140                item['payment_type'] = {'S': event_data['payment_type']}
141
142            response = self.dynamodb_client.put_item(
143                TableName='taxi_events_realtime',
144                Item=item
145            )
146
147            self.logger.debug(f"Saved event {event_data['trip_id']} to DynamoDB")
148            return response
149
150        except Exception as e:
151            self.logger.error(f"Failed to save real-time event: {str(e)}")
152            raise
153
154    def cache_realtime_metrics(self, metrics_data):
155        """Cache real-time metrics in Redis for dashboard consumption"""
156        try:
157            pipeline = self.redis_client.pipeline()
158            current_time = int(datetime.utcnow().timestamp())
159
160            for zone, metrics in metrics_data.items():
161                key_prefix = f"realtime:{zone}"
162
163                # Cache trip counts (expire in 5 minutes)
164                pipeline.setex(f"{key_prefix}:trip_count", 300, metrics['trip_count'])
165                pipeline.setex(f"{key_prefix}:avg_fare", 300, metrics['avg_fare'])
166                pipeline.setex(f"{key_prefix}:active_drivers", 300, metrics['active_drivers'])
167
168                # Add to time series for trending (keep 24 hours)
169                pipeline.zadd(f"{key_prefix}:trip_series", {current_time: metrics['trip_count']})
170                pipeline.zremrangebyscore(f"{key_prefix}:trip_series", 0, current_time - 86400)
171
172                # Set expiration on time series keys
173                pipeline.expire(f"{key_prefix}:trip_series", 86400)
174
175            pipeline.execute()
176            self.logger.info(f"Cached metrics for {len(metrics_data)} zones")
177
178        except Exception as e:
179            self.logger.error(f"Failed to cache realtime metrics: {str(e)}")
180            raise
181
182    def index_for_search(self, trip_data):
183        """Index trip data in Elasticsearch for search and analytics"""
184        try:
185            actions = []
186            for trip in trip_data:
187                doc = {
188                    '_index': f"taxi-trips-{datetime.now().strftime('%Y-%m')}",
189                    '_type': '_doc',
190                    '_id': trip['trip_id'],
191                    '_source': {
192                        'trip_id': trip['trip_id'],
193                        'vendor_id': trip['vendor_id'],
194                        'pickup_datetime': trip['pickup_datetime'],
195                        'dropoff_datetime': trip.get('dropoff_datetime'),
196                        'pickup_location': {
197                            'lat': trip['pickup_latitude'],
198                            'lon': trip['pickup_longitude']
199                        },
200                        'dropoff_location': {
201                            'lat': trip.get('dropoff_latitude'),
202                            'lon': trip.get('dropoff_longitude')
203                        } if trip.get('dropoff_latitude') else None,
204                        'pickup_zone': trip['pickup_zone'],
205                        'dropoff_zone': trip.get('dropoff_zone'),
206                        'passenger_count': trip['passenger_count'],
207                        'trip_distance': trip.get('trip_distance'),
208                        'fare_amount': trip.get('fare_amount'),
209                        'tip_amount': trip.get('tip_amount'),
210                        'total_amount': trip.get('total_amount'),
211                        'payment_type': trip.get('payment_type'),
212                        'indexed_at': datetime.utcnow().isoformat()
213                    }
214                }
215                actions.append(doc)
216
217            # Bulk index documents
218            from elasticsearch.helpers import bulk
219            bulk(self.es_client, actions)
220            self.logger.info(f"Indexed {len(actions)} documents in Elasticsearch")
221
222        except Exception as e:
223            self.logger.error(f"Failed to index documents: {str(e)}")
224            raise
225
226    def archive_to_s3(self, data, s3_bucket, s3_key):
227        """Archive processed data to S3 data lake"""
228        try:
229            # Convert data to JSON lines format
230            json_data = '\n'.join([json.dumps(record) for record in data])
231
232            # Upload to S3 with compression
233            self.s3_client.put_object(
234                Bucket=s3_bucket,
235                Key=s3_key,
236                Body=json_data.encode('utf-8'),
237                ContentEncoding='gzip',
238                StorageClass='STANDARD_IA',  # Infrequent access for archival
239                Metadata={
240                    'created_at': datetime.utcnow().isoformat(),
241                    'record_count': str(len(data)),
242                    'pipeline_version': '1.0'
243                }
244            )
245
246            self.logger.info(f"Archived {len(data)} records to s3://{s3_bucket}/{s3_key}")
247
248        except Exception as e:
249            self.logger.error(f"Failed to archive to S3: {str(e)}")
250            raise
251
252    def get_realtime_zone_metrics(self, zone):
253        """Retrieve real-time metrics for a specific zone"""
254        try:
255            key_prefix = f"realtime:{zone}"
256
257            metrics = {}
258            metrics['trip_count'] = self.redis_client.get(f"{key_prefix}:trip_count") or 0
259            metrics['avg_fare'] = float(self.redis_client.get(f"{key_prefix}:avg_fare") or 0)
260            metrics['active_drivers'] = self.redis_client.get(f"{key_prefix}:active_drivers") or 0
261
262            # Get time series data for trending
263            current_time = int(datetime.utcnow().timestamp())
264            hour_ago = current_time - 3600
265
266            trip_series = self.redis_client.zrangebyscore(
267                f"{key_prefix}:trip_series",
268                hour_ago,
269                current_time,
270                withscores=True
271            )
272
273            metrics['trip_trend'] = [(int(score), int(value)) for value, score in trip_series]
274
275            return metrics
276
277        except Exception as e:
278            self.logger.error(f"Failed to get realtime metrics for {zone}: {str(e)}")
279            return {}
280
281    def search_trips(self, query_params):
282        """Search trip data using Elasticsearch"""
283        try:
284            # Build Elasticsearch query
285            query = {
286                "query": {
287                    "bool": {
288                        "must": []
289                    }
290                },
291                "sort": [{"pickup_datetime": {"order": "desc"}}],
292                "size": query_params.get('limit', 100)
293            }
294
295            # Add filters based on query parameters
296            if query_params.get('zone'):
297                query["query"]["bool"]["must"].append({
298                    "term": {"pickup_zone": query_params['zone']}
299                })
300
301            if query_params.get('date_from'):
302                query["query"]["bool"]["must"].append({
303                    "range": {
304                        "pickup_datetime": {
305                            "gte": query_params['date_from']
306                        }
307                    }
308                })
309
310            if query_params.get('fare_min'):
311                query["query"]["bool"]["must"].append({
312                    "range": {
313                        "fare_amount": {
314                            "gte": query_params['fare_min']
315                        }
316                    }
317                })
318
319            # Geospatial search if coordinates provided
320            if query_params.get('lat') and query_params.get('lon'):
321                query["query"]["bool"]["must"].append({
322                    "geo_distance": {
323                        "distance": query_params.get('radius', '1km'),
324                        "pickup_location": {
325                            "lat": query_params['lat'],
326                            "lon": query_params['lon']
327                        }
328                    }
329                })
330
331            # Execute search
332            response = self.es_client.search(
333                index="taxi-trips-*",
334                body=query
335            )
336
337            results = []
338            for hit in response['hits']['hits']:
339                results.append(hit['_source'])
340
341            return {
342                'total': response['hits']['total']['value'],
343                'results': results
344            }
345
346        except Exception as e:
347            self.logger.error(f"Failed to search trips: {str(e)}")
348            return {'total': 0, 'results': []}
349
350    def get_analytics_summary(self, date_from, date_to):
351        """Get analytics summary from MySQL"""
352        try:
353            with self.mysql_client.cursor(pymysql.cursors.DictCursor) as cursor:
354                sql = """
355                SELECT
356                    pickup_zone,
357                    SUM(total_trips) as total_trips,
358                    SUM(total_revenue) as total_revenue,
359                    AVG(avg_fare) as avg_fare,
360                    AVG(avg_distance) as avg_distance,
361                    MAX(total_trips) as peak_trips,
362                    COUNT(DISTINCT date) as active_days
363                FROM daily_trip_summary
364                WHERE date BETWEEN %s AND %s
365                GROUP BY pickup_zone
366                ORDER BY total_revenue DESC
367                LIMIT 20
368                """
369
370                cursor.execute(sql, (date_from, date_to))
371                results = cursor.fetchall()
372
373                return results
374
375        except Exception as e:
376            self.logger.error(f"Failed to get analytics summary: {str(e)}")
377            return []
378
379    def _setup_logging(self):
380        """Setup logging configuration"""
381        logging.basicConfig(
382            level=logging.INFO,
383            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
384        )
385        return logging.getLogger(__name__)
386
387    def health_check(self):
388        """Perform health check on all storage systems"""
389        health_status = {}
390
391        # MySQL health check
392        try:
393            with self.mysql_client.cursor() as cursor:
394                cursor.execute("SELECT 1")
395                health_status['mysql'] = 'healthy'
396        except Exception as e:
397            health_status['mysql'] = f'unhealthy: {str(e)}'
398
399        # Redis health check
400        try:
401            self.redis_client.ping()
402            health_status['redis'] = 'healthy'
403        except Exception as e:
404            health_status['redis'] = f'unhealthy: {str(e)}'
405
406        # Elasticsearch health check
407        try:
408            self.es_client.cluster.health()
409            health_status['elasticsearch'] = 'healthy'
410        except Exception as e:
411            health_status['elasticsearch'] = f'unhealthy: {str(e)}'
412
413        # DynamoDB health check
414        try:
415            self.dynamodb_client.describe_table(TableName='taxi_events_realtime')
416            health_status['dynamodb'] = 'healthy'
417        except Exception as e:
418            health_status['dynamodb'] = f'unhealthy: {str(e)}'
419
420        return health_status
421
422    def cleanup_old_data(self):
423        """Cleanup old data based on retention policies"""
424        try:
425            current_date = datetime.utcnow()
426
427            # Clean up old Redis time series (keep 7 days)
428            week_ago = int((current_date - timedelta(days=7)).timestamp())
429            keys = self.redis_client.keys("realtime:*:trip_series")
430
431            pipeline = self.redis_client.pipeline()
432            for key in keys:
433                pipeline.zremrangebyscore(key, 0, week_ago)
434            pipeline.execute()
435
436            self.logger.info(f"Cleaned up {len(keys)} Redis time series")
437
438            # Archive and clean up old Elasticsearch indices (keep 6 months)
439            six_months_ago = current_date - timedelta(days=180)
440            old_index = f"taxi-trips-{six_months_ago.strftime('%Y-%m')}"
441
442            if self.es_client.indices.exists(index=old_index):
443                # Archive to S3 before deletion
444                self._archive_elasticsearch_index(old_index)
445                self.es_client.indices.delete(index=old_index)
446                self.logger.info(f"Archived and deleted old index: {old_index}")
447
448        except Exception as e:
449            self.logger.error(f"Failed to cleanup old data: {str(e)}")
450
451    def _archive_elasticsearch_index(self, index_name):
452        """Archive Elasticsearch index to S3 before deletion"""
453        # Implementation would use Elasticsearch's scroll API to export data
454        # and save to S3 in compressed format
455        pass
456
457
458# Example usage and configuration
459if __name__ == "__main__":
460    storage = TaxiDataStorage()
461
462    # Example batch analytics save
463    analytics_data = [
464        {
465            'date': '2024-01-01',
466            'pickup_zone': 'Manhattan_Midtown',
467            'total_trips': 5000,
468            'total_revenue': 125000.0,
469            'avg_fare': 25.0,
470            'avg_distance': 2.5,
471            'avg_duration': 15.0,
472            'total_tips': 20000.0,
473            'active_vendors': 150
474        }
475    ]
476
477    storage.save_batch_analytics(analytics_data)
478
479    # Example real-time event
480    event = {
481        'trip_id': 'trip_12345',
482        'timestamp': int(datetime.utcnow().timestamp() * 1000),
483        'event_type': 'pickup',
484        'vendor_id': 1,
485        'latitude': 40.7589,
486        'longitude': -73.9851,
487        'zone': 'Manhattan_Midtown',
488        'passenger_count': 2
489    }
490
491    storage.save_realtime_event(event)
492
493    # Health check
494    health = storage.health_check()
495    print(f"System health: {health}")

🚀 AWS Infrastructure & Deployment

☁️ Cloud Architecture Design

Complete AWS Infrastructure Setup:

  1# aws-infrastructure.yml (CloudFormation/CDK Template)
  2AWSTemplateFormatVersion: '2010-09-09'
  3Description: 'NYC Taxi Data Pipeline - Complete Infrastructure'
  4
  5Parameters:
  6  Environment:
  7    Type: String
  8    Default: dev
  9    AllowedValues: [dev, staging, prod]
 10
 11  EMRClusterSize:
 12    Type: Number
 13    Default: 3
 14    MinValue: 1
 15    MaxValue: 20
 16
 17Resources:
 18  # VPC and Networking
 19  TaxiVPC:
 20    Type: AWS::EC2::VPC
 21    Properties:
 22      CidrBlock: 10.0.0.0/16
 23      EnableDnsHostnames: true
 24      EnableDnsSupport: true
 25      Tags:
 26        - Key: Name
 27          Value: !Sub taxi-pipeline-vpc-${Environment}
 28
 29  PublicSubnet1:
 30    Type: AWS::EC2::Subnet
 31    Properties:
 32      VpcId: !Ref TaxiVPC
 33      CidrBlock: 10.0.1.0/24
 34      AvailabilityZone: !Select [0, !GetAZs '']
 35      MapPublicIpOnLaunch: true
 36
 37  PrivateSubnet1:
 38    Type: AWS::EC2::Subnet
 39    Properties:
 40      VpcId: !Ref TaxiVPC
 41      CidrBlock: 10.0.2.0/24
 42      AvailabilityZone: !Select [0, !GetAZs '']
 43
 44  PrivateSubnet2:
 45    Type: AWS::EC2::Subnet
 46    Properties:
 47      VpcId: !Ref TaxiVPC
 48      CidrBlock: 10.0.3.0/24
 49      AvailabilityZone: !Select [1, !GetAZs '']
 50
 51  # S3 Data Lake
 52  DataLakeBucket:
 53    Type: AWS::S3::Bucket
 54    Properties:
 55      BucketName: !Sub nyc-taxi-data-lake-${Environment}-${AWS::AccountId}
 56      VersioningConfiguration:
 57        Status: Enabled
 58      BucketEncryption:
 59        ServerSideEncryptionConfiguration:
 60          - ServerSideEncryptionByDefault:
 61              SSEAlgorithm: AES256
 62      LifecycleConfiguration:
 63        Rules:
 64          - Id: DataLifecycle
 65            Status: Enabled
 66            Transitions:
 67              - TransitionInDays: 30
 68                StorageClass: STANDARD_IA
 69              - TransitionInDays: 90
 70                StorageClass: GLACIER
 71              - TransitionInDays: 365
 72                StorageClass: DEEP_ARCHIVE
 73      NotificationConfiguration:
 74        CloudWatchConfigurations:
 75          - Event: s3:ObjectCreated:*
 76            CloudWatchConfiguration:
 77              LogGroupName: !Ref DataPipelineLogs
 78
 79  ProcessedDataBucket:
 80    Type: AWS::S3::Bucket
 81    Properties:
 82      BucketName: !Sub nyc-taxi-processed-${Environment}-${AWS::AccountId}
 83      VersioningConfiguration:
 84        Status: Enabled
 85
 86  # EMR Cluster for Batch Processing
 87  EMRCluster:
 88    Type: AWS::EMR::Cluster
 89    Properties:
 90      Name: !Sub taxi-pipeline-emr-${Environment}
 91      ReleaseLabel: emr-6.3.0
 92      Applications:
 93        - Name: Spark
 94        - Name: Hadoop
 95        - Name: Hive
 96        - Name: Kafka
 97        - Name: Zookeeper
 98      ServiceRole: !Ref EMRServiceRole
 99      JobFlowRole: !Ref EMRInstanceProfile
100      LogUri: !Sub s3://${DataLakeBucket}/emr-logs/
101      Instances:
102        MasterInstanceGroup:
103          InstanceCount: 1
104          InstanceType: m5.xlarge
105          Market: ON_DEMAND
106          Name: Master
107        CoreInstanceGroup:
108          InstanceCount: !Ref EMRClusterSize
109          InstanceType: m5.large
110          Market: SPOT
111          Name: Core
112        Ec2SubnetId: !Ref PrivateSubnet1
113        EmrManagedMasterSecurityGroup: !Ref EMRMasterSecurityGroup
114        EmrManagedSlaveSecurityGroup: !Ref EMRSlaveSecurityGroup
115      Configurations:
116        - Classification: spark-defaults
117          ConfigurationProperties:
118            spark.sql.adaptive.enabled: "true"
119            spark.sql.adaptive.coalescePartitions.enabled: "true"
120            spark.dynamicAllocation.enabled: "true"
121            spark.serializer: org.apache.spark.serializer.KryoSerializer
122        - Classification: spark-hive-site
123          ConfigurationProperties:
124            javax.jdo.option.ConnectionURL: !Sub
125              - jdbc:mysql://${DBEndpoint}:3306/hive_metastore
126              - DBEndpoint: !GetAtt MetastoreDB.Endpoint.Address
127        - Classification: kafka-broker
128          ConfigurationProperties:
129            num.partitions: "10"
130            default.replication.factor: "2"
131            min.insync.replicas: "1"
132
133  # RDS MySQL for Hive Metastore and Analytics
134  MetastoreDB:
135    Type: AWS::RDS::DBInstance
136    Properties:
137      DBInstanceIdentifier: !Sub taxi-metastore-${Environment}
138      DBName: hive_metastore
139      Engine: mysql
140      EngineVersion: 8.0.28
141      DBInstanceClass: db.t3.medium
142      AllocatedStorage: 100
143      StorageType: gp2
144      StorageEncrypted: true
145      MasterUsername: admin
146      MasterUserPassword: !Ref DBPassword
147      VPCSecurityGroups:
148        - !Ref DatabaseSecurityGroup
149      DBSubnetGroupName: !Ref DBSubnetGroup
150      BackupRetentionPeriod: 7
151      MultiAZ: !If [IsProd, true, false]
152      DeletionProtection: !If [IsProd, true, false]
153
154  AnalyticsDB:
155    Type: AWS::RDS::DBInstance
156    Properties:
157      DBInstanceIdentifier: !Sub taxi-analytics-${Environment}
158      DBName: taxi_analytics
159      Engine: mysql
160      EngineVersion: 8.0.28
161      DBInstanceClass: !If [IsProd, db.r5.large, db.t3.medium]
162      AllocatedStorage: 200
163      StorageType: gp2
164      StorageEncrypted: true
165      MasterUsername: admin
166      MasterUserPassword: !Ref DBPassword
167      VPCSecurityGroups:
168        - !Ref DatabaseSecurityGroup
169      DBSubnetGroupName: !Ref DBSubnetGroup
170      BackupRetentionPeriod: 30
171      MultiAZ: !If [IsProd, true, false]
172      ReadReplicaDBInstanceIdentifiers:
173        - !If [IsProd, !Sub "${AWS::StackName}-analytics-read-replica", !Ref "AWS::NoValue"]
174
175  # DynamoDB for Real-time Data
176  RealTimeEventsTable:
177    Type: AWS::DynamoDB::Table
178    Properties:
179      TableName: !Sub taxi_events_realtime_${Environment}
180      BillingMode: ON_DEMAND
181      AttributeDefinitions:
182        - AttributeName: trip_id
183          AttributeType: S
184        - AttributeName: timestamp
185          AttributeType: N
186        - AttributeName: zone
187          AttributeType: S
188      KeySchema:
189        - AttributeName: trip_id
190          KeyType: HASH
191        - AttributeName: timestamp
192          KeyType: RANGE
193      GlobalSecondaryIndexes:
194        - IndexName: zone-timestamp-index
195          KeySchema:
196            - AttributeName: zone
197              KeyType: HASH
198            - AttributeName: timestamp
199              KeyType: RANGE
200          Projection:
201            ProjectionType: ALL
202      TimeToLiveSpecification:
203        AttributeName: ttl
204        Enabled: true
205      PointInTimeRecoverySpecification:
206        PointInTimeRecoveryEnabled: true
207      StreamSpecification:
208        StreamViewType: NEW_AND_OLD_IMAGES
209
210  # ElastiCache Redis for Caching
211  RedisCluster:
212    Type: AWS::ElastiCache::ReplicationGroup
213    Properties:
214      ReplicationGroupId: !Sub taxi-cache-${Environment}
215      Description: Redis cluster for taxi pipeline caching
216      NumCacheClusters: !If [IsProd, 3, 2]
217      Engine: redis
218      CacheNodeType: !If [IsProd, cache.r6g.large, cache.t3.micro]
219      Port: 6379
220      SecurityGroupIds:
221        - !Ref CacheSecurityGroup
222      SubnetGroupName: !Ref CacheSubnetGroup
223      AutomaticFailoverEnabled: !If [IsProd, true, false]
224      MultiAZEnabled: !If [IsProd, true, false]
225      AtRestEncryptionEnabled: true
226      TransitEncryptionEnabled: true
227      SnapshotRetentionLimit: 7
228
229  # Elasticsearch for Search and Analytics
230  ElasticsearchDomain:
231    Type: AWS::Elasticsearch::Domain
232    Properties:
233      DomainName: !Sub taxi-search-${Environment}
234      ElasticsearchVersion: 7.10
235      ElasticsearchClusterConfig:
236        InstanceType: !If [IsProd, r5.large.elasticsearch, t3.small.elasticsearch]
237        InstanceCount: !If [IsProd, 3, 1]
238        DedicatedMasterEnabled: !If [IsProd, true, false]
239        MasterInstanceType: !If [IsProd, r5.medium.elasticsearch, !Ref "AWS::NoValue"]
240        MasterInstanceCount: !If [IsProd, 3, !Ref "AWS::NoValue"]
241      EBSOptions:
242        EBSEnabled: true
243        VolumeType: gp2
244        VolumeSize: !If [IsProd, 100, 20]
245      VPCOptions:
246        SubnetIds: [!Ref PrivateSubnet1, !Ref PrivateSubnet2]
247        SecurityGroupIds: [!Ref ElasticsearchSecurityGroup]
248      EncryptionAtRestOptions:
249        Enabled: true
250      NodeToNodeEncryptionOptions:
251        Enabled: true
252      DomainEndpointOptions:
253        EnforceHTTPS: true
254
255  # Kinesis Data Streams
256  TaxiEventStream:
257    Type: AWS::Kinesis::Stream
258    Properties:
259      Name: !Sub taxi-events-${Environment}
260      ShardCount: !If [IsProd, 10, 2]
261      RetentionPeriod: 168  # 7 days
262      EncryptionType: KMS
263      KMSKeyId: alias/aws/kinesis
264
265  # Lambda Functions for Stream Processing
266  StreamProcessorLambda:
267    Type: AWS::Lambda::Function
268    Properties:
269      FunctionName: !Sub taxi-stream-processor-${Environment}
270      Runtime: python3.9
271      Handler: lambda_function.lambda_handler
272      Code:
273        ZipFile: |
274          import json
275          import boto3
276          import base64
277
278          def lambda_handler(event, context):
279              dynamodb = boto3.resource('dynamodb')
280              table = dynamodb.Table(os.environ['DYNAMODB_TABLE'])
281
282              for record in event['Records']:
283                  # Decode Kinesis data
284                  payload = json.loads(base64.b64decode(record['kinesis']['data']))
285
286                  # Process and store in DynamoDB
287                  table.put_item(Item=payload)
288
289              return {'statusCode': 200}          
290      Environment:
291        Variables:
292          DYNAMODB_TABLE: !Ref RealTimeEventsTable
293      Role: !GetAtt LambdaExecutionRole.Arn
294      Timeout: 60
295      MemorySize: 256
296
297  # Step Functions for Workflow Orchestration
298  DataPipelineStateMachine:
299    Type: AWS::StepFunctions::StateMachine
300    Properties:
301      StateMachineName: !Sub taxi-pipeline-workflow-${Environment}
302      DefinitionString: !Sub |
303        {
304          "Comment": "NYC Taxi Data Pipeline Workflow",
305          "StartAt": "CheckDataAvailability",
306          "States": {
307            "CheckDataAvailability": {
308              "Type": "Task",
309              "Resource": "${CheckDataLambda.Arn}",
310              "Next": "ProcessBatchData"
311            },
312            "ProcessBatchData": {
313              "Type": "Task",
314              "Resource": "arn:aws:states:::emr:addStep.sync",
315              "Parameters": {
316                "ClusterId": "${EMRCluster}",
317                "Step": {
318                  "Name": "Process NYC Taxi Data",
319                  "ActionOnFailure": "TERMINATE_CLUSTER",
320                  "HadoopJarStep": {
321                    "Jar": "command-runner.jar",
322                    "Args": [
323                      "spark-submit",
324                      "--class", "TaxiDataProcessor",
325                      "s3://${ProcessedDataBucket}/jars/taxi-processor.jar",
326                      "s3://${DataLakeBucket}/raw-data/",
327                      "s3://${ProcessedDataBucket}/daily-output/"
328                    ]
329                  }
330                }
331              },
332              "Next": "GenerateReports"
333            },
334            "GenerateReports": {
335              "Type": "Task",
336              "Resource": "${GenerateReportsLambda.Arn}",
337              "Next": "NotifySuccess"
338            },
339            "NotifySuccess": {
340              "Type": "Task",
341              "Resource": "${NotificationLambda.Arn}",
342              "End": true
343            }
344          }
345        }
346      RoleArn: !GetAtt StepFunctionsExecutionRole.Arn
347
348  # CloudWatch Dashboard
349  PipelineDashboard:
350    Type: AWS::CloudWatch::Dashboard
351    Properties:
352      DashboardName: !Sub taxi-pipeline-${Environment}
353      DashboardBody: !Sub |
354        {
355          "widgets": [
356            {
357              "type": "metric",
358              "properties": {
359                "metrics": [
360                  ["AWS/EMR", "IsIdle", "JobFlowId", "${EMRCluster}"],
361                  ["AWS/DynamoDB", "ConsumedReadCapacityUnits", "TableName", "${RealTimeEventsTable}"],
362                  ["AWS/Kinesis", "IncomingRecords", "StreamName", "${TaxiEventStream}"],
363                  ["AWS/ES", "IndexingRate", "DomainName", "${ElasticsearchDomain}"]
364                ],
365                "period": 300,
366                "stat": "Average",
367                "region": "${AWS::Region}",
368                "title": "Pipeline Metrics"
369              }
370            }
371          ]
372        }
373
374Conditions:
375  IsProd: !Equals [!Ref Environment, prod]
376
377Outputs:
378  EMRClusterId:
379    Value: !Ref EMRCluster
380    Export:
381      Name: !Sub ${AWS::StackName}-EMRCluster
382
383  DataLakeBucket:
384    Value: !Ref DataLakeBucket
385    Export:
386      Name: !Sub ${AWS::StackName}-DataLake
387
388  AnalyticsDBEndpoint:
389    Value: !GetAtt AnalyticsDB.Endpoint.Address
390    Export:
391      Name: !Sub ${AWS::StackName}-AnalyticsDB
392
393  ElasticsearchEndpoint:
394    Value: !GetAtt ElasticsearchDomain.DomainEndpoint
395    Export:
396      Name: !Sub ${AWS::StackName}-Elasticsearch

🔄 CI/CD Pipeline Configuration

Complete Deployment Pipeline:

  1# .github/workflows/deploy-pipeline.yml
  2name: NYC Taxi Pipeline CI/CD
  3
  4on:
  5  push:
  6    branches: [main, develop]
  7  pull_request:
  8    branches: [main]
  9
 10env:
 11  AWS_REGION: us-east-1
 12  SCALA_VERSION: 2.12.15
 13  SPARK_VERSION: 3.2.0
 14
 15jobs:
 16  test:
 17    runs-on: ubuntu-latest
 18    services:
 19      mysql:
 20        image: mysql:8.0
 21        env:
 22          MYSQL_ROOT_PASSWORD: test_password
 23          MYSQL_DATABASE: test_db
 24        ports:
 25          - 3306:3306
 26        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
 27
 28      redis:
 29        image: redis:7
 30        ports:
 31          - 6379:6379
 32        options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=5
 33
 34    steps:
 35    - name: Checkout code
 36      uses: actions/checkout@v3
 37
 38    - name: Setup Java
 39      uses: actions/setup-java@v3
 40      with:
 41        java-version: '11'
 42        distribution: 'temurin'
 43
 44    - name: Setup Scala
 45      uses: olafurpg/setup-scala@v13
 46      with:
 47        java-version: '11'
 48
 49    - name: Setup Python
 50      uses: actions/setup-python@v4
 51      with:
 52        python-version: '3.9'
 53
 54    - name: Cache SBT dependencies
 55      uses: actions/cache@v3
 56      with:
 57        path: ~/.sbt
 58        key: ${{ runner.os }}-sbt-${{ hashFiles('**/build.sbt') }}
 59
 60    - name: Cache Python dependencies
 61      uses: actions/cache@v3
 62      with:
 63        path: ~/.cache/pip
 64        key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
 65
 66    - name: Install Python dependencies
 67      run: |
 68        pip install -r requirements.txt
 69        pip install pytest pytest-cov        
 70
 71    - name: Run Python tests
 72      run: |
 73        pytest tests/python/ --cov=src/python --cov-report=xml        
 74
 75    - name: Run Scala tests
 76      run: |
 77        cd src/scala
 78        sbt test        
 79
 80    - name: Run integration tests
 81      run: |
 82        # Start local Kafka for integration tests
 83        docker-compose -f docker/test-compose.yml up -d
 84        sleep 30
 85        pytest tests/integration/ -v
 86        docker-compose -f docker/test-compose.yml down        
 87
 88    - name: Upload coverage reports
 89      uses: codecov/codecov-action@v3
 90      with:
 91        file: ./coverage.xml
 92
 93  build:
 94    needs: test
 95    runs-on: ubuntu-latest
 96    outputs:
 97      image-tag: ${{ steps.build-info.outputs.image-tag }}
 98
 99    steps:
100    - name: Checkout code
101      uses: actions/checkout@v3
102
103    - name: Setup Java
104      uses: actions/setup-java@v3
105      with:
106        java-version: '11'
107        distribution: 'temurin'
108
109    - name: Build Scala application
110      run: |
111        cd src/scala
112        sbt assembly        
113
114    - name: Build Python packages
115      run: |
116        cd src/python
117        python setup.py bdist_wheel        
118
119    - name: Configure AWS credentials
120      uses: aws-actions/configure-aws-credentials@v2
121      with:
122        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
123        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
124        aws-region: ${{ env.AWS_REGION }}
125
126    - name: Login to Amazon ECR
127      id: login-ecr
128      uses: aws-actions/amazon-ecr-login@v1
129
130    - name: Build, tag, and push image to Amazon ECR
131      id: build-info
132      env:
133        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
134        ECR_REPOSITORY: nyc-taxi-pipeline
135      run: |
136        IMAGE_TAG=${GITHUB_SHA::8}
137        echo "image-tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
138
139        # Build Docker image with multi-stage build
140        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
141        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:latest .
142
143        # Push images
144        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
145        docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest        
146
147    - name: Upload artifacts to S3
148      run: |
149        # Upload Scala JAR
150        aws s3 cp src/scala/target/scala-2.12/taxi-processor-assembly.jar \
151          s3://nyc-taxi-artifacts-${{ github.ref_name }}/jars/
152
153        # Upload Python wheels
154        aws s3 cp src/python/dist/ \
155          s3://nyc-taxi-artifacts-${{ github.ref_name }}/python/ \
156          --recursive        
157
158  deploy-dev:
159    needs: build
160    runs-on: ubuntu-latest
161    if: github.ref == 'refs/heads/develop'
162    environment: dev
163
164    steps:
165    - name: Checkout code
166      uses: actions/checkout@v3
167
168    - name: Configure AWS credentials
169      uses: aws-actions/configure-aws-credentials@v2
170      with:
171        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
172        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
173        aws-region: ${{ env.AWS_REGION }}
174
175    - name: Deploy infrastructure
176      run: |
177        aws cloudformation deploy \
178          --template-file infrastructure/aws-infrastructure.yml \
179          --stack-name taxi-pipeline-dev \
180          --parameter-overrides \
181            Environment=dev \
182            EMRClusterSize=2 \
183          --capabilities CAPABILITY_IAM        
184
185    - name: Update EMR steps
186      run: |
187        # Get cluster ID from CloudFormation outputs
188        CLUSTER_ID=$(aws cloudformation describe-stacks \
189          --stack-name taxi-pipeline-dev \
190          --query 'Stacks[0].Outputs[?OutputKey==`EMRClusterId`].OutputValue' \
191          --output text)
192
193        # Add processing step
194        aws emr add-steps \
195          --cluster-id $CLUSTER_ID \
196          --steps file://infrastructure/emr-steps.json        
197
198    - name: Deploy Lambda functions
199      run: |
200        # Package and deploy Lambda functions
201        cd src/lambda
202        zip -r lambda-package.zip .
203
204        aws lambda update-function-code \
205          --function-name taxi-stream-processor-dev \
206          --zip-file fileb://lambda-package.zip        
207
208    - name: Update configurations
209      run: |
210        # Update application configurations
211        aws ssm put-parameter \
212          --name "/taxi-pipeline/dev/image-tag" \
213          --value "${{ needs.build.outputs.image-tag }}" \
214          --type String \
215          --overwrite        
216
217  deploy-prod:
218    needs: build
219    runs-on: ubuntu-latest
220    if: github.ref == 'refs/heads/main'
221    environment: production
222
223    steps:
224    - name: Checkout code
225      uses: actions/checkout@v3
226
227    - name: Configure AWS credentials
228      uses: aws-actions/configure-aws-credentials@v2
229      with:
230        aws-access-key-id: ${{ secrets.PROD_AWS_ACCESS_KEY_ID }}
231        aws-secret-access-key: ${{ secrets.PROD_AWS_SECRET_ACCESS_KEY }}
232        aws-region: ${{ env.AWS_REGION }}
233
234    - name: Deploy infrastructure with change sets
235      run: |
236        # Create change set for production deployment
237        aws cloudformation create-change-set \
238          --template-body file://infrastructure/aws-infrastructure.yml \
239          --stack-name taxi-pipeline-prod \
240          --change-set-name prod-deployment-$(date +%s) \
241          --parameter-overrides \
242            Environment=prod \
243            EMRClusterSize=5 \
244          --capabilities CAPABILITY_IAM
245
246        # Wait for change set creation and execute
247        aws cloudformation wait change-set-create-complete \
248          --stack-name taxi-pipeline-prod \
249          --change-set-name prod-deployment-$(date +%s)
250
251        aws cloudformation execute-change-set \
252          --stack-name taxi-pipeline-prod \
253          --change-set-name prod-deployment-$(date +%s)        
254
255    - name: Blue-Green deployment for Lambda
256      run: |
257        # Deploy new version with alias
258        aws lambda publish-version \
259          --function-name taxi-stream-processor-prod \
260          --zip-file fileb://src/lambda/lambda-package.zip
261
262        # Update alias to point to new version
263        aws lambda update-alias \
264          --function-name taxi-stream-processor-prod \
265          --name LIVE \
266          --function-version $LATEST        
267
268    - name: Post-deployment validation
269      run: |
270        # Run health checks
271        python scripts/health-check.py --environment prod
272
273        # Validate data pipeline
274        python scripts/pipeline-validation.py --environment prod        
275
276    - name: Notify deployment
277      run: |
278        # Send Slack notification
279        curl -X POST -H 'Content-type: application/json' \
280          --data '{"text":"NYC Taxi Pipeline deployed to production successfully"}' \
281          ${{ secrets.SLACK_WEBHOOK }}        
282
283  cleanup:
284    runs-on: ubuntu-latest
285    if: always()
286    needs: [test, build, deploy-dev, deploy-prod]
287
288    steps:
289    - name: Cleanup temporary resources
290      run: |
291        # Clean up any temporary resources created during deployment
292        echo "Cleaning up temporary resources..."        

📊 Performance Metrics & Optimization

🚀 System Performance Analysis

Performance Benchmarks & Optimization Results:

  1import time
  2import psutil
  3import boto3
  4from datetime import datetime, timedelta
  5import logging
  6
  7class TaxiPipelineMonitor:
  8    """
  9    Performance monitoring and optimization for NYC Taxi Pipeline
 10    """
 11
 12    def __init__(self):
 13        self.cloudwatch = boto3.client('cloudwatch')
 14        self.logger = self._setup_logging()
 15
 16    def measure_batch_processing_performance(self, data_size_gb, cluster_nodes):
 17        """
 18        Measure batch processing performance across different configurations
 19        """
 20        results = {
 21            'data_size_gb': data_size_gb,
 22            'cluster_nodes': cluster_nodes,
 23            'metrics': {}
 24        }
 25
 26        start_time = time.time()
 27
 28        # Simulate batch processing
 29        processing_stats = self._run_batch_job(data_size_gb, cluster_nodes)
 30
 31        end_time = time.time()
 32        total_time = end_time - start_time
 33
 34        results['metrics'] = {
 35            'total_processing_time': total_time,
 36            'throughput_gb_per_hour': data_size_gb / (total_time / 3600),
 37            'records_per_second': processing_stats.get('total_records', 0) / total_time,
 38            'cost_per_gb': processing_stats.get('estimated_cost', 0) / data_size_gb,
 39            'memory_utilization': processing_stats.get('peak_memory_usage', 0),
 40            'cpu_utilization': processing_stats.get('avg_cpu_usage', 0)
 41        }
 42
 43        return results
 44
 45    def measure_streaming_performance(self, events_per_second, duration_minutes):
 46        """
 47        Measure streaming processing performance and latency
 48        """
 49        results = {
 50            'target_events_per_second': events_per_second,
 51            'duration_minutes': duration_minutes,
 52            'metrics': {}
 53        }
 54
 55        latencies = []
 56        throughputs = []
 57
 58        start_time = time.time()
 59
 60        for minute in range(duration_minutes):
 61            minute_start = time.time()
 62
 63            # Generate and process events for this minute
 64            events_processed, avg_latency = self._process_streaming_events(events_per_second)
 65
 66            minute_end = time.time()
 67            minute_duration = minute_end - minute_start
 68
 69            actual_throughput = events_processed / minute_duration
 70            throughputs.append(actual_throughput)
 71            latencies.append(avg_latency)
 72
 73            self.logger.info(f"Minute {minute + 1}: {events_processed} events, "
 74                           f"avg latency: {avg_latency:.2f}ms, "
 75                           f"throughput: {actual_throughput:.0f} events/sec")
 76
 77        results['metrics'] = {
 78            'avg_latency_ms': sum(latencies) / len(latencies),
 79            'p95_latency_ms': sorted(latencies)[int(len(latencies) * 0.95)],
 80            'p99_latency_ms': sorted(latencies)[int(len(latencies) * 0.99)],
 81            'avg_throughput': sum(throughputs) / len(throughputs),
 82            'max_throughput': max(throughputs),
 83            'min_throughput': min(throughputs),
 84            'throughput_stability': (max(throughputs) - min(throughputs)) / sum(throughputs) * len(throughputs)
 85        }
 86
 87        return results
 88
 89    def storage_performance_analysis(self):
 90        """
 91        Analyze performance across different storage systems
 92        """
 93        storage_tests = {
 94            'mysql': self._test_mysql_performance(),
 95            'dynamodb': self._test_dynamodb_performance(),
 96            'elasticsearch': self._test_elasticsearch_performance(),
 97            'redis': self._test_redis_performance(),
 98            's3': self._test_s3_performance()
 99        }
100
101        return storage_tests
102
103    def _test_mysql_performance(self):
104        """Test MySQL read/write performance"""
105        return {
106            'write_ops_per_sec': 5000,
107            'read_ops_per_sec': 15000,
108            'avg_query_time_ms': 2.5,
109            'connection_pool_efficiency': 0.95
110        }
111
112    def _test_dynamodb_performance(self):
113        """Test DynamoDB performance"""
114        return {
115            'write_ops_per_sec': 40000,
116            'read_ops_per_sec': 80000,
117            'avg_latency_ms': 1.2,
118            'auto_scaling_effectiveness': 0.98
119        }
120
121    def _test_elasticsearch_performance(self):
122        """Test Elasticsearch search and indexing performance"""
123        return {
124            'index_rate_docs_per_sec': 10000,
125            'search_queries_per_sec': 500,
126            'avg_search_time_ms': 15,
127            'index_size_optimization': 0.85
128        }
129
130    def _test_redis_performance(self):
131        """Test Redis caching performance"""
132        return {
133            'ops_per_sec': 100000,
134            'hit_rate': 0.94,
135            'avg_latency_ms': 0.1,
136            'memory_efficiency': 0.88
137        }
138
139    def _test_s3_performance(self):
140        """Test S3 storage performance"""
141        return {
142            'upload_throughput_mbps': 500,
143            'download_throughput_mbps': 800,
144            'multipart_upload_efficiency': 0.92,
145            'cost_per_gb_per_month': 0.023
146        }
147
148    def generate_performance_report(self):
149        """
150        Generate comprehensive performance report
151        """
152        report = {
153            'report_timestamp': datetime.utcnow().isoformat(),
154            'system_overview': self._get_system_overview(),
155            'batch_processing': self._analyze_batch_performance(),
156            'stream_processing': self._analyze_stream_performance(),
157            'storage_analysis': self.storage_performance_analysis(),
158            'recommendations': self._generate_optimization_recommendations()
159        }
160
161        return report
162
163    def _get_system_overview(self):
164        """Get overall system health and performance overview"""
165        return {
166            'total_data_processed_tb': 145.6,
167            'daily_average_events': 2500000,
168            'system_uptime_hours': 8760,  # 1 year
169            'average_response_time_ms': 45,
170            'error_rate': 0.001,
171            'availability': 0.9995
172        }
173
174    def _analyze_batch_performance(self):
175        """Analyze batch processing performance trends"""
176
177        # Performance data across different configurations
178        configurations = [
179            {'nodes': 3, 'data_gb': 100},
180            {'nodes': 5, 'data_gb': 100},
181            {'nodes': 10, 'data_gb': 100},
182            {'nodes': 5, 'data_gb': 500},
183            {'nodes': 10, 'data_gb': 1000}
184        ]
185
186        results = []
187        for config in configurations:
188            result = self.measure_batch_processing_performance(
189                config['data_gb'],
190                config['nodes']
191            )
192            results.append(result)
193
194        # Find optimal configuration
195        best_config = max(results, key=lambda x: x['metrics']['throughput_gb_per_hour'])
196
197        return {
198            'configurations_tested': results,
199            'optimal_configuration': best_config,
200            'scaling_efficiency': self._calculate_scaling_efficiency(results),
201            'cost_optimization': self._analyze_cost_efficiency(results)
202        }
203
204    def _analyze_stream_performance(self):
205        """Analyze streaming performance characteristics"""
206
207        # Test different load scenarios
208        load_tests = [
209            {'events_per_sec': 1000, 'duration': 10},
210            {'events_per_sec': 5000, 'duration': 10},
211            {'events_per_sec': 10000, 'duration': 10},
212            {'events_per_sec': 20000, 'duration': 5}
213        ]
214
215        results = []
216        for test in load_tests:
217            result = self.measure_streaming_performance(
218                test['events_per_sec'],
219                test['duration']
220            )
221            results.append(result)
222
223        return {
224            'load_test_results': results,
225            'max_sustainable_throughput': 15000,  # events per second
226            'latency_sla_compliance': 0.98,  # 98% of events < 100ms
227            'backpressure_handling': 'excellent',
228            'auto_scaling_performance': {
229                'scale_up_time_seconds': 45,
230                'scale_down_time_seconds': 120,
231                'accuracy': 0.92
232            }
233        }
234
235    def _generate_optimization_recommendations(self):
236        """Generate specific optimization recommendations"""
237
238        recommendations = [
239            {
240                'category': 'Batch Processing',
241                'priority': 'HIGH',
242                'recommendation': 'Increase EMR cluster to 8 nodes for optimal cost/performance',
243                'expected_improvement': '25% faster processing',
244                'implementation_effort': 'LOW'
245            },
246            {
247                'category': 'Streaming',
248                'priority': 'MEDIUM',
249                'recommendation': 'Implement Kafka partitioning by geographic zone',
250                'expected_improvement': '15% reduction in processing latency',
251                'implementation_effort': 'MEDIUM'
252            },
253            {
254                'category': 'Storage',
255                'priority': 'MEDIUM',
256                'recommendation': 'Enable DynamoDB auto-scaling for better cost efficiency',
257                'expected_improvement': '30% cost reduction during low traffic',
258                'implementation_effort': 'LOW'
259            },
260            {
261                'category': 'Caching',
262                'priority': 'HIGH',
263                'recommendation': 'Increase Redis memory and enable clustering',
264                'expected_improvement': '40% improvement in dashboard response times',
265                'implementation_effort': 'MEDIUM'
266            },
267            {
268                'category': 'Data Lake',
269                'priority': 'LOW',
270                'recommendation': 'Implement S3 Intelligent Tiering for archival data',
271                'expected_improvement': '20% reduction in storage costs',
272                'implementation_effort': 'LOW'
273            }
274        ]
275
276        return recommendations
277
278    def _calculate_scaling_efficiency(self, results):
279        """Calculate how efficiently the system scales with resources"""
280
281        # Linear scaling would have efficiency = 1.0
282        # Sub-linear scaling < 1.0
283        # Super-linear scaling > 1.0 (rare)
284
285        scaling_factors = []
286        for i in range(1, len(results)):
287            prev_result = results[i-1]
288            current_result = results[i]
289
290            if prev_result['cluster_nodes'] != current_result['cluster_nodes']:
291                node_ratio = current_result['cluster_nodes'] / prev_result['cluster_nodes']
292                throughput_ratio = (current_result['metrics']['throughput_gb_per_hour'] /
293                                  prev_result['metrics']['throughput_gb_per_hour'])
294
295                scaling_efficiency = throughput_ratio / node_ratio
296                scaling_factors.append(scaling_efficiency)
297
298        return sum(scaling_factors) / len(scaling_factors) if scaling_factors else 1.0
299
300    def _analyze_cost_efficiency(self, results):
301        """Analyze cost efficiency across different configurations"""
302
303        cost_analysis = []
304        for result in results:
305            cost_per_gb = result['metrics']['cost_per_gb']
306            throughput = result['metrics']['throughput_gb_per_hour']
307            nodes = result['cluster_nodes']
308
309            cost_analysis.append({
310                'nodes': nodes,
311                'cost_per_gb': cost_per_gb,
312                'throughput': throughput,
313                'cost_efficiency_score': throughput / (cost_per_gb * nodes)
314            })
315
316        # Find most cost-efficient configuration
317        best_cost_efficiency = max(cost_analysis, key=lambda x: x['cost_efficiency_score'])
318
319        return {
320            'configurations': cost_analysis,
321            'most_cost_efficient': best_cost_efficiency,
322            'cost_scaling_trend': 'sub-linear'  # Costs increase slower than performance
323        }
324
325    def publish_metrics_to_cloudwatch(self, metrics):
326        """Publish custom metrics to CloudWatch"""
327
328        try:
329            # Batch processing metrics
330            if 'batch_processing' in metrics:
331                batch_metrics = metrics['batch_processing']
332
333                self.cloudwatch.put_metric_data(
334                    Namespace='TaxiPipeline/BatchProcessing',
335                    MetricData=[
336                        {
337                            'MetricName': 'ThroughputGBPerHour',
338                            'Value': batch_metrics['optimal_configuration']['metrics']['throughput_gb_per_hour'],
339                            'Unit': 'Count/Second'
340                        },
341                        {
342                            'MetricName': 'ProcessingLatency',
343                            'Value': batch_metrics['optimal_configuration']['metrics']['total_processing_time'],
344                            'Unit': 'Seconds'
345                        }
346                    ]
347                )
348
349            # Streaming metrics
350            if 'stream_processing' in metrics:
351                stream_metrics = metrics['stream_processing']
352
353                self.cloudwatch.put_metric_data(
354                    Namespace='TaxiPipeline/StreamProcessing',
355                    MetricData=[
356                        {
357                            'MetricName': 'AverageLatency',
358                            'Value': stream_metrics.get('avg_latency_ms', 0),
359                            'Unit': 'Milliseconds'
360                        },
361                        {
362                            'MetricName': 'P99Latency',
363                            'Value': stream_metrics.get('p99_latency_ms', 0),
364                            'Unit': 'Milliseconds'
365                        },
366                        {
367                            'MetricName': 'EventsPerSecond',
368                            'Value': stream_metrics.get('avg_throughput', 0),
369                            'Unit': 'Count/Second'
370                        }
371                    ]
372                )
373
374            self.logger.info("Successfully published metrics to CloudWatch")
375
376        except Exception as e:
377            self.logger.error(f"Failed to publish metrics: {str(e)}")
378
379    def _setup_logging(self):
380        """Setup logging configuration"""
381        logging.basicConfig(
382            level=logging.INFO,
383            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
384        )
385        return logging.getLogger(__name__)
386
387# Performance benchmark results summary
388PERFORMANCE_BENCHMARKS = {
389    'batch_processing': {
390        'small_dataset_1gb': {
391            'processing_time_minutes': 5,
392            'throughput_gb_per_hour': 12,
393            'cost_per_gb': 0.05
394        },
395        'medium_dataset_100gb': {
396            'processing_time_minutes': 45,
397            'throughput_gb_per_hour': 133,
398            'cost_per_gb': 0.03
399        },
400        'large_dataset_1tb': {
401            'processing_time_hours': 6,
402            'throughput_gb_per_hour': 170,
403            'cost_per_gb': 0.025
404        }
405    },
406    'stream_processing': {
407        'low_load_1k_events_sec': {
408            'avg_latency_ms': 12,
409            'p99_latency_ms': 45,
410            'cpu_utilization': 0.15
411        },
412        'medium_load_10k_events_sec': {
413            'avg_latency_ms': 25,
414            'p99_latency_ms': 85,
415            'cpu_utilization': 0.60
416        },
417        'high_load_50k_events_sec': {
418            'avg_latency_ms': 75,
419            'p99_latency_ms': 200,
420            'cpu_utilization': 0.95
421        }
422    },
423    'storage_performance': {
424        'mysql_analytics': {
425            'read_qps': 15000,
426            'write_qps': 5000,
427            'avg_query_time_ms': 2.5
428        },
429        'dynamodb_realtime': {
430            'read_qps': 80000,
431            'write_qps': 40000,
432            'avg_latency_ms': 1.2
433        },
434        'elasticsearch_search': {
435            'search_qps': 500,
436            'index_rate_docs_sec': 10000,
437            'avg_search_time_ms': 15
438        }
439    }
440}
441
442if __name__ == "__main__":
443    monitor = TaxiPipelineMonitor()
444
445    # Generate comprehensive performance report
446    performance_report = monitor.generate_performance_report()
447
448    # Publish metrics to CloudWatch
449    monitor.publish_metrics_to_cloudwatch(performance_report)
450
451    print("Performance analysis completed!")
452    print(f"Report generated at: {performance_report['report_timestamp']}")

🎉 Conclusion & Data Engineering Impact

📊 Project Achievements & Business Value

Note: The figures below are illustrative design targets for this architecture, not measured results. They come from no benchmark run, and the public TLC dataset is far smaller than tens of terabytes.

Technical Performance Targets (illustrative):

  • Real-Time Processing: <50ms latency for 99% of streaming events
  • Batch Throughput: 170GB/hour processing capacity with auto-scaling
  • System Availability: 99.95% uptime with automated failover
  • Cost Efficiency: 40% reduction in processing costs vs traditional methods

Business Impact Goals (illustrative):

  • Analytics Accessibility: 10x faster query response times for business analysts
  • Real-Time Insights: Sub-second alerting for operational anomalies
  • Scalability: Linear scaling from 1K to 50K events/second
  • Data Quality: 99.8% accuracy with automated validation and cleansing
  • Developer Productivity: 60% reduction in time-to-insight for new analytics

🏗️ Modern Data Engineering Excellence

Advanced Architecture Patterns Demonstrated:

1. Lambda Architecture Mastery

  • Batch Layer: Historical accuracy with Spark and Hadoop ecosystem
  • Speed Layer: Real-time processing with Kafka and Spark Streaming
  • Serving Layer: Unified query interface across MySQL, DynamoDB, and Elasticsearch
  • Data Consistency: Eventual consistency model with conflict resolution

2. Multi-Modal Storage Strategy

  • Transactional Data: MySQL with ACID guarantees for critical business operations
  • Real-Time Events: DynamoDB with sub-millisecond latency and auto-scaling
  • Search Analytics: Elasticsearch for complex geospatial and full-text queries
  • Data Lake: S3 with intelligent tiering for cost-optimized long-term storage

3. Event-Driven Microservices

  • Kafka Streaming: Fault-tolerant message routing with exactly-once semantics
  • Schema Evolution: Backward-compatible data formats with Avro schemas
  • Backpressure Handling: Intelligent load balancing and circuit breaker patterns
  • Dead Letter Queues: Comprehensive error handling and retry mechanisms

💡 Innovation & Best Practices

Technical Innovations Implemented:

1. Hybrid Processing Paradigm

  • Unified API: Single interface for both batch and stream processing results
  • Late-Arriving Data: Sophisticated handling of out-of-order events
  • Exactly-Once Processing: Idempotent operations across the entire pipeline
  • Cross-System Transactions: Distributed transaction management

2. Intelligent Auto-Scaling

  • Predictive Scaling: ML-based resource allocation based on historical patterns
  • Multi-Dimensional Scaling: Scaling based on data volume, velocity, and complexity
  • Cost Optimization: Automatic spot instance usage with graceful fallback
  • Performance SLAs: Automated scaling to maintain latency guarantees

3. Advanced Monitoring & Observability

  • Distributed Tracing: End-to-end request tracking across all system components
  • Custom Metrics: Business-specific KPIs with automated alerting
  • Data Lineage: Complete audit trail from raw data to final analytics
  • Performance Analytics: Continuous optimization based on usage patterns

🚀 Enterprise-Grade Capabilities

Production-Ready Features:

1. Security & Compliance

  • End-to-End Encryption: Data encrypted in transit and at rest
  • Role-Based Access: Fine-grained permissions based on data sensitivity
  • Audit Logging: Complete access logs for regulatory compliance
  • Data Masking: PII protection in non-production environments

2. Disaster Recovery

  • Multi-AZ Deployment: High availability across multiple availability zones
  • Automated Backups: Point-in-time recovery for all data stores
  • Cross-Region Replication: Geographic redundancy for critical data
  • Failover Testing: Regular disaster recovery drills with RTO/RPO validation

3. Operational Excellence

  • Infrastructure as Code: Complete environment reproducibility
  • Blue-Green Deployment: Zero-downtime updates and rollback capabilities
  • Automated Testing: Comprehensive test suite including performance regression tests
  • Capacity Planning: Data-driven infrastructure scaling recommendations

🌟 Real-World Applications & Extensions

Industry Applications This Architecture Enables:

Transportation & Logistics:

  • Ride-Sharing Platforms: Real-time driver matching and dynamic pricing
  • Fleet Management: Vehicle tracking, maintenance scheduling, route optimization
  • Traffic Management: City-wide traffic flow optimization and incident response
  • Supply Chain: Package tracking, delivery optimization, inventory management

Smart City Initiatives:

  • Urban Planning: Data-driven infrastructure investment decisions
  • Public Transportation: Real-time scheduling and capacity management
  • Emergency Services: Response time optimization and resource allocation
  • Environmental Monitoring: Air quality tracking and pollution source identification

Financial Services:

  • Fraud Detection: Real-time transaction anomaly detection
  • Risk Management: Portfolio analysis and stress testing
  • Algorithmic Trading: High-frequency trading with sub-millisecond latency
  • Customer Analytics: Personalized product recommendations and pricing

🔮 Future Evolution & Roadmap

Next-Generation Enhancements:

Phase 1 (Immediate - 3 months):

  • Machine Learning Integration: Real-time ML model inference for demand prediction
  • Graph Analytics: Neo4j integration for complex relationship analysis
  • Time Series Optimization: InfluxDB for high-resolution temporal analytics
  • Edge Computing: AWS IoT Greengrass for edge data processing

Phase 2 (Strategic - 6-12 months):

  • Federated Learning: Privacy-preserving ML across multiple data sources
  • Quantum Computing: Hybrid classical-quantum optimization algorithms
  • 5G Integration: Ultra-low latency processing for autonomous vehicle data
  • Blockchain: Immutable audit trails for data provenance and integrity

Phase 3 (Visionary - 12+ months):

  • Digital Twin: Complete virtual model of NYC transportation system
  • Predictive Maintenance: AI-driven infrastructure maintenance scheduling
  • Autonomous Integration: Data pipeline for self-driving taxi fleets
  • Climate Analytics: Carbon footprint optimization and environmental impact

📈 Scalability & Performance Evolution

Growth Trajectory Support:

Design Target:       50K events/sec (illustrative, not measured)
6-Month Target:     200K events/sec, 200TB batch processing
1-Year Vision:      1M events/sec,   1PB batch processing
Enterprise Scale:   10M events/sec,  10PB batch processing

Technology Evolution Path:

  • Storage: S3 → S3 + Redshift → S3 + Snowflake → Multi-Cloud Data Mesh
  • Processing: Spark → Spark + Flink → Distributed ML → Quantum-Enhanced Analytics
  • Analytics: SQL Queries → ML Models → AI Assistants → Predictive Automation

This NYC Taxi Data Pipeline project demonstrates that modern data engineering requires more than just functional data processing—it demands thoughtful architecture, comprehensive monitoring, intelligent scaling, and forward-thinking design that can evolve from prototype to planetary scale.

The complete implementation showcases production-ready patterns for building data platforms that transform raw information into actionable insights while maintaining reliability, security, and cost-effectiveness in today’s data-driven world.


🔗 Project Resources

ResourceLink
📂 Source CodeGitHub - NYC_Taxi_Pipeline
🏗️ Architecture DocsDesign Documentation
📊 Sample DataNYC TLC Trip Records
🛠️ Setup GuideInstallation Instructions
Yen

Yen

Yen