Production-grade guide to streaming pipeline patterns covering architecture patterns, implementation strategies, testing approaches, and operational best practices for enterprise engineering teams.
This reference describes specific architectural approaches for building data pipelines that process continuous streams of events rather than discrete batches. These patterns apply when you need to handle real-time data flows where latency requirements exceed batch processing intervals, or when systems must react to events as they occur.
When a single event source must feed multiple downstream consumers, use a fan-out approach with message queuing. The event producer sends each message to a topic or queue that multiple consumers can subscribe to independently.
# Example using Kafka command line tools
kafka-topics.sh --create --topic user-events \
--bootstrap-server kafka-broker:9092 \
--partitions 6 --replication-factor 3
# Consumer groups process the same topic
kafka-console-consumer.sh --topic user-events \
--bootstrap-server kafka-broker:9092 \
--group analytics-group
kafka-console-consumer.sh --topic user-events \
--bootstrap-server kafka-broker:9092 \
--group reporting-group
The key configuration is the consumer group ID. Messages are distributed among consumers within the same group, but each consumer receives a copy of every message when different groups are used.
When event order matters for processing, use partitioning to ensure related events go to the same processing unit. For example, if processing user actions by user ID, partition by user_id to maintain ordering within partitions.
# Kafka producer with key-based partitioning
kafka-console-producer.sh --topic user-events \
--bootstrap-server kafka-broker:9092 \
--property parse.key=true \
--property key.separator=:
Messages must have keys that are consistent across the stream to preserve order within partitions. If you do not specify a key, messages may be distributed randomly across partitions, breaking order guarantees.
When downstream systems cannot keep up with upstream event rates, implement backpressure by pausing or throttling producers. This can be achieved through consumer group coordination or explicit rate limiting.
# Example using Kafka consumer configuration
kafka-console-consumer.sh --topic user-events \
--bootstrap-server kafka-broker:9092 \
--max-poll-records 100 \
--session-timeout-ms 10000 \
--heartbeat-interval-ms 3000
The max-poll-records parameter limits how many records are fetched per poll cycle. If processing takes longer than the heartbeat interval, the consumer group will rebalance and potentially reprocess messages.
When events fail processing, route them to a separate queue for later inspection or retry. This prevents failed messages from blocking subsequent messages in the same stream.
# Configure Kafka consumer to send failures to DLQ topic
kafka-console-consumer.sh --topic user-events \
--bootstrap-server kafka-broker:9092 \
--property value.deserializer=org.apache.kafka.common.serialization.StringDeserializer \
--property key.deserializer=org.apache.kafka.common.serialization.StringDeserializer \
--offset.reset=earliest \
--max.poll.records=100
Ensure that processing the same event multiple times produces the same result. This is critical for handling network failures or consumer restarts where events may be reprocessed.
# Example idempotent processing function
def process_event(event):
# Use unique event ID to prevent duplicate processing
event_id = event['id']
# Check if already processed
if check_processed(event_id):
return
try:
# Process the event
result = transform_event(event)
save_result(result)
# Mark as processed
mark_processed(event_id)
except Exception as e:
# Log failure but do not mark as processed
log_failure(event_id, str(e))
raise # Re-raise to trigger retry mechanism
When processing fails, implement retries with increasing delays between attempts.
# Consumer configuration for retry behavior
kafka-console-consumer.sh --topic user-events \
--bootstrap-server kafka-broker:9092 \
--retries=3 \
--retry.backoff.ms=1000 \
--max.poll.interval.ms=300000
The max.poll.interval.ms controls how long a consumer can take to process messages before being considered dead and triggering a rebalance. If processing takes longer than this interval, the consumer group will rebalance, potentially causing duplicate processing.
Use transactional producers and consumer groups to ensure each message is processed exactly once, even if failures occur during processing.
# Kafka transactional producer example
kafka-console-producer.sh --topic user-events \
--bootstrap-server kafka-broker:9092 \
--transactional.id=processing-1 \
--enable.idempotence=true \
--max.in.flight.requests.per.connection=5
The enable.idempotence=true flag ensures that producers can handle duplicate messages without creating duplicates in the destination. The transactional.id enables transactional semantics.
Aggregate events over time windows to build summaries or perform calculations that span multiple events.
-- Example SQL for windowed aggregation
SELECT
window_start,
window_end,
user_id,
count(*) as event_count,
avg(event_value) as average_value
FROM (
SELECT
user_id,
event_value,
tumble(event_time, INTERVAL '10' MINUTE) as window
FROM events_stream
)
GROUP BY window_start, window_end, user_id
The tumble function creates fixed-time windows. If a window spans across midnight, you must handle the boundary correctly to avoid splitting events.
When processing multiple sources, implement rate limiting to prevent overwhelming downstream systems.
# Kafka consumer with throttling
kafka-console-consumer.sh --topic user-events \
--bootstrap-server kafka-broker:9092 \
--max.poll.records=10 \
--poll.interval.ms=1000
The poll.interval.ms parameter controls how frequently the consumer polls for new messages. Setting this too low can cause excessive polling overhead, while setting it too high can delay processing.
Only process events that meet specific criteria to reduce unnecessary work.
# Example conditional processing logic
def should_process_event(event):
# Filter based on event type or content
if event['type'] not in ['click', 'purchase']:
return False
# Filter based on business rules
if event['user_id'] in ['internal_user_1', 'internal_user_2']:
return False
return True
def process_event(event):
if not should_process_event(event):
return
# Process only qualifying events
...
Monitor how far behind the current time events are to detect processing delays.
# Kafka consumer lag monitoring
kafka-consumer-groups.sh --bootstrap-server kafka-broker:9092 \
--describe --group analytics-group
The output includes Lag column showing how many messages behind the consumer is. If this value grows over time, it indicates processing is not keeping up.
Track how old events are when they reach processing to detect data freshness issues.
# Example age monitoring in processing function
def process_event(event):
event_age = datetime.now() - event['timestamp']
# Log if event is older than 5 minutes
if event_age > timedelta(minutes=5):
log_warning(f"Event is {event_age} old")
# Process normally
...
When consumers join or leave a group, rebalancing can cause processing gaps or duplicates.
The session.timeout.ms controls how long the consumer can go without heartbeats before being considered dead. If this timeout is too short relative to processing time, consumers will be removed from the group unnecessarily.
When partitions are reassigned, consumers may lose track of their current position.
Always use consistent partitioning keys across all consumers in a group to avoid message duplication or loss during rebalancing.
Misconfigured serialization can cause entire streams to stop processing.
If a consumer cannot deserialize messages from a topic, it will stop consuming and potentially fail to advance offsets. Always configure appropriate deserializers for each topic.
Consumers that run for extended periods may accumulate memory if not properly managing state or resources.
Monitor consumer memory usage and ensure proper cleanup of temporary objects between processing cycles.
If consumers fail to commit offsets correctly, they may reprocess the same messages repeatedly.
The enable.auto.commit setting controls whether offsets are automatically committed. If set to false, you must manually commit offsets or risk message loss during failures.
Setting max.poll.records too high relative to processing time can cause consumers to timeout and trigger rebalances.
Always measure actual processing time for your events and adjust batch sizes accordingly.
This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy