Distributed Computing for Health Data - 10 - Kafka Basic Usage
These are my personal notes related to the course Distributed Computing for Health Data provided by Health Data Research UK. I use this space to consolidate concepts and revisit material over time.
Lesson 10 - Kafka Basic Usage
In the first half of the lesson, they follow Kafkas’s quickstart tutorial so I won’t be copying and pasting it here. Instead, since I had the need to simplify core concepts and build a mental model of how Kafka actually works I slightly deviated from the the first half of the lesson.
Thinking in Events vs State
For a long time, most software systems have been built around databases. Databases encourage us to think of the world in terms of things:
- users
- devices
- patients
- trains
These things have a state, and we store that state in tables. This model worked for many years but more and more people are now thinking of the world in terms of events.
From State to Events
An event represents something that happened at a specific point in time:
- a user logged in
- a sensor sent a reading
- a patient’s heart rate changed
- a train arrived at a station
Events also have state, but a different kind of state: what happened, and when it happened.So instead of storing only the latest state of a thing, we store the full history of what happened to it.
Logs as the Core Abstraction
Logs are timestamped records that document events, actions, and messages occurring within an application, operating system, or server.
Storing large volumes of immutable events in traditional databases is not practical. Event-driven systems therefore rely on a simpler abstraction: logs. Kafka is a system for managing logs.
So again, a log is:
- an ordered, append-only sequence of events
- When an event happens, we append it to the log. Never updated or deleted
Also:
- They are simple to reason about
- They are easy to replicate
- Easy to escalate horizontaly
Apache Kafka is essentialy a distributed system for managing logs.
In Kafka, logs are called topics. So topics are (again):
- an ordered collection of events
- stored durably on disk
- replicated across multiple machines
This means Kafka is resilient to hardware failures. Data does not disappear if a single machine goes down. Topics can:
- store data for minutes, hours, days, or years
- be small or extremely large
- be replayed at any point in time
- Each event in a topic represents something that happened in the business
Kafka encourages us to think in events first, things second.
From Monoliths to Microservices and Event-Driven Systems
When databases dominated system design, most architectures were monoliths:
- one large application
- one central database
- tightly coupled components
As systems grew, these architectures became difficult to maintain, reason about, and evolve. Kafka fits naturally with microservices architectures.
In an event-driven system:
- services consume events from topics
- perform some computation
- publish new events to other topics
for example:
Topic A → Service 1 → Topic B → Service 2 → Topic C
Each service is independent, loosely coupled, and communicates only through events.
Kafka as an Integration Layer
Kafka is not just a runtime system. It is also an integration platform.
Kafka Connect
Kafka Connect allows external systems to connect to Kafka:
- databases
- cloud storage
- APIs
- SaaS platforms
It provides a large ecosystem of connectors:
- some open source
- some commercial
This makes Kafka a central hub for data in motion.
Stream Processing with Kafka
Kafka-based services typically:
- read from topics
- transform data
- write to new topics
Common operations include:
- filter()
- groupBy()
- count()
- aggregate()
- enrichment() which is the equivalent to join() in databases
Kafka Streams is a Java API that supports this model directly, allowing developers to build stream-processing applications embedded inside services.
Why Kafka Matters
In addition to being a messaging system, Kafka represents a deeper architectural shift:
- from modelling systems around current state
- to modelling systems around immutable event histories
This shift enables:
- real-time analytics
- system replay and debugging
- better fault tolerance
- loosely coupled architectures
- scalable data pipelines
In many modern systems, Kafka often becomes the backbone that connects storage, computation, and real-time services into a single event-driven platform.
Kafka Producer()
Producers make the connection between data source and Kafka. We can build a producer for any data source (for example a real-time API). Its job is to:
- Set up connections, instantiate KafkaProducer()
- Load data
- Transform data (convert to message)
- Encode and send to Kafka topic
- Clean up and close
# needs pip install kafka-python
from kafka import KafkaProducer
import csv
import time
##### ---------- Set Up and Conection-------- #####
KAFKA_TOPIC = "patient-monitoring"
KAFKA_BROKER = "localhost:9092" # change if kafka is somewhere else
# Create Kafka producer
producer = KafkaProducer(bootstrap_servers = KAFKA_BROKER)
##### ---------- Data Transformation and Sending -------- #####
# Read and send csv data
with open("patient_data.csv", "r") as file:
reader = csv.reader(file)
header = next(reader) # skip header if needed
for row in reader:
# Convert row to a comma-separated string
message = ",".join(row)
producer.send(KAFKA_TOPIC, value = message.encode("utf-8"))
print(f"Sent: {message}")
print(time.sleep(1))
##### ---------- Clean Up -------- #####
producer.flush() # Ensure all messages are sent
producer.close()
Kafka Consumer()
Consumers connect Kafka and downstream applications. Its job is to:
- Set up connections and subscribe to one or more topics
- Join a consumer group
- Read messages from Kafka (in order)
- Decode and process messages
- Track progress using offsets
from kafka import KafkaConsumer
KAFKA_TOPIC = "patient-monitoring"
KAFKA_BROKER = "localhost:9092"
# create kafka consumer
consumer = KafkaConsumer(
KAFKA_TOPIC, # topic to subscribe to
bootsrap_servers = KAFKA_BROKER,
auto_offset_reset = "earliest",
anable_auto_commit = True, # consumer automatically commits its offset after reading messages
grou_id = "csv-group", # consumer group that the consumer belongs to
)
print("Listening for messages... ")
for message in consumer:
print(f"Received: {message.value.decode("uft-8")}")
Consumer Groups
Consumers usually run in groups. Kafka ensures that:
- each message is processed by only one consumer in the group
- load is automatically balanced across consumers
This is how Kafka scales horizontally.
Architecturally, this separation between Producers() and Consumers() is critical and very powerful. In fact, is one of the core reasons why Kafka fits so well with event-driven and microservices architectures:
- producers don’t know who consumes the data
- consumers don’t care where the data cames from
- new services can be added without touching existing ones