A MLOps pipeline that transforms e-commerce behavior data into real-time purchase predictions. Built on modern open-source technologies including Kafka, Flink, Spark, Ray, and MLflow, this project demonstrates a complete ML lifecycle from data ingestion through model deployment. The system features automated CDC, multi-layer data warehousing, real-time feature serving, and comprehensive observability.

eCommerce Behavior Data from Multi Category Store
The dataset can be found here. This dataset contains behavior data from over 285 million user events on a large multi-category eCommerce website.
The data spans 7 months (October 2019 to April 2020) and captures user-product interactions like views, cart additions/removals, and purchases. Each event represents a many-to-many relationship between users and products.
The dataset was collected by the Open CDP project, an open source customer data platform that enables tracking and analysis of user behavior data.
| Field | Description |
|---|---|
| event_time | UTC timestamp when the event occurred |
| event_type | Type of user interaction event |
| product_id | Unique identifier for the product |
| category_id | Product category identifier |
| category_code | Product category taxonomy (when available for meaningful categories) |
| brand | Brand name (lowercase, may be missing) |
| price | Product price (float) |
| user_id | Permanent user identifier |
| user_session | Temporary session ID that changes after long user inactivity |
The dataset captures four types of user interactions:
The core modeling task is to predict whether a user will purchase a product at the moment they add it to their shopping cart.
We transform the raw event data into meaningful features for our machine learning model. The analysis focuses specifically on cart addition events and their subsequent outcomes.
Key engineered features include:
| Feature | Description |
|---|---|
| category_code_level1 | Main product category |
| category_code_level2 | Product sub-category |
| event_weekday | Day of week when cart addition occurred |
| activity_count | Total user activities in the current session |
| price | Original product price |
| brand | Product brand name |
| is_purchased | Target variable: whether cart item was eventually purchased |
You can download the dataset and put it under the data folder.
The system comprises four main components—Data, Training, Serving, and Observability—alongside a Dev Environment and a Model Registry.
tracking.raw_user_behavior topictracking_postgres_cdc.public.eventstracking.user_behavior.validated for valid eventstracking.user_behavior.invalid for schema violationslocalhost:5001localhost:3301localhost:8265localhost:3009localhost:8089The architecture prioritizes reliability, scalability, and observability while maintaining clear separation of concerns between pipeline stages. Each component is containerized and can be deployed independently using Docker Compose.
All available commands can be found in the Makefile.
In this section, we will dive into the details of the system.
Please run the following command to setup the .env files:
cp .env.example .env
cp ./src/cdc/.env.example ./src/cdc/.env
cp ./src/model_registry/.env.example ./src/model_registry/.env
cp ./src/orchestration/.env.example ./src/orchestration/.env
cp ./src/producer/.env.example ./src/producer/.env
cp ./src/streaming/.env.example ./src/streaming/.env
Note: I don't use any secrets in this project, so run the above command and you are good to go.
I will use the same network for all the services, first we need to create the network.
make up-network
make up-kafka
The last service in the docker-compose.kafka.yaml file is kafka_producer, this service acts as a producer and will start sending messages to the tracking.raw_user_behavior topic.
To check if Kafka is running, you can go to localhost:9021 and you should see the Kafka dashboard. Then go to the Topics tab and you should see tracking.raw_user_behavior topic.

To check if the producer is sending messages, you can click on the tracking.raw_user_behavior topic and you should see the messages being sent.

Here is an example of the message's value in the tracking.raw_user_behavior topic:
{
"schema": {
"type": "struct",
"fields": [
{
"name": "event_time",
"type": "string"
},
{
"name": "event_type",
"type": "string"
},
{
"name": "product_id",
"type": "long"
},
{
"name": "category_id",
"type": "long"
},
{
"name": "category_code",
"type": ["null", "string"],
"default": null
},
{
"name": "brand",
"type": ["null", "string"],
"default": null
},
{
"name": "price",
"type": "double"
},
{
"name": "user_id",
"type": "long"
},
{
"name": "user_session",
"type": "string"
}
]
},
"payload": {
"event_time": "2019-10-01 02:30:12 UTC",
"event_type": "view",
"product_id": 1306133,
"category_id": "2053013558920217191",
"category_code": "computers.notebook",
"brand": "xiaomi",
"price": 1029.37,
"user_id": 512900744,
"user_session": "76b918d5-b344-41fc-8632-baf222ec760f"
}
}
make up-cdc
Next, we start the CDC (Change Data Capture) service using Docker Compose. This setup includes the following components:
Steps involved:
The data is automatically synced from PostgreSQL to the tracking_postgres_cdc.public.events topic. To confirm this, go to the Connect tab in the Kafka UI; you should see a connector named cdc-postgresql.

Return to localhost:9021; there should be a new topic called tracking_postgres_cdc.public.events.

make schema_validation
This is a Flink job that will consume the tracking_postgres_cdc.public.events and tracking.raw_user_behavior topics and validate the schema of the events. The validated events will be sent to the tracking.user_behavior.validated topic and the invalid events will be sent to the tracking.user_behavior.invalid topic, respectively. For easier understanding, I don't push these Flink jobs into a Docker Compose file, but you can do it if you want. Watch the terminal to see the job running, the log may look like this:

We can handle 10k RPS, noting that approximately 10% of events are failures. I purposely make the producer send invalid events to the tracking.user_behavior.invalid topic. You can check this at line 127 in src/producer/produce.py.
After starting the job, you can go to localhost:9021 and you should see the tracking.user_behavior.validated and tracking.user_behavior.invalid topics.

Beside that, we can also start the alert_invalid_events job to alert the invalid events.
make alert_invalid_events
Note: This feature of pushing the invalid events to Elasticsearch is not implemented yet, I will implement it in the future, but you can do it easily by modifying the src/streaming/jobs/alert_invalid_events_job.py file.
First, we need to start the Data Warehouse and the Online Store.
make up-dwh
make up-online-store
The Data Warehouse is just a PostgreSQL instance.
The Online Store is a Redis instance.
Look at the `docker-co
$ claude mcp add Customer-Purchase-Prediction-ML-System \
-- python -m otcore.mcp_server <graph>