Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/src/main/scala/kafka/server/BrokerFeatures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ object BrokerFeatures extends Logging {
def createDefault(): BrokerFeatures = {
new BrokerFeatures(Features.supportedFeatures(
java.util.Collections.singletonMap(MetadataVersion.FEATURE_NAME,
new SupportedVersionRange(MetadataVersion.IBP_3_0_IV0.featureLevel(), MetadataVersion.latest().featureLevel()))))
new SupportedVersionRange(MetadataVersion.MINIMUM_KRAFT_VERSION.featureLevel(), MetadataVersion.latest().featureLevel()))))
}

def createEmpty(): BrokerFeatures = {
Expand Down
50 changes: 18 additions & 32 deletions core/src/main/scala/kafka/server/KafkaConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1790,38 +1790,24 @@ class KafkaConfig private(doLog: Boolean, val props: java.util.Map[_, _], dynami
// We keep the user-provided String as `MetadataVersion.fromVersionString` can choose a slightly different version (eg if `0.10.0`
// is passed, `0.10.0-IV0` may be picked)
val interBrokerProtocolVersionString = getString(KafkaConfig.InterBrokerProtocolVersionProp)
val interBrokerProtocolVersion = MetadataVersion.fromVersionString(interBrokerProtocolVersionString)

val fetchRequestVersion: Short =
if (interBrokerProtocolVersion.isAtLeast(IBP_3_1_IV0)) 13
else if (interBrokerProtocolVersion.isAtLeast(IBP_2_7_IV1)) 12
else if (interBrokerProtocolVersion.isAtLeast(IBP_2_3_IV1)) 11
else if (interBrokerProtocolVersion.isAtLeast(IBP_2_1_IV2)) 10
else if (interBrokerProtocolVersion.isAtLeast(IBP_2_0_IV1)) 8
else if (interBrokerProtocolVersion.isAtLeast(IBP_1_1_IV0)) 7
else if (interBrokerProtocolVersion.isAtLeast(IBP_0_11_0_IV1)) 5
else if (interBrokerProtocolVersion.isAtLeast(IBP_0_11_0_IV0)) 4
else if (interBrokerProtocolVersion.isAtLeast(IBP_0_10_1_IV1)) 3
else if (interBrokerProtocolVersion.isAtLeast(IBP_0_10_0_IV0)) 2
else if (interBrokerProtocolVersion.isAtLeast(IBP_0_9_0)) 1
else 0

val offsetForLeaderEpochRequestVersion: Short =
if (interBrokerProtocolVersion.isAtLeast(IBP_2_8_IV0)) 4
else if (interBrokerProtocolVersion.isAtLeast(IBP_2_3_IV1)) 3
else if (interBrokerProtocolVersion.isAtLeast(IBP_2_1_IV1)) 2
else if (interBrokerProtocolVersion.isAtLeast(IBP_2_0_IV0)) 1
else 0

val listOffsetRequestVersion: Short =
if (interBrokerProtocolVersion.isAtLeast(IBP_3_0_IV1)) 7
else if (interBrokerProtocolVersion.isAtLeast(IBP_2_8_IV0)) 6
else if (interBrokerProtocolVersion.isAtLeast(IBP_2_2_IV1)) 5
else if (interBrokerProtocolVersion.isAtLeast(IBP_2_1_IV1)) 4
else if (interBrokerProtocolVersion.isAtLeast(IBP_2_0_IV1)) 3
else if (interBrokerProtocolVersion.isAtLeast(IBP_0_11_0_IV0)) 2
else if (interBrokerProtocolVersion.isAtLeast(IBP_0_10_1_IV2)) 1
else 0
val interBrokerProtocolVersion = if (processRoles.isEmpty) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are still a few places referencing this static value (Partition, GroupCoordinator, etc). Should we change them to use MetadataVersion?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we should fix those as well. I believe all the remaining usages are for versions prior to 3.0-IV1, so in KRaft mode we can pass a static version 3.0-IV1 (regardless of the actual MetadataVersion). Setting the IBP statically to the minimum KRaft compatible version (when in KRaft mode) also guards against any incorrect/inadvertent future usages of config.interBrokerProtocolVersion.

Basically, the PR grew pretty large/complex when I was trying to include the remaining conversions. WDYT about completing those in a separate PR?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mumrah : Thanks for the explanation. Covering that in a separate PR sounds good to me.

MetadataVersion.fromVersionString(interBrokerProtocolVersionString)
} else {
if (originals.containsKey(KafkaConfig.InterBrokerProtocolVersionProp)) {
// A user-supplied IBP was given
val configuredVersion = MetadataVersion.fromVersionString(interBrokerProtocolVersionString)
if (!configuredVersion.isKRaftSupported) {
throw new ConfigException(s"A non-KRaft version ${interBrokerProtocolVersionString} given for ${KafkaConfig.InterBrokerProtocolVersionProp}")
} else {
warn(s"${KafkaConfig.InterBrokerProtocolVersionProp} is deprecated in KRaft mode as of 3.3 and will only " +
s"be read when first upgrading from a KRaft prior to 3.3. See kafka-storage.sh help for details on setting " +
s"the metadata version for a new KRaft cluster.")
}
}
// In KRaft mode, we pin this value to the minimum KRaft-supported version. This prevents inadvertent usage of
// the static IBP config in broker components running in KRaft mode
MetadataVersion.MINIMUM_KRAFT_VERSION
}

/** ********* Controlled shutdown configuration ***********/
val controlledShutdownMaxRetries = getInt(KafkaConfig.ControlledShutdownMaxRetriesProp)
Expand Down
16 changes: 10 additions & 6 deletions core/src/main/scala/kafka/server/KafkaRaftServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import org.apache.kafka.server.metrics.KafkaYammerMetrics

import java.nio.file.Paths
import scala.collection.Seq
import scala.compat.java8.FunctionConverters.asJavaSupplier
import scala.jdk.CollectionConverters._

/**
Expand Down Expand Up @@ -180,13 +181,16 @@ object KafkaRaftServer {
"If you intend to create a new broker, you should remove all data in your data directories (log.dirs).")
}

// Load the bootstrap metadata file or, in the case of an upgrade from KRaft preview, bootstrap the
// metadata.version corresponding to a user-configured IBP.
val bootstrapMetadata = if (config.originals.containsKey(KafkaConfig.InterBrokerProtocolVersionProp)) {
BootstrapMetadata.load(Paths.get(config.metadataLogDir), config.interBrokerProtocolVersion)
} else {
BootstrapMetadata.load(Paths.get(config.metadataLogDir), MetadataVersion.IBP_3_0_IV0)
// Load the bootstrap metadata file. In the case of an upgrade from older KRaft where there is no bootstrap metadata,
// read the IBP from config in order to bootstrap the equivalent metadata version.
def getUserDefinedIBPVersionOrThrow(): MetadataVersion = {
if (config.originals.containsKey(KafkaConfig.InterBrokerProtocolVersionProp)) {
MetadataVersion.fromVersionString(config.interBrokerProtocolVersionString)
} else {
throw new KafkaException(s"Cannot upgrade from KRaft version prior to 3.3 without first setting ${KafkaConfig.InterBrokerProtocolVersionProp} on each broker.")
}
}
val bootstrapMetadata = BootstrapMetadata.load(Paths.get(config.metadataLogDir), asJavaSupplier(() => getUserDefinedIBPVersionOrThrow()))

(metaProperties, bootstrapMetadata, offlineDirs.toSeq)
}
Expand Down
22 changes: 16 additions & 6 deletions core/src/main/scala/kafka/server/RemoteLeaderEndPoint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import org.apache.kafka.common.message.OffsetForLeaderEpochRequestData.{OffsetFo
import org.apache.kafka.common.message.OffsetForLeaderEpochResponseData.EpochEndOffset
import org.apache.kafka.common.protocol.Errors
import org.apache.kafka.common.requests.{FetchRequest, FetchResponse, ListOffsetsRequest, ListOffsetsResponse, OffsetsForLeaderEpochRequest, OffsetsForLeaderEpochResponse}
import org.apache.kafka.server.common.MetadataVersion
import org.apache.kafka.server.common.MetadataVersion.IBP_0_10_1_IV2

import scala.jdk.CollectionConverters._
Expand All @@ -46,13 +47,16 @@ import scala.compat.java8.OptionConverters.RichOptionForJava8
* @param brokerConfig Broker configuration
* @param replicaManager A ReplicaManager
* @param quota The quota, used when building a fetch request
* @param metadataVersionSupplier A supplier that returns the current MetadataVersion. This can change during
* runtime in KRaft mode.
*/
class RemoteLeaderEndPoint(logPrefix: String,
blockingSender: BlockingSend,
private[server] val fetchSessionHandler: FetchSessionHandler, // visible for testing
brokerConfig: KafkaConfig,
replicaManager: ReplicaManager,
quota: ReplicaQuota) extends LeaderEndPoint with Logging {
quota: ReplicaQuota,
metadataVersionSupplier: () => MetadataVersion) extends LeaderEndPoint with Logging {

this.logIdent = logPrefix

Expand All @@ -61,7 +65,7 @@ class RemoteLeaderEndPoint(logPrefix: String,
private val maxBytes = brokerConfig.replicaFetchResponseMaxBytes
private val fetchSize = brokerConfig.replicaFetchMaxBytes

override val isTruncationOnFetchSupported = brokerConfig.interBrokerProtocolVersion.isTruncationOnFetchSupported
override def isTruncationOnFetchSupported = metadataVersionSupplier().isTruncationOnFetchSupported

override def initiateClose(): Unit = blockingSender.initiateClose()

Expand Down Expand Up @@ -106,7 +110,8 @@ class RemoteLeaderEndPoint(logPrefix: String,
.setPartitionIndex(topicPartition.partition)
.setCurrentLeaderEpoch(currentLeaderEpoch)
.setTimestamp(earliestOrLatest)))
val requestBuilder = ListOffsetsRequest.Builder.forReplica(brokerConfig.listOffsetRequestVersion, brokerConfig.brokerId)
val metadataVersion = metadataVersionSupplier()
val requestBuilder = ListOffsetsRequest.Builder.forReplica(metadataVersion.listOffsetRequestVersion, brokerConfig.brokerId)
.setTargetTimes(Collections.singletonList(topic))

val clientResponse = blockingSender.sendRequest(requestBuilder)
Expand All @@ -116,7 +121,7 @@ class RemoteLeaderEndPoint(logPrefix: String,

Errors.forCode(responsePartition.errorCode) match {
case Errors.NONE =>
if (brokerConfig.interBrokerProtocolVersion.isAtLeast(IBP_0_10_1_IV2))
if (metadataVersion.isAtLeast(IBP_0_10_1_IV2))
responsePartition.offset
else
responsePartition.oldStyleOffsets.get(0)
Expand All @@ -141,7 +146,7 @@ class RemoteLeaderEndPoint(logPrefix: String,
}

val epochRequest = OffsetsForLeaderEpochRequest.Builder.forFollower(
brokerConfig.offsetForLeaderEpochRequestVersion, topics, brokerConfig.brokerId)
metadataVersionSupplier().offsetForLeaderEpochRequestVersion, topics, brokerConfig.brokerId)
debug(s"Sending offset for leader epoch request $epochRequest")

try {
Expand Down Expand Up @@ -201,7 +206,12 @@ class RemoteLeaderEndPoint(logPrefix: String,
val fetchRequestOpt = if (fetchData.sessionPartitions.isEmpty && fetchData.toForget.isEmpty) {
None
} else {
val version: Short = if (brokerConfig.fetchRequestVersion >= 13 && !fetchData.canUseTopicIds) 12 else brokerConfig.fetchRequestVersion
val metadataVersion = metadataVersionSupplier()
val version: Short = if (metadataVersion.fetchRequestVersion >= 13 && !fetchData.canUseTopicIds) {
12
} else {
metadataVersion.fetchRequestVersion
}
val requestBuilder = FetchRequest.Builder
.forReplica(version, brokerConfig.brokerId, maxWait, minBytes, fetchData.toSend)
.setMaxBytes(maxBytes)
Expand Down
9 changes: 6 additions & 3 deletions core/src/main/scala/kafka/server/ReplicaFetcherManager.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ import kafka.cluster.BrokerEndPoint
import org.apache.kafka.clients.FetchSessionHandler
import org.apache.kafka.common.metrics.Metrics
import org.apache.kafka.common.utils.{LogContext, Time}
import org.apache.kafka.server.common.MetadataVersion

class ReplicaFetcherManager(brokerConfig: KafkaConfig,
protected val replicaManager: ReplicaManager,
metrics: Metrics,
time: Time,
threadNamePrefix: Option[String] = None,
quotaManager: ReplicationQuotaManager)
quotaManager: ReplicationQuotaManager,
metadataVersionSupplier: () => MetadataVersion)
extends AbstractFetcherManager[ReplicaFetcherThread](
name = "ReplicaFetcherManager on broker " + brokerConfig.brokerId,
clientId = "Replica",
Expand All @@ -41,9 +43,10 @@ class ReplicaFetcherManager(brokerConfig: KafkaConfig,
val endpoint = new BrokerBlockingSender(sourceBroker, brokerConfig, metrics, time, fetcherId,
s"broker-${brokerConfig.brokerId}-fetcher-$fetcherId", logContext)
val fetchSessionHandler = new FetchSessionHandler(logContext, sourceBroker.id)
val leader = new RemoteLeaderEndPoint(logContext.logPrefix, endpoint, fetchSessionHandler, brokerConfig, replicaManager, quotaManager)
val leader = new RemoteLeaderEndPoint(logContext.logPrefix, endpoint, fetchSessionHandler, brokerConfig,
replicaManager, quotaManager, metadataVersionSupplier)
new ReplicaFetcherThread(threadName, leader, brokerConfig, failedPartitions, replicaManager,
quotaManager, logContext.logPrefix)
quotaManager, logContext.logPrefix, metadataVersionSupplier)
}

def shutdown(): Unit = {
Expand Down
8 changes: 5 additions & 3 deletions core/src/main/scala/kafka/server/ReplicaFetcherThread.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ import kafka.log.{LeaderOffsetIncremented, LogAppendInfo}
import org.apache.kafka.common.record.MemoryRecords
import org.apache.kafka.common.requests._
import org.apache.kafka.common.TopicPartition
import org.apache.kafka.server.common.MetadataVersion

class ReplicaFetcherThread(name: String,
leader: LeaderEndPoint,
brokerConfig: KafkaConfig,
failedPartitions: FailedPartitions,
replicaMgr: ReplicaManager,
quota: ReplicaQuota,
logPrefix: String)
logPrefix: String,
metadataVersionSupplier: () => MetadataVersion)
extends AbstractFetcherThread(name = name,
clientId = name,
leader = leader,
Expand All @@ -39,7 +41,7 @@ class ReplicaFetcherThread(name: String,

this.logIdent = logPrefix

override protected val isOffsetForLeaderEpochSupported: Boolean = brokerConfig.interBrokerProtocolVersion.isOffsetForLeaderEpochSupported
override protected val isOffsetForLeaderEpochSupported: Boolean = metadataVersionSupplier().isOffsetForLeaderEpochSupported

override protected def latestEpoch(topicPartition: TopicPartition): Option[Int] = {
replicaMgr.localLogOrException(topicPartition).latestEpoch
Expand Down Expand Up @@ -135,7 +137,7 @@ class ReplicaFetcherThread(name: String,

def maybeWarnIfOversizedRecords(records: MemoryRecords, topicPartition: TopicPartition): Unit = {
// oversized messages don't cause replication to fail from fetch request version 3 (KIP-74)
if (brokerConfig.fetchRequestVersion <= 2 && records.sizeInBytes > 0 && records.validBytes <= 0)
if (metadataVersionSupplier().fetchRequestVersion <= 2 && records.sizeInBytes > 0 && records.validBytes <= 0)
error(s"Replication is failing due to a message that is greater than replica.fetch.max.bytes for partition $topicPartition. " +
"This generally occurs when the max.message.bytes has been overridden to exceed this value and a suitably large " +
"message has also been sent. To fix this problem increase replica.fetch.max.bytes in your broker config to be " +
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/scala/kafka/server/ReplicaManager.scala
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ class ReplicaManager(val config: KafkaConfig,
// If inter-broker protocol (IBP) < 1.0, the controller will send LeaderAndIsrRequest V0 which does not include isNew field.
// In this case, the broker receiving the request cannot determine whether it is safe to create a partition if a log directory has failed.
// Thus, we choose to halt the broker on any log directory failure if IBP < 1.0
val haltBrokerOnFailure = config.interBrokerProtocolVersion.isLessThan(IBP_1_0_IV0)
val haltBrokerOnFailure = metadataCache.metadataVersion().isLessThan(IBP_1_0_IV0)
logDirFailureHandler = new LogDirFailureHandler("LogDirFailureHandler", haltBrokerOnFailure)
logDirFailureHandler.start()
}
Expand Down Expand Up @@ -1773,7 +1773,7 @@ class ReplicaManager(val config: KafkaConfig,
* OffsetForLeaderEpoch request.
*/
protected def initialFetchOffset(log: UnifiedLog): Long = {
if (config.interBrokerProtocolVersion.isTruncationOnFetchSupported() && log.latestEpoch.nonEmpty)
if (metadataCache.metadataVersion().isTruncationOnFetchSupported && log.latestEpoch.nonEmpty)
log.logEndOffset
else
log.highWatermark
Expand Down Expand Up @@ -1903,7 +1903,7 @@ class ReplicaManager(val config: KafkaConfig,
}

protected def createReplicaFetcherManager(metrics: Metrics, time: Time, threadNamePrefix: Option[String], quotaManager: ReplicationQuotaManager) = {
new ReplicaFetcherManager(config, this, metrics, time, threadNamePrefix, quotaManager)
new ReplicaFetcherManager(config, this, metrics, time, threadNamePrefix, quotaManager, () => metadataCache.metadataVersion())
}

protected def createReplicaAlterLogDirsManager(quotaManager: ReplicationQuotaManager, brokerTopicStats: BrokerTopicStats) = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import org.apache.kafka.common.internals.Topic
import org.apache.kafka.image.{MetadataDelta, MetadataImage, TopicDelta, TopicsImage}
import org.apache.kafka.metadata.authorizer.ClusterMetadataAuthorizer
import org.apache.kafka.server.authorizer.Authorizer
import org.apache.kafka.server.common.MetadataVersion

import scala.collection.mutable

Expand Down Expand Up @@ -132,10 +131,7 @@ class BrokerMetadataPublisher(conf: KafkaConfig,
// Publish the new metadata image to the metadata cache.
metadataCache.setImage(newImage)

val metadataVersionLogMsg = newImage.features().metadataVersion() match {
case MetadataVersion.UNINITIALIZED => "un-initialized metadata.version"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does broker startup have any guarantees with respect to the metadata version in the log? For example, does the controller require the latest metadata feature record to be consumed before a broker can be unfenced? I'm mainly trying to understand if we might revert to an older version and what the consequences of that would be.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The broker remains fenced until it catches up on the metadata log (up to the high watermark, I believe). The broker components that care about metadata (which is most of them) have some "initialize" or "startup" method called when we first publish metadata on a broker. This should mean that when a broker component is fully started, it will have the current metadata version of the cluster.

If a metadata version upgrade happened while a broker was initializing, I think it would either be included in the initial MetadataImage, or it would be handled separately. Either way, components which use the metadata version need to cope with the version changing at runtime.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I guess I was considering if we need any protection from an uninitialized metadata version in the code. We had the UNINITIALIZED sentinel before, but now it looks like we would use the min KRaft version or IBP. I don't know if there are any consequences to relying on an old version. Writes to the metadata log will go through the controller. Replication is protected by fencing. How about forwarding?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IBP is just used for bootstrapping a metadata version when upgrading from an older KRaft cluster (see my other comment). Broker and controller components will assume the minimum KRaft version until we finish loading the metadata log.

Forwarding should be okay since we don't start accepting external connections until after we've caught up on metadata in the broker.

// Enable inbound TCP connections. Each endpoint will be started only once its matching
// authorizer future is completed.
socketServer.enableRequestProcessing(authorizerFutures)

case mv: MetadataVersion => s"metadata.version ${mv.featureLevel()}"
}
val metadataVersionLogMsg = s"metadata.version ${newImage.features().metadataVersion()}"

if (_firstPublish) {
info(s"Publishing initial metadata at offset $highestOffsetAndEpoch with $metadataVersionLogMsg.")
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/kafka/tools/StorageTool.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ object StorageTool extends Logging {
val clusterId = namespace.getString("cluster_id")
val metadataVersion = getMetadataVersion(namespace)
if (!metadataVersion.isKRaftSupported) {
throw new TerseFailure(s"Must specify a metadata version of at least 1.")
throw new TerseFailure(s"Must specify a valid KRaft metadata version of at least 3.0.")
}
val metaProperties = buildMetadataProperties(clusterId, config.get)
val ignoreFormatted = namespace.getBoolean("ignore_formatted")
Expand Down Expand Up @@ -99,7 +99,7 @@ object StorageTool extends Logging {
action(storeTrue())
formatParser.addArgument("--release-version", "-r").
action(store()).
help(s"A release version to use for the initial metadata.version. The default is (${MetadataVersion.latest().version()})")
help(s"A KRaft release version to use for the initial metadata version. The minimum is 3.0, the default is (${MetadataVersion.latest().version()})")
Comment thread
mumrah marked this conversation as resolved.
Outdated

parser.parseArgsOrFail(args)
}
Expand Down
Loading