-
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 16 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, | ||
|
|
@@ -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); | ||
|
|
||
| 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 |
|---|---|---|
|
|
@@ -207,6 +207,8 @@ public class ProducerConfig extends AbstractConfig { | |
| private static final String MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION_DOC = "The maximum number of unacknowledged requests the client will send on a single connection before blocking." | ||
| + " Note that if this config is set to be greater than 1 and <code>enable.idempotence</code> is set to false, there is a risk of" | ||
| + " message re-ordering after a failed send due to retries (i.e., if retries are enabled)."; | ||
| // max.in.flight.requests.per.connection should be less than or equal to 5 when idempotence producer enabled to ensure message ordering | ||
| private static final int MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION_FOR_IDEMPOTENCE = 5; | ||
|
|
||
| /** <code>retries</code> */ | ||
| public static final String RETRIES_CONFIG = CommonClientConfigs.RETRIES_CONFIG; | ||
|
|
@@ -269,8 +271,8 @@ public class ProducerConfig extends AbstractConfig { | |
| public static final String ENABLE_IDEMPOTENCE_CONFIG = "enable.idempotence"; | ||
| public static final String ENABLE_IDEMPOTENCE_DOC = "When set to 'true', the producer will ensure that exactly one copy of each message is written in the stream. If 'false', producer " | ||
| + "retries due to broker failures, etc., may write duplicates of the retried message in the stream. " | ||
| + "Note that enabling idempotence requires <code>" + MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION + "</code> to be less than or equal to 5 " | ||
| + "(with message ordering preserved for any allowable value), <code>" + RETRIES_CONFIG + "</code> to be greater than 0, and <code>" | ||
| + "Note that enabling idempotence requires <code>" + MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION + "</code> to be less than or equal to " + MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION_FOR_IDEMPOTENCE | ||
| + " (with message ordering preserved for any allowable value), <code>" + RETRIES_CONFIG + "</code> to be greater than 0, and <code>" | ||
| + ACKS_CONFIG + "</code> must be 'all'. If these values are not explicitly set by the user, suitable values will be chosen. If incompatible " | ||
| + "values are set, a <code>ConfigException</code> will be thrown."; | ||
|
|
||
|
|
@@ -438,9 +440,8 @@ 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; | ||
| } | ||
|
|
||
|
|
@@ -456,33 +457,30 @@ 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 Map<String, Object> originalConfigs = this.originals(); | ||
| 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 = originalConfigs.containsKey(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 = originalConfigs.containsKey(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 = originalConfigs.containsKey(MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION); | ||
| if (userConfiguredInflightRequests && MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION_FOR_IDEMPOTENCE < this.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."); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -514,12 +512,11 @@ 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 idempotenceEnabled = this.getBoolean(ENABLE_IDEMPOTENCE_CONFIG); | ||
| 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 |
||
| } | ||
|
|
||
|
|
||
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.
We won't overriding the default
ENABLE_IDEMPOTENCE_CONFIGbecause we already default to true. No need to inform users.