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