-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-13598: enable idempotence producer by default and validate the configs #11691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
70b240b
a240180
da25d7b
8779dcf
a5f23b8
ca93d15
14294aa
4d5d527
3c2f1de
a3c7bcf
03cd033
eadc897
1a96c6b
d1e3bcb
d312fe0
306501a
9ba42d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -442,7 +442,7 @@ public KafkaProducer(Properties properties, Serializer<K> keySerializer, Seriali | |
|
|
||
| // visible for testing | ||
| Sender newSender(LogContext logContext, KafkaClient kafkaClient, ProducerMetadata metadata) { | ||
| int maxInflightRequests = configureInflightRequests(producerConfig); | ||
| int maxInflightRequests = producerConfig.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION); | ||
| int requestTimeoutMs = producerConfig.getInt(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG); | ||
| ChannelBuilder channelBuilder = ClientUtils.createChannelBuilder(producerConfig, time, logContext); | ||
| ProducerMetrics metricsRegistry = new ProducerMetrics(this.metrics); | ||
|
|
@@ -465,7 +465,8 @@ Sender newSender(LogContext logContext, KafkaClient kafkaClient, ProducerMetadat | |
| apiVersions, | ||
| throttleTimeSensor, | ||
| logContext); | ||
| short acks = configureAcks(producerConfig, log); | ||
|
|
||
| short acks = Short.parseShort(producerConfig.getString(ProducerConfig.ACKS_CONFIG)); | ||
| return new Sender(logContext, | ||
| client, | ||
| metadata, | ||
|
|
@@ -493,7 +494,7 @@ private static int configureDeliveryTimeout(ProducerConfig config, Logger log) { | |
| int lingerAndRequestTimeoutMs = (int) Math.min((long) lingerMs + requestTimeoutMs, Integer.MAX_VALUE); | ||
|
|
||
| if (deliveryTimeoutMs < lingerAndRequestTimeoutMs) { | ||
| if (config.originals().containsKey(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG)) { | ||
| if (config.hasKeyInOriginals(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG)) { | ||
| // throw an exception if the user explicitly set an inconsistent value | ||
| throw new ConfigException(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG | ||
| + " should be equal to or larger than " + ProducerConfig.LINGER_MS_CONFIG | ||
|
|
@@ -511,15 +512,8 @@ private static int configureDeliveryTimeout(ProducerConfig config, Logger log) { | |
|
|
||
| private TransactionManager configureTransactionState(ProducerConfig config, | ||
| LogContext logContext) { | ||
|
|
||
| TransactionManager transactionManager = null; | ||
|
|
||
| final boolean userConfiguredIdempotence = config.originals().containsKey(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG); | ||
| final boolean userConfiguredTransactions = config.originals().containsKey(ProducerConfig.TRANSACTIONAL_ID_CONFIG); | ||
| if (userConfiguredTransactions && !userConfiguredIdempotence) | ||
| log.info("Overriding the default {} to true since {} is specified.", ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, | ||
| ProducerConfig.TRANSACTIONAL_ID_CONFIG); | ||
|
|
||
|
Comment on lines
517
to
522
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We won't overriding the default |
||
| if (config.idempotenceEnabled()) { | ||
| final String transactionalId = config.getString(ProducerConfig.TRANSACTIONAL_ID_CONFIG); | ||
| final int transactionTimeoutMs = config.getInt(ProducerConfig.TRANSACTION_TIMEOUT_CONFIG); | ||
|
|
@@ -540,28 +534,6 @@ private TransactionManager configureTransactionState(ProducerConfig config, | |
| return transactionManager; | ||
| } | ||
|
|
||
| private static int configureInflightRequests(ProducerConfig config) { | ||
| if (config.idempotenceEnabled() && 5 < config.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION)) { | ||
| throw new ConfigException("Must set " + ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION + " to at most 5" + | ||
| " to use the idempotent producer."); | ||
| } | ||
| return config.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION); | ||
| } | ||
|
|
||
| private static short configureAcks(ProducerConfig config, Logger log) { | ||
| boolean userConfiguredAcks = config.originals().containsKey(ProducerConfig.ACKS_CONFIG); | ||
| short acks = Short.parseShort(config.getString(ProducerConfig.ACKS_CONFIG)); | ||
|
|
||
| if (config.idempotenceEnabled()) { | ||
| if (!userConfiguredAcks) | ||
| log.info("Overriding the default {} to all since idempotence is enabled.", ProducerConfig.ACKS_CONFIG); | ||
| else if (acks != -1) | ||
| throw new ConfigException("Must set " + ProducerConfig.ACKS_CONFIG + " to all in order to use the idempotent " + | ||
| "producer. Otherwise we cannot guarantee idempotence."); | ||
| } | ||
| return acks; | ||
| } | ||
|
|
||
|
Comment on lines
543
to
564
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move this idempotence configs validation into |
||
| /** | ||
| * Needs to be called before any other methods when the transactional.id is set in the configuration. | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -438,15 +438,14 @@ public class ProducerConfig extends AbstractConfig { | |
| @Override | ||
| protected Map<String, Object> postProcessParsedConfig(final Map<String, Object> parsedValues) { | ||
| Map<String, Object> refinedConfigs = CommonClientConfigs.postProcessReconnectBackoffConfigs(this, parsedValues); | ||
| maybeOverrideEnableIdempotence(refinedConfigs); | ||
| postProcessAndValidateIdempotenceConfigs(refinedConfigs); | ||
| maybeOverrideClientId(refinedConfigs); | ||
| maybeOverrideAcksAndRetries(refinedConfigs); | ||
| return refinedConfigs; | ||
| } | ||
|
|
||
| private void maybeOverrideClientId(final Map<String, Object> configs) { | ||
| String refinedClientId; | ||
| boolean userConfiguredClientId = this.originals().containsKey(CLIENT_ID_CONFIG); | ||
| boolean userConfiguredClientId = this.hasKeyInOriginals(CLIENT_ID_CONFIG); | ||
| if (userConfiguredClientId) { | ||
| refinedClientId = this.getString(CLIENT_ID_CONFIG); | ||
| } else { | ||
|
|
@@ -456,33 +455,29 @@ private void maybeOverrideClientId(final Map<String, Object> configs) { | |
| configs.put(CLIENT_ID_CONFIG, refinedClientId); | ||
| } | ||
|
|
||
| private void maybeOverrideEnableIdempotence(final Map<String, Object> configs) { | ||
| boolean userConfiguredIdempotence = this.originals().containsKey(ENABLE_IDEMPOTENCE_CONFIG); | ||
| boolean userConfiguredTransactions = this.originals().containsKey(TRANSACTIONAL_ID_CONFIG); | ||
|
|
||
| if (userConfiguredTransactions && !userConfiguredIdempotence) { | ||
| configs.put(ENABLE_IDEMPOTENCE_CONFIG, true); | ||
| } | ||
| } | ||
|
Comment on lines
459
to
466
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unnecessary method since |
||
|
|
||
| private void maybeOverrideAcksAndRetries(final Map<String, Object> configs) { | ||
| private void postProcessAndValidateIdempotenceConfigs(final Map<String, Object> configs) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We won't override any configs now, change the method name. |
||
| final String acksStr = parseAcks(this.getString(ACKS_CONFIG)); | ||
| configs.put(ACKS_CONFIG, acksStr); | ||
| // For idempotence producers, values for `RETRIES_CONFIG` and `ACKS_CONFIG` might need to be overridden. | ||
|
|
||
| // For idempotence producers, values for `RETRIES_CONFIG` and `ACKS_CONFIG` need validation | ||
| if (idempotenceEnabled()) { | ||
| boolean userConfiguredRetries = this.originals().containsKey(RETRIES_CONFIG); | ||
| if (this.getInt(RETRIES_CONFIG) == 0) { | ||
| boolean userConfiguredRetries = hasKeyInOriginals(RETRIES_CONFIG); | ||
| if (userConfiguredRetries && this.getInt(RETRIES_CONFIG) == 0) { | ||
| throw new ConfigException("Must set " + ProducerConfig.RETRIES_CONFIG + " to non-zero when using the idempotent producer."); | ||
| } | ||
| configs.put(RETRIES_CONFIG, userConfiguredRetries ? this.getInt(RETRIES_CONFIG) : Integer.MAX_VALUE); | ||
|
|
||
| boolean userConfiguredAcks = this.originals().containsKey(ACKS_CONFIG); | ||
| boolean userConfiguredAcks = hasKeyInOriginals(ACKS_CONFIG); | ||
| final short acks = Short.valueOf(acksStr); | ||
| if (userConfiguredAcks && acks != (short) -1) { | ||
| throw new ConfigException("Must set " + ACKS_CONFIG + " to all in order to use the idempotent " + | ||
| "producer. Otherwise we cannot guarantee idempotence."); | ||
| } | ||
| configs.put(ACKS_CONFIG, "-1"); | ||
|
|
||
| boolean userConfiguredInflightRequests = hasKeyInOriginals(MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION); | ||
| if (userConfiguredInflightRequests && 5 < this.getInt(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not being familiar with the code, can we make
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good suggestion! Updated. |
||
| throw new ConfigException("Must set " + ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION + " to at most 5" + | ||
| " to use the idempotent producer."); | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add validation for |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -514,11 +509,12 @@ public ProducerConfig(Map<String, Object> props) { | |
| } | ||
|
|
||
| boolean idempotenceEnabled() { | ||
| boolean userConfiguredIdempotence = this.originals().containsKey(ENABLE_IDEMPOTENCE_CONFIG); | ||
| boolean userConfiguredTransactions = this.originals().containsKey(TRANSACTIONAL_ID_CONFIG); | ||
| boolean idempotenceEnabled = userConfiguredIdempotence && this.getBoolean(ENABLE_IDEMPOTENCE_CONFIG); | ||
|
|
||
| if (!idempotenceEnabled && userConfiguredIdempotence && userConfiguredTransactions) | ||
| boolean userConfiguredIdempotence = this.hasKeyInOriginals(ENABLE_IDEMPOTENCE_CONFIG); | ||
| boolean userConfiguredTransactions = this.hasKeyInOriginals(TRANSACTIONAL_ID_CONFIG); | ||
| boolean idempotenceEnabled = !userConfiguredIdempotence || this.getBoolean(ENABLE_IDEMPOTENCE_CONFIG); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the key fix: before the PR, we only considered the boolean idempotenceEnabled = userConfiguredIdempotence && this.getBoolean(ENABLE_IDEMPOTENCE_CONFIG);Now, change it to fix the issue. boolean idempotenceEnabled = !userConfiguredIdempotence || this.getBoolean(ENABLE_IDEMPOTENCE_CONFIG); |
||
| // default value for enable.idempotence is true since v3.0, it must be wrong setting if transactionId set and idempotence disabled | ||
| if (!idempotenceEnabled && userConfiguredTransactions) | ||
| throw new ConfigException("Cannot set a " + ProducerConfig.TRANSACTIONAL_ID_CONFIG + " without also enabling idempotence."); | ||
| return userConfiguredTransactions || idempotenceEnabled; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think the check for
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right! After we default the |
||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,14 +24,15 @@ | |
| import org.slf4j.LoggerFactory; | ||
| import org.apache.kafka.common.config.provider.ConfigProvider; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.HashMap; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Set; | ||
| import java.util.HashSet; | ||
| import java.util.HashMap; | ||
| import java.util.Objects; | ||
| import java.util.TreeMap; | ||
| import java.util.ArrayList; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's keep the current import order
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Weird, not sure why intellij "help" me reorder it. Updated! |
||
| import java.util.concurrent.ConcurrentHashMap; | ||
|
|
||
| /** | ||
|
|
@@ -226,6 +227,11 @@ public Set<String> unused() { | |
| return keys; | ||
| } | ||
|
|
||
| public boolean hasKeyInOriginals(String configKey) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about |
||
| Objects.requireNonNull(configKey, "config key cannot be null"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this null check? Shouldn't we rely on the behaviour of the underlying Map?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make sense! We can rely on the underlying map. |
||
| return originals.containsKey(configKey); | ||
| } | ||
|
|
||
| public Map<String, Object> originals() { | ||
| Map<String, Object> copy = new RecordingMap<>(); | ||
| copy.putAll(originals); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
originals().containsKeyusage. Inoriginals(), we'll make a copy of the configs, and in most cases, we only want to checkcontainsKey. Refactor it by directly check the key inAbstractConfigsto avoid unnecessary map copy.