Skip to main content

MSK Kafka

The multi-region shopping mall platform uses Amazon MSK (Managed Streaming for Apache Kafka) to implement event-driven communication between microservices. Topics are replicated between the two regions through MSK Replicator.

Architecture​

Cluster Specifications​

Itemus-east-1us-west-2
Cluster Nameproduction-msk-us-east-1production-msk-us-west-2
Kafka Version3.5.13.5.1
Broker Typekafka.m5.2xlargekafka.m5.2xlarge
Broker Count6 (2 per AZ)6 (2 per AZ)
Storage1TB EBS / broker1TB EBS / broker
AuthenticationSASL/SCRAMSASL/SCRAM
EncryptionTLS + At-restTLS + At-rest

Connection Information​

us-east-1​

ItemValue
Bootstrap Servers (SASL)b-1.productionmskuseast1.xxxxxx.xxx.kafka.us-east-1.amazonaws.com:9096,b-2.productionmskuseast1.xxxxxx.xxx.kafka.us-east-1.amazonaws.com:9096,b-3.productionmskuseast1.xxxxxx.xxx.kafka.us-east-1.amazonaws.com:9096
Port9096 (SASL/SCRAM)

us-west-2​

ItemValue
Bootstrap Servers (SASL)b-1.productionmskuswest2.yyyyyy.yyy.kafka.us-west-2.amazonaws.com:9096
Port9096 (SASL/SCRAM)

Terraform Configuration​

locals {
topics = {
"orders.created" = { partitions = 12 }
"orders.confirmed" = { partitions = 12 }
"orders.cancelled" = { partitions = 6 }
"payments.completed" = { partitions = 12 }
"payments.failed" = { partitions = 6 }
"catalog.updated" = { partitions = 12 }
"catalog.price-changed" = { partitions = 6 }
"inventory.reserved" = { partitions = 24 }
"inventory.released" = { partitions = 12 }
"user.registered" = { partitions = 6 }
"user.activity" = { partitions = 24 }
"reviews.created" = { partitions = 6 }
}
}

resource "aws_msk_configuration" "this" {
name = "${var.environment}-msk-config-${var.region}"
kafka_versions = [var.kafka_version]

server_properties = <<PROPERTIES
auto.create.topics.enable=false
default.replication.factor=3
min.insync.replicas=2
num.partitions=6
log.retention.hours=168
PROPERTIES
}

resource "aws_msk_cluster" "this" {
cluster_name = "${var.environment}-msk-${var.region}"
kafka_version = var.kafka_version # "3.5.1"
number_of_broker_nodes = var.number_of_broker_nodes # 6

broker_node_group_info {
instance_type = var.broker_instance_type # kafka.m5.2xlarge
client_subnets = var.data_subnet_ids
security_groups = [var.security_group_id]

storage_info {
ebs_storage_info {
volume_size = var.ebs_volume_size # 1000
}
}
}

encryption_info {
encryption_at_rest_kms_key_arn = var.kms_key_arn

encryption_in_transit {
client_broker = "TLS"
in_cluster = true
}
}

client_authentication {
sasl {
scram = true
}
}

open_monitoring {
prometheus {
jmx_exporter {
enabled_in_broker = true
}
node_exporter {
enabled_in_broker = true
}
}
}

logging_info {
broker_logs {
cloudwatch_logs {
enabled = true
log_group = aws_cloudwatch_log_group.msk.name
}
}
}

configuration_info {
arn = aws_msk_configuration.this.arn
revision = aws_msk_configuration.this.latest_revision
}
}

Topic Design​

Domain-specific Topics​

Topic Details​

TopicPartitionsReplication FactorRetentionDescription
orders.created1237 daysOrder creation events
orders.confirmed1237 daysOrder confirmation events
orders.cancelled637 daysOrder cancellation events
payments.completed1237 daysPayment completion events
payments.failed6330 days (DLQ)Payment failure events
catalog.updated1237 daysProduct information changes
catalog.price-changed637 daysPrice change events
inventory.reserved2437 daysInventory reservation events
inventory.released1237 daysInventory release events
user.registered637 daysUser registration events
user.activity2437 daysUser activity logs
reviews.created637 daysReview creation events

Dead Letter Queue (DLQ)​

Failed messages are moved to DLQ topics:

DLQ TopicRetentionPurpose
dlq.orders30 daysOrder processing failures
dlq.payments30 daysPayment processing failures
dlq.inventory30 daysInventory processing failures

MSK Replicator​

Replicates topics between the two regions.

resource "aws_msk_replicator" "this" {
count = var.enable_replicator ? 1 : 0

replicator_name = "${var.environment}-msk-replicator-${var.region}"

kafka_cluster {
amazon_msk_cluster {
msk_cluster_arn = var.source_cluster_arn
}

vpc_config {
subnet_ids = var.data_subnet_ids
security_groups_ids = [var.security_group_id]
}
}

kafka_cluster {
amazon_msk_cluster {
msk_cluster_arn = var.target_cluster_arn
}

vpc_config {
subnet_ids = var.data_subnet_ids
security_groups_ids = [var.security_group_id]
}
}

replication_info_list {
source_kafka_cluster_arn = var.source_cluster_arn
target_kafka_cluster_arn = var.target_cluster_arn
target_compression_type = "GZIP"

topic_replication {
topics_to_replicate = var.replicator_topics

copy_access_control_lists_for_topics = true
copy_topic_configurations = true
detect_and_copy_new_topics = true
}

consumer_group_replication {
consumer_groups_to_replicate = [".*"]
synchronise_consumer_group_offsets = true
detect_and_copy_new_consumer_groups = true
}
}

service_execution_role_arn = aws_iam_role.msk_replicator[0].arn
}

Replicator Settings​

ItemValue
Replication Directionus-east-1 -> us-west-2
Replicated TopicsAll major topics
Consumer Group SyncEnabled
CompressionGZIP
Latency< 1 second (typical)

Event Schema​

OrderCreated Event​

{
"eventId": "evt-12345",
"eventType": "OrderCreated",
"timestamp": "2024-03-15T10:30:00Z",
"source": "order-service",
"region": "us-east-1",
"data": {
"orderId": "ORD-001",
"userId": "USER-001",
"items": [
{
"productId": "PROD-001",
"quantity": 2,
"price": 1650000
}
],
"totalAmount": 3300000,
"currency": "KRW",
"shippingAddress": {
"zipCode": "06234",
"address": "123 Teheran-ro, Gangnam-gu, Seoul..."
}
},
"metadata": {
"correlationId": "corr-12345",
"traceId": "trace-12345"
}
}

PaymentCompleted Event​

{
"eventId": "evt-12346",
"eventType": "PaymentCompleted",
"timestamp": "2024-03-15T10:31:00Z",
"source": "payment-service",
"region": "us-east-1",
"data": {
"paymentId": "PAY-001",
"orderId": "ORD-001",
"amount": 3300000,
"currency": "KRW",
"method": "card",
"provider": "toss",
"transactionId": "txn-12345"
},
"metadata": {
"correlationId": "corr-12345",
"traceId": "trace-12345"
}
}

Producer/Consumer Configuration​

Producer Configuration (Go)​

config := sarama.NewConfig()
config.Producer.RequiredAcks = sarama.WaitForAll // acks=all
config.Producer.Retry.Max = 5
config.Producer.Return.Successes = true
config.Net.SASL.Enable = true
config.Net.SASL.Mechanism = sarama.SASLTypeSCRAMSHA512
config.Net.SASL.User = username
config.Net.SASL.Password = password
config.Net.TLS.Enable = true

Consumer Configuration (Go)​

config := sarama.NewConfig()
config.Consumer.Group.Rebalance.Strategy = sarama.BalanceStrategyRoundRobin
config.Consumer.Offsets.Initial = sarama.OffsetOldest
config.Consumer.Offsets.AutoCommit.Enable = true
config.Consumer.Offsets.AutoCommit.Interval = 1 * time.Second
config.Net.SASL.Enable = true
config.Net.SASL.Mechanism = sarama.SASLTypeSCRAMSHA512
config.Net.TLS.Enable = true

Monitoring​

Key Metrics​

MetricDescriptionAlarm Threshold
MessagesInPerSecMessages received per secondMonitor
BytesInPerSecBytes received per second> 100MB/s
UnderReplicatedPartitionsUnder-replicated partitions> 0
OfflinePartitionsCountOffline partitions> 0
ActiveControllerCountActive controllers!= 1
ConsumerLagConsumer lag> 10000
KafkaDataLogsDiskUsedDisk usage> 80%

Prometheus Metrics (JMX Exporter)​

# prometheus-msk-config.yml
- job_name: 'msk'
static_configs:
- targets:
- 'b-1.productionmskuseast1...:11001'
- 'b-2.productionmskuseast1...:11001'
- 'b-3.productionmskuseast1...:11001'

Next Steps​