| name | infra-kafka-platform-review |
| description | Kafka production platform review — broker configuration (replication factor/min.insync.replicas/rack awareness/KRaft mode), topic design (partition count formula/compaction/retention), consumer group management (lag monitoring/rebalance tuning/cooperative sticky), producer tuning (acks/idempotence/compression/batching), security (SASL_SSL/ACLs/mTLS), JMX metrics to Prometheus (kafka-exporter/JMX exporter), alert rules (under-replicated partitions/ISR shrink/consumer lag/disk), capacity planning, Strimzi Kubernetes operator |
Kafka Production Platform Review
When to Use
- Reviewing a Kafka cluster before production promotion
- Diagnosing replication lag, consumer group issues, or performance problems
- Designing topic partitioning and retention strategy
- Setting up Kafka monitoring with Prometheus and Grafana
- Running Kafka on Kubernetes with Strimzi
Broker Configuration (Production)
# server.properties — production broker settings
# Cluster identity (KRaft mode — no ZooKeeper for Kafka 3.3+)
process.roles=broker,controller # dev only; separate in prod
node.id=1
controller.quorum.voters=1@kafka-0:9093,2@kafka-1:9093,3@kafka-2:9093
# Replication safety
default.replication.factor=3
min.insync.replicas=2 # producer acks=all requires 2 of 3 ISR
unclean.leader.election.enable=false # never elect out-of-sync replica as leader
auto.create.topics.enable=false # control schema drift
# Rack awareness (requires broker.rack set per broker)
replica.selector.class=org.apache.kafka.common.replica.RackAwareReplicaSelector
# Log retention
log.retention.hours=168 # 7 days default
log.segment.bytes=1073741824 # 1 GB segment rollover
log.cleanup.policy=delete # or compact for changelog topics
log.retention.check.interval.ms=300000
# Compression (broker-level re-compression)
compression.type=producer # preserve producer compression
# Network / threads
num.network.threads=8 # increase on high-throughput brokers
num.io.threads=16
socket.send.buffer.bytes=102400
socket.receive.buffer.bytes=102400
socket.request.max.bytes=104857600 # 100 MB max request
# Replication performance
replica.fetch.max.bytes=1048576
replica.fetch.wait.max.ms=500
# Listeners
listeners=SASL_SSL://:9092,CONTROLLER://:9093
advertised.listeners=SASL_SSL://kafka-0.kafka.svc.cluster.local:9092
# JVM heap (KAFKA_HEAP_OPTS)
# For brokers with 64 GB RAM, OS page cache is more important than heap
KAFKA_HEAP_OPTS="-Xms6g -Xmx6g"
KAFKA_JVM_PERFORMANCE_OPTS="-server -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35"
Topic Design Best Practices
Partition Count Formula
partitions = max(target_throughput_MBps / broker_write_MBps, consumer_parallelism)
# Example: 500 MB/s throughput, 100 MB/s per broker write, 20 consumer instances
partitions = max(500/100, 20) = max(5, 20) = 20 partitions
kafka-topics.sh \
--bootstrap-server kafka:9092 \
--create \
--topic orders \
--partitions 24 \
--replication-factor 3 \
--config min.insync.replicas=2 \
--config retention.ms=604800000 \
--config segment.bytes=536870912 \
--config compression.type=lz4
kafka-topics.sh --bootstrap-server kafka:9092 --describe --topic orders
Topic Naming Convention
{env}.{domain}.{entity}.{version}
prod.orders.created.v1
prod.customers.updated.v2
staging.events.pageview.v1
Compacted Topics (Changelog)
kafka-topics.sh --bootstrap-server kafka:9092 \
--create \
--topic customer-state \
--partitions 12 \
--replication-factor 3 \
--config cleanup.policy=compact \
--config min.cleanable.dirty.ratio=0.1 \
--config segment.ms=3600000 \
--config delete.retention.ms=86400000
Consumer Group Management
Cooperative Sticky Rebalance
from confluent_kafka import Consumer
consumer = Consumer({
'bootstrap.servers': 'kafka:9092',
'group.id': 'orders-processor',
'auto.offset.reset': 'earliest',
'enable.auto.commit': False,
'partition.assignment.strategy': 'cooperative-sticky',
'session.timeout.ms': 60000,
'heartbeat.interval.ms': 10000,
'max.poll.interval.ms': 300000,
'fetch.min.bytes': 65536,
'fetch.max.wait.ms': 500,
'max.partition.fetch.bytes': 1048576,
})
Consumer Lag Monitoring
kafka-consumer-groups.sh \
--bootstrap-server kafka:9092 \
--describe \
--all-groups \
| awk 'NR==1 || $6 > 0 {print}' \
| sort -k6 -rn
kafka-consumer-groups.sh \
--bootstrap-server kafka:9092 \
--group orders-processor \
--describe
kafka-consumer-groups.sh \
--bootstrap-server kafka:9092 \
--group orders-processor \
--topic orders \
--reset-offsets \
--to-datetime 2024-01-15T03:00:00.000 \
--dry-run
Producer Configuration
from confluent_kafka import Producer
producer = Producer({
'bootstrap.servers': 'kafka:9092',
'acks': 'all',
'enable.idempotence': True,
'compression.type': 'lz4',
'linger.ms': 5,
'batch.size': 65536,
'buffer.memory': 67108864,
'retries': 2147483647,
'delivery.timeout.ms': 120000,
'max.in.flight.requests.per.connection': 5,
})
Security: SASL_SSL + ACLs
# Broker: SASL/SCRAM-SHA-512 over TLS
listeners=SASL_SSL://:9092
ssl.keystore.location=/etc/kafka/ssl/kafka.server.keystore.jks
ssl.keystore.password=${SSL_KEYSTORE_PASSWORD}
ssl.truststore.location=/etc/kafka/ssl/kafka.server.truststore.jks
ssl.truststore.password=${SSL_TRUSTSTORE_PASSWORD}
ssl.client.auth=required # mTLS: require client certificates
sasl.enabled.mechanisms=SCRAM-SHA-512
sasl.mechanism.inter.broker.protocol=SCRAM-SHA-512
# ACL authorization
authorizer.class.name=kafka.security.authorizer.AclAuthorizer
super.users=User:admin
kafka-configs.sh --bootstrap-server kafka:9092 \
--alter \
--add-config 'SCRAM-SHA-512=[iterations=8192,password=secret]' \
--entity-type users \
--entity-name orders-producer
kafka-acls.sh --bootstrap-server kafka:9092 \
--add \
--allow-principal User:orders-producer \
--operation Write \
--topic orders
kafka-acls.sh --bootstrap-server kafka:9092 \
--add \
--allow-principal User:orders-consumer \
--operation Read \
--topic orders \
--group orders-processor
kafka-acls.sh --bootstrap-server kafka:9092 --list
Strimzi Kafka on Kubernetes
apiVersion: kafka.strimzi.io/v1beta2
kind: Kafka
metadata:
name: production
namespace: kafka
spec:
kafka:
version: 3.7.0
replicas: 3
listeners:
- name: tls
port: 9093
type: internal
tls: true
authentication:
type: scram-sha-512
config:
default.replication.factor: 3
min.insync.replicas: 2
unclean.leader.election.enable: "false"
auto.create.topics.enable: "false"
log.retention.hours: 168
compression.type: producer
num.network.threads: 8
num.io.threads: 16
storage:
type: jbod
volumes:
- id: 0
type: persistent-claim
size: 500Gi
class: fast-ssd
deleteClaim: false
rack:
topologyKey: topology.kubernetes.io/zone
resources:
requests:
memory: 16Gi
cpu: "4"
limits:
memory: 16Gi
cpu: "8"
jvmOptions:
-Xms: 6144m
-Xmx: 6144m
metricsConfig:
type: jmxPrometheusExporter
valueFrom:
configMapKeyRef:
name: kafka-metrics
key: kafka-metrics-config.yml
zookeeper:
replicas: 3
storage:
type: persistent-claim
size: 10Gi
class: fast-ssd
entityOperator:
topicOperator: {}
userOperator: {}
Prometheus Monitoring
lowercaseOutputName: true
rules:
- pattern: kafka.server<type=ReplicaManager, name=UnderReplicatedPartitions><>Value
name: kafka_server_replicamanager_underreplicatedpartitions
type: GAUGE
- pattern: kafka.controller<type=KafkaController, name=ActiveControllerCount><>Value
name: kafka_controller_active_count
type: GAUGE
- pattern: kafka.server<type=ReplicaManager, name=IsrShrinksPerSec><>(.+)
name: kafka_server_replicamanager_isrshrinks_total
type: COUNTER
- pattern: kafka.server<type=BrokerTopicMetrics, name=(BytesIn|BytesOut)PerSec, topic=(.+)><>OneMinuteRate
name: kafka_server_brokertopicmetrics_$1_rate
labels:
topic: $2
type: GAUGE
- pattern: kafka.network<type=RequestMetrics, name=TotalTimeMs, request=(.+)><>99thPercentile
name: kafka_network_requestmetrics_totaltime_p99_ms
labels:
request: $1
type: GAUGE
groups:
- name: kafka_alerts
rules:
- alert: KafkaUnderReplicatedPartitions
expr: kafka_server_replicamanager_underreplicatedpartitions > 0
for: 5m
labels:
severity: critical
annotations:
summary: "Under-replicated partitions detected: {{ $value }}"
- alert: KafkaNoActiveController
expr: sum(kafka_controller_active_count) != 1
for: 2m
labels:
severity: critical
annotations:
summary: "Kafka cluster has {{ $value }} active controllers (expected 1)"
- alert: KafkaConsumerGroupLag
expr: kafka_consumer_group_lag{group=~".*-processor"} > 100000
for: 10m
labels:
severity: warning
annotations:
summary: "Consumer group {{ $labels.group }} lag {{ $value }} messages"
- alert: KafkaBrokerDiskHigh
expr: kubelet_volume_stats_available_bytes{persistentvolumeclaim=~"data-production-kafka-.*"} / kubelet_volume_stats_capacity_bytes < 0.15
for: 5m
labels:
severity: warning
annotations:
summary: "Kafka broker disk < 15% free"
Capacity Planning
def estimate_kafka_storage(
topics: list[dict],
overhead_factor: float = 1.3,
) -> dict:
total_bytes_per_sec = 0
total_storage_bytes = 0
for t in topics:
write_bytes_sec = t["msg_size_bytes"] * t["msgs_per_sec"]
replicated_bytes_sec = write_bytes_sec * t["replication_factor"]
storage = replicated_bytes_sec * t["retention_hours"] * 3600
total_bytes_per_sec += replicated_bytes_sec
total_storage_bytes += storage
total_storage_with_overhead = total_storage_bytes * overhead_factor
return {
"total_write_throughput_MBps": round(total_bytes_per_sec / 1e6, 1),
"total_storage_TB": round(total_storage_with_overhead / 1e12, 2),
"recommended_broker_disk_TB": round(total_storage_with_overhead / 3 / 1e12, 2),
}
result = estimate_kafka_storage([
{"name": "orders", "partitions": 24, "replication_factor": 3,
"msg_size_bytes": 2048, "msgs_per_sec": 10000, "retention_hours": 168},
{"name": "events", "partitions": 48, "replication_factor": 3,
"msg_size_bytes": 512, "msgs_per_sec": 100000, "retention_hours": 72},
])
Production Readiness Checklist
Cluster Configuration:
[ ] 3+ brokers, 3 controllers (KRaft) or 3 ZooKeeper nodes
[ ] min.insync.replicas=2 (all topics)
[ ] unclean.leader.election.enable=false
[ ] auto.create.topics.enable=false
[ ] Rack awareness configured (broker.rack + replica.selector.class)
Data Safety:
[ ] Replication factor ≥ 3 for all production topics
[ ] Producer acks=all + enable.idempotence=true
[ ] Consumer manual commit (not auto-commit)
[ ] DLQ (Dead Letter Queue) for poison messages
Security:
[ ] SASL_SSL on all listeners
[ ] mTLS (ssl.client.auth=required) for internal communication
[ ] ACLs on all topics (no wildcard consumer access)
[ ] Credentials rotated via external secret manager
Operations:
[ ] JMX metrics exposed to Prometheus
[ ] Alerts on UnderReplicatedPartitions, ISR shrinks, disk ≥ 85%
[ ] Consumer group lag dashboard per consumer group
[ ] Kafka upgrade runbook documented (rolling restart procedure)
[ ] Topic retention and cleanup policy reviewed quarterly
Anti-Patterns
acks=1 for critical data — broker ack without ISR confirmation loses data on leader failure; use acks=all for anything that matters.
- Auto-create topics enabled — rogue producers create under-configured topics with replication factor 1; disable and manage topics via IaC.
- Consumer auto-commit — marks offset committed before processing completes; use manual commit after successful processing.
- All topics in one consumer group — a single group consuming many unrelated topics makes lag monitoring noisy and rebalance storms more likely.
- No partition count headroom — scaling consumer instances beyond partition count gains nothing; plan partitions for 2–3x future consumer scale.
References
- Confluent production deployment:
docs.confluent.io/platform/current/kafka/deployment.html
- Strimzi operator:
strimzi.io/docs/operators/latest/overview.html
- Kafka monitoring JMX:
docs.confluent.io/platform/current/kafka/monitoring.html
- KRaft mode:
kafka.apache.org/documentation/#kraft
- Related skills:
[[apache-kafka]], [[infra-kafka-cost-optimizer]], [[infra-streaming-reliability-review]], [[dataops-disaster-recovery-review]]