Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Kafka Metadata Management

Kafka clients maintain metadata about the cluster topology, including broker addresses, topic partitions, and partition leaders. Proper metadata management is essential for efficient request routing and handling topology changes.

Client Metadata CacheCluster InfoBroker ListTopic MetadataordersCluster IDController IDBroker 1: host1:9092Broker 2: host2:9092Broker 3: host3:9092P0: leader=1, ISR=[1,2,3]P1: leader=2, ISR=[2,3,1]P2: leader=3, ISR=[3,1,2]Updated on:- Startup- NotLeaderOrFollower error- metadata.max.age.ms expiry- New topic access

FieldDescription
cluster_idUnique cluster identifier
controller_idCurrent controller broker ID
brokersList of all brokers with host/port/rack
FieldDescription
node_idUnique broker identifier
hostBroker hostname or IP
portBroker port (default 9092)
rackRack identifier (optional)
FieldDescription
nameTopic name
partitionsNumber of partitions
is_internalInternal topic flag

Replication factor is derived from the partition replica lists, not returned directly in the metadata response.

FieldDescription
partitionPartition index
leaderCurrent leader broker ID
leader_epochLeader election epoch
replicasAll replica broker IDs
isrIn-sync replica broker IDs
offline_replicasOffline replica broker IDs

BootstrapClientBootstrapBootstrapClientClientBootstrapBroker 1BootstrapBroker 1BootstrapBroker 2BootstrapBroker 2BootstrapConnect (bootstrap.servers)alt[Connection Success]ConnectedMetadataRequest(topics=[])MetadataResponse(full cluster metadata)Cache metadata:- All brokers- All topics (if authorized)- Partition leaders[Connection Failed]Connect (next bootstrap)ConnectedMetadataRequestMetadataResponse
# Multiple brokers for redundancy
bootstrap.servers=kafka1:9092,kafka2:9092,kafka3:9092
# Client will try brokers in random order
# Only needs ONE successful connection for discovery

Best Practices:

GuidelineRationale
List 3+ brokersRedundancy during broker failures
Use DNS namesEasier maintenance than IPs
Include brokers from different racksSurvive rack failures
Don’t list all brokersUnnecessary, any broker returns full metadata

MetadataRequest {
topics: [TopicName] // Empty for all topics
allow_auto_topic_creation: bool
include_cluster_authorized_operations: bool
include_topic_authorized_operations: bool
}
MetadataResponse {
throttle_time_ms: int32
brokers: [Broker]
cluster_id: string
controller_id: int32
topics: [TopicMetadata]
}
Broker {
node_id: int32
host: string
port: int32
rack: string (nullable)
}
TopicMetadata {
error_code: int16
name: string
is_internal: bool
partitions: [PartitionMetadata]
}
PartitionMetadata {
error_code: int16
partition_index: int32
leader_id: int32
leader_epoch: int32
replica_nodes: [int32]
isr_nodes: [int32]
offline_replicas: [int32]
}

Metadata Refresh TriggersScheduledError-DrivenExplicitmetadata.max.age.msexpiryNOT_LEADER_OR_FOLLOWERUNKNOWN_TOPIC_OR_PARTITION(topic expected)LEADER_NOT_AVAILABLENew topic accessawaitUpdate() call
# Maximum age before forced refresh
metadata.max.age.ms=300000 # 5 minutes (default)
Check metadata ageAge > metadata.max.age.ms?yesnoSchedule refreshError triggers refresh?yesnoImmediate refreshUse cached metadataPrefer least-loaded brokerSelect broker for requestSend MetadataRequestRequest successful?yesnoUpdate cacheNotify waitersApply backoffRetry with different broker

Metadata CacheClusterTopicsPartitionsTimestampsnodes: Map<Integer, Node>controller: NodeclusterId: StringMap<String, TopicMetadata>Map<TopicPartition, PartitionInfo>lastRefreshMs: longlastSuccessfulRefreshMs: long
EventAction
metadata.max.age.ms expiryMark stale, refresh
NOT_LEADER_OR_FOLLOWERInvalidate partition
UNKNOWN_TOPIC_OR_PARTITIONInvalidate topic
Node disconnectInvalidate node
// Find leader for partition
public Node leader(TopicPartition partition) {
PartitionInfo info = partitionsByTopicPartition.get(partition);
if (info == null) {
return null;
}
return info.leader();
}
// Find all partitions for topic
public List<PartitionInfo> partitionsForTopic(String topic) {
return partitionsByTopic.get(topic);
}

Error CodeNameCauseClient Action
3UNKNOWN_TOPIC_OR_PARTITIONTopic/partition doesn’t existRefresh only if the topic is expected to exist
5LEADER_NOT_AVAILABLELeader election in progressWait and retry
6NOT_LEADER_OR_FOLLOWERStale leader infoRefresh metadata
29COORDINATOR_NOT_AVAILABLEGroup coordinator unavailableRetry FindCoordinator
Receive error responseError type?Refresh metadata immediatelyRetry request to new leaderRefresh metadata (if topic expected)Wait for topic creation (if auto-create)Wait (leader election)Refresh metadataRetry requestApply backoffRetry requestReturn error to callerNOT_LEADER_OR_FOLLOWERNon-retriableUNKNOWN_TOPIC_OR_PARTITIONLEADER_NOT_AVAILABLEOther retriable

Leader epoch is a monotonically increasing number that identifies the term of a partition leader. It prevents issues from stale leadership information.

Leader Epoch TimelineLeader Epoch TimelinePartition LeaderBroker 1Broker 2Broker 1Broker 3Leader Epoch01230100200250
ProducerOld LeaderNew LeaderProducerProducerOld Leader(epoch 5)Old Leader(epoch 5)New Leader(epoch 6)New Leader(epoch 6)Client has stale metadata(leader = old, epoch = 5)ProduceRequest(partition, epoch=5)NOT_LEADER_OR_FOLLOWER(current_epoch=6)Refresh metadataProduceRequest(partition, epoch=6)Success

# Client rack (for follower fetching)
client.rack=rack-a
# Enables rack-aware replica selection
Rack ARack BConsumerFollower(Broker 2)Leader(Broker 1)Consumer in rack-a fetchesfrom follower in rack-a(Kafka 2.4+, KIP-392)Fetch fromrack-local followerReplicate

ProducerMetadata CachePartitionerProducerProducerMetadata CacheMetadata CachePartitionerPartitionerGet topic metadataTopicMetadataSelect partition(key, numPartitions)partition=2Get leader for partition 2Broker 3Send to Broker 3
ConsumerMetadata CacheCoordinatorConsumerConsumerMetadata CacheMetadata CacheCoordinatorCoordinatorGet partitions for topics[P0, P1, P2, ...]JoinGroup(topics)Assignment(P0, P2)Get leaders for P0, P2P0→Broker1, P2→Broker3Fetch from assigned partitions

OptimizationConfiguration
Increase refresh intervalmetadata.max.age.ms=600000
Request specific topicsDon’t request all topics
Cache locallyAvoid redundant lookups

Kafka batches metadata requests when multiple threads need updates:

Thread 1Thread 2Metadata ManagerBrokerThread 1Thread 1Thread 2Thread 2Metadata ManagerMetadata ManagerBrokerBrokerrequestUpdate()requestUpdate()Coalesce requestsinto single batchMetadataRequestMetadataResponseMetadata updatedMetadata updated

Producer client metrics are reported under the producer-metrics group.

MetricDescriptionAlert Threshold
metadata-ageAge in seconds of the current producer metadata> 2 × metadata.max.age.ms / 1000
metadata-wait-time-ns-totalCumulative time spent waiting for metadata (ns)Sustained increase in rate
# Enable metadata debug logging
log4j.logger.org.apache.kafka.clients.Metadata=DEBUG
log4j.logger.org.apache.kafka.clients.NetworkClient=DEBUG

Common issues:

SymptomCauseSolution
Frequent refreshesMany NOT_LEADER errorsCheck cluster stability
Stale metadataLong metadata.max.age.msReduce refresh interval
Missing topicsAuthorization issuesCheck ACLs
Wrong broker countPartial visibilityCheck bootstrap servers

FeatureMinimum Version
Basic metadata0.8.0
Rack information0.10.0
Leader epoch0.11.0
Offline replicas1.0.0
Authorized operations2.3.0