Kafka to MySQL to a Real-Time Fraud Dashboard
Club the producer, two consumers, two MySQL tables, and Grafana into one live decision system.
Learning Outcomes and Timeline
- Run independent Kafka consumer groups for transaction storage and alert storage.
- Explain why live dashboards use ingestion time while business analysis uses event time.
- Build Grafana panels that visibly update as MySQL rows arrive.
- Translate a live fraud signal into an operational decision.
| Time | Activity | Output |
|---|---|---|
| 0β15 | Reconnect the course architecture | Shared data-flow understanding |
| 15β35 | Start MySQL, consumers, and producer | Two live MySQL tables |
| 35β70 | Build four Grafana panels | Live fraud dashboard |
| 70β85 | Observe alerts and make decisions | 60-second management briefing |
| 85β90 | Stop and reset safely | Repeatable classroom lab |
The Complete Architecture
transactions_16.csv
|
v
transaction_producer_16.py
|
v
transactions-topic-l16 (Kafka)
| |
v v
consumer_store_mysql_16.py consumer_alerts_mysql_16.py
group: mysql-store-l16 group: mysql-alert-l16
| |
v v
transactions table fraud_alerts table
\ /
\ /
+------ Grafana ------+
refresh: 5s
Business design: The transaction table answers βwhat is happening?β The alert table answers βwhat needs attention?β Separate consumer groups let each workflow receive every Kafka message and evolve independently.
Download the Isolated Lecture 16 Files
The Lecture 16 generator stores its last issued number in .transactions_sequence_l16. Repeated batches therefore continue the ID range instead of colliding with rows already stored in sda_course_l16.
For larger or custom datasets, use the course-wide Configurable Transaction Generator.
Lab Part 1 β Start a Clean Live Pipeline
1. Start MySQL and Grafana
docker network create sda-analytics docker run -d \ --name sda-mysql \ --network sda-analytics \ -e MYSQL_ROOT_PASSWORD=admin \ -p 3306:3306 \ mysql:8.4 docker run -d \ --name grafana \ --network sda-analytics \ -p 3000:3000 \ -e GF_SECURITY_ADMIN_PASSWORD=admin \ grafana/grafana:latest
If the containers already exist, use docker start sda-mysql grafana instead of creating them again.
2. Install Python Packages and Generate Data
pip install kafka-python mysql-connector-python python generate_transactions_16.py
3. Reset the Demo Tables
Use a clean run so counts rise from zero and every student can see the dashboard move.
Stop any producer or consumer processes left from an earlier run before resetting the tables.
docker exec -it sda-mysql mysql -uroot -padmin CREATE DATABASE IF NOT EXISTS sda_course_l16; USE sda_course_l16; DROP TABLE IF EXISTS fraud_alerts; DROP TABLE IF EXISTS transactions; CREATE USER IF NOT EXISTS 'grafana'@'%' IDENTIFIED BY 'grafana'; GRANT SELECT ON sda_course_l16.* TO 'grafana'@'%'; FLUSH PRIVILEGES; exit
Classroom-only reset: Dropping tables deletes prior lab data. Never use this reset pattern on a production database.
4. Run Four Terminals
| Terminal | Command | Purpose |
|---|---|---|
| 1 | python consumer_store_mysql_16.py | Create and populate transactions |
| 2 | python consumer_alerts_mysql_16.py | Create and populate fraud_alerts |
| 3 | python transaction_producer_16.py | Publish the live stream |
| 4 | docker exec sda-mysql mysql -uroot -padmin -e "SELECT COUNT(*) FROM sda_course_l16.transactions; SELECT COUNT(*) FROM sda_course_l16.fraud_alerts;" | Optional row-count checkpoint; rerun to see growth |
Start both consumers before the producer. The producer sends approximately three transactions per second at the default delay.
Lab Part 2 β Build the Real-Time Dashboard
Open http://localhost:3000. Add a separate MySQL datasource for this lab: host sda-mysql:3306, database sda_course_l16, user grafana, password grafana. Name it MySQL β Lecture 16 Live.
- Create a dashboard named
Live Fraud Operations β Lecture 16. - Set the time range to Last 5 minutes.
- Set Refresh to
5s. - Build the four panels below.
Panel A β Transactions Received (Stat)
SELECT COUNT(*) AS value FROM transactions WHERE $__timeFilter(ingested_at);
This number should increase on nearly every refresh.
Panel B β Live Throughput (Time Series)
SELECT $__timeGroupAlias(ingested_at, '5s'), COUNT(*) AS value FROM transactions WHERE $__timeFilter(ingested_at) GROUP BY 1 ORDER BY 1;
Business question: Is the transaction stream flowing at the expected rate?
Panel C β Alerts by Type (Bar Chart)
SELECT alert_type, COUNT(*) AS value FROM fraud_alerts WHERE $__timeFilter(detected_at) GROUP BY alert_type ORDER BY value DESC;
Business question: Which risk pattern is creating the current review workload?
Panel D β Live Alert Queue (Table)
SELECT detected_at, severity, alert_type, transaction_id, user_name, amount, merchant, city, message FROM fraud_alerts WHERE $__timeFilter(detected_at) ORDER BY detected_at DESC LIMIT 20;
Use value mappings or cell colours: critical = red, warning = amber.
Time-field rule: timestamp represents when the simulated transaction happened in August 2025. ingested_at and detected_at represent when today's classroom pipeline processed it. Use processing time to demonstrate a live dashboard; use event time for historical business analysis.
Lab Part 3 β Observe, Decide, and Troubleshoot
What Students Should Observe
- The transaction count rises continuously.
- The throughput line forms a stable band while the producer runs.
- The alert queue updates only when a flagged event arrives.
- Stopping the producer flattens throughput without deleting historical rows.
60-Second Management Briefing
- Signal: State current throughput and alert count.
- Pattern: Identify the dominant alert type and severity.
- Decision: Choose which cases analysts should inspect first.
- Caveat: Explain that flagged means βreview,β not confirmed fraud.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| Dashboard stays at zero | Wrong time field or range | Use ingested_at/detected_at and Last 5 minutes |
| No alert rows yet | Producer has not reached a flagged record | Keep it running or reduce DELAY_SECONDS to 0.05 |
| Consumer cannot connect | MySQL still starting | Wait, then check docker logs sda-mysql |
| Grafana datasource fails | Grafana and MySQL are on different networks | Connect both containers to sda-analytics |
| Counts do not restart at zero | Old tables or committed Kafka offsets | Drop tables and use a new group ID for a full replay |
Key Takeaways
- Kafka decouples producers from multiple independent business workflows.
- MySQL turns stream outputs into queryable operational tables.
- Grafana refresh creates near-real-time visibility; it does not consume Kafka directly.
- Event time and processing time answer different management questions.
- A useful dashboard ends with a prioritised action, not merely a chart.