Skip to content

Commit c0f674c

Browse files
authored
Throttle empty subscription poll logs (#18063) (#18070)
* Throttle empty subscription poll logs * Use i18n messages for empty poll logs # Conflicts: # iotdb-client/subscription/src/main/i18n/en/org/apache/iotdb/rpc/subscription/i18n/SubscriptionMessages.java # iotdb-client/subscription/src/main/i18n/zh/org/apache/iotdb/rpc/subscription/i18n/SubscriptionMessages.java # iotdb-client/subscription/src/main/java/org/apache/iotdb/session/subscription/consumer/EmptyPollLogThrottler.java # iotdb-client/subscription/src/main/java/org/apache/iotdb/session/subscription/consumer/base/AbstractSubscriptionPullConsumer.java
1 parent d00d476 commit c0f674c

5 files changed

Lines changed: 163 additions & 10 deletions

File tree

‎iotdb-client/subscription/pom.xml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,10 @@
7777
<groupId>org.apache.thrift</groupId>
7878
<artifactId>libthrift</artifactId>
7979
</dependency>
80+
<dependency>
81+
<groupId>junit</groupId>
82+
<artifactId>junit</artifactId>
83+
<scope>test</scope>
84+
</dependency>
8085
</dependencies>
8186
</project>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iotdb.session.subscription.consumer;
21+
22+
import java.util.OptionalLong;
23+
import java.util.concurrent.TimeUnit;
24+
import java.util.function.LongSupplier;
25+
26+
final class EmptyPollLogThrottler {
27+
28+
private static final long DEFAULT_LOG_INTERVAL_NANOS = TimeUnit.MINUTES.toNanos(1);
29+
30+
private final long logIntervalNanos;
31+
private final LongSupplier ticker;
32+
33+
private long consecutiveEmptyPollCount;
34+
private long lastLogTimeNanos;
35+
private boolean hasLoggedEmptyPoll;
36+
37+
EmptyPollLogThrottler() {
38+
this(DEFAULT_LOG_INTERVAL_NANOS, System::nanoTime);
39+
}
40+
41+
EmptyPollLogThrottler(final long logIntervalNanos, final LongSupplier ticker) {
42+
this.logIntervalNanos = Math.max(logIntervalNanos, 1);
43+
this.ticker = ticker;
44+
}
45+
46+
synchronized OptionalLong markEmptyPollAndMaybeGetCount() {
47+
consecutiveEmptyPollCount++;
48+
final long currentTimeNanos = ticker.getAsLong();
49+
if (!hasLoggedEmptyPoll || currentTimeNanos - lastLogTimeNanos >= logIntervalNanos) {
50+
hasLoggedEmptyPoll = true;
51+
lastLogTimeNanos = currentTimeNanos;
52+
return OptionalLong.of(consecutiveEmptyPollCount);
53+
}
54+
return OptionalLong.empty();
55+
}
56+
57+
synchronized void reset() {
58+
consecutiveEmptyPollCount = 0;
59+
lastLogTimeNanos = 0;
60+
hasLoggedEmptyPoll = false;
61+
}
62+
}

‎iotdb-client/subscription/src/main/java/org/apache/iotdb/session/subscription/consumer/SubscriptionPullConsumer.java‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.List;
3535
import java.util.Map;
3636
import java.util.Objects;
37+
import java.util.OptionalLong;
3738
import java.util.Properties;
3839
import java.util.Set;
3940
import java.util.SortedMap;
@@ -65,6 +66,8 @@ public class SubscriptionPullConsumer extends SubscriptionConsumer {
6566

6667
private SortedMap<Long, Set<SubscriptionCommitContext>> uncommittedCommitContexts;
6768

69+
private final EmptyPollLogThrottler emptyPollLogThrottler = new EmptyPollLogThrottler();
70+
6871
private final AtomicBoolean isClosed = new AtomicBoolean(true);
6972

7073
@Override
@@ -115,6 +118,7 @@ public synchronized void open() throws SubscriptionException {
115118

116119
// set isClosed to false before submitting workers
117120
isClosed.set(false);
121+
emptyPollLogThrottler.reset();
118122

119123
// submit auto poll worker if enabling auto commit
120124
if (autoCommit) {
@@ -181,14 +185,22 @@ public List<SubscriptionMessage> poll(final Set<String> topicNames, final long t
181185

182186
final List<SubscriptionMessage> messages = multiplePoll(parsedTopicNames, timeoutMs);
183187
if (messages.isEmpty()) {
184-
LOGGER.info(
185-
"SubscriptionPullConsumer {} poll empty message from topics {} after {} millisecond(s)",
186-
this,
187-
CollectionUtils.getLimitedString(parsedTopicNames, 32),
188-
timeoutMs);
188+
final OptionalLong consecutiveEmptyPollCount =
189+
emptyPollLogThrottler.markEmptyPollAndMaybeGetCount();
190+
if (consecutiveEmptyPollCount.isPresent()) {
191+
LOGGER.info(
192+
"SubscriptionPullConsumer {} poll empty message from topics {} after {} millisecond(s), "
193+
+ "consecutive empty polls: {}",
194+
this,
195+
CollectionUtils.getLimitedString(parsedTopicNames, 32),
196+
timeoutMs,
197+
consecutiveEmptyPollCount.getAsLong());
198+
}
189199
return messages;
190200
}
191201

202+
emptyPollLogThrottler.reset();
203+
192204
// add to uncommitted messages
193205
if (autoCommit) {
194206
final long currentTimestamp = System.currentTimeMillis();

‎iotdb-client/subscription/src/main/java/org/apache/iotdb/session/subscription/consumer/SubscriptionPushConsumer.java‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.List;
3232
import java.util.Map;
3333
import java.util.Objects;
34+
import java.util.OptionalLong;
3435
import java.util.Properties;
3536
import java.util.concurrent.ScheduledFuture;
3637
import java.util.concurrent.atomic.AtomicBoolean;
@@ -56,6 +57,8 @@ public class SubscriptionPushConsumer extends SubscriptionConsumer {
5657
private final long autoPollIntervalMs;
5758
private final long autoPollTimeoutMs;
5859

60+
private final EmptyPollLogThrottler emptyPollLogThrottler = new EmptyPollLogThrottler();
61+
5962
private final AtomicBoolean isClosed = new AtomicBoolean(true);
6063

6164
protected SubscriptionPushConsumer(final Builder builder) {
@@ -121,6 +124,7 @@ public synchronized void open() throws SubscriptionException {
121124

122125
// set isClosed to false before submitting workers
123126
isClosed.set(false);
127+
emptyPollLogThrottler.reset();
124128

125129
// submit auto poll worker
126130
submitAutoPollWorker();
@@ -176,14 +180,21 @@ public void run() {
176180
final List<SubscriptionMessage> messages =
177181
multiplePoll(subscribedTopics.keySet(), autoPollTimeoutMs);
178182
if (messages.isEmpty()) {
179-
LOGGER.info(
180-
"SubscriptionPushConsumer {} poll empty message from topics {} after {} millisecond(s)",
181-
this,
182-
CollectionUtils.getLimitedString(subscribedTopics.keySet(), 32),
183-
autoPollTimeoutMs);
183+
final OptionalLong consecutiveEmptyPollCount =
184+
emptyPollLogThrottler.markEmptyPollAndMaybeGetCount();
185+
if (consecutiveEmptyPollCount.isPresent()) {
186+
LOGGER.info(
187+
"SubscriptionPushConsumer {} poll empty message from topics {} after {} millisecond(s), "
188+
+ "consecutive empty polls: {}",
189+
SubscriptionPushConsumer.this,
190+
CollectionUtils.getLimitedString(subscribedTopics.keySet(), 32),
191+
autoPollTimeoutMs,
192+
consecutiveEmptyPollCount.getAsLong());
193+
}
184194
return;
185195
}
186196

197+
emptyPollLogThrottler.reset();
187198
if (ackStrategy.equals(AckStrategy.BEFORE_CONSUME)) {
188199
ack(messages);
189200
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.iotdb.session.subscription.consumer;
21+
22+
import org.junit.Assert;
23+
import org.junit.Test;
24+
25+
import java.util.OptionalLong;
26+
import java.util.concurrent.atomic.AtomicLong;
27+
28+
public class EmptyPollLogThrottlerTest {
29+
30+
@Test
31+
public void testThrottleConsecutiveEmptyPollLogs() {
32+
final AtomicLong ticker = new AtomicLong();
33+
final EmptyPollLogThrottler throttler = new EmptyPollLogThrottler(100, ticker::get);
34+
35+
OptionalLong logCount = throttler.markEmptyPollAndMaybeGetCount();
36+
Assert.assertTrue(logCount.isPresent());
37+
Assert.assertEquals(1, logCount.getAsLong());
38+
39+
ticker.addAndGet(99);
40+
logCount = throttler.markEmptyPollAndMaybeGetCount();
41+
Assert.assertFalse(logCount.isPresent());
42+
43+
ticker.incrementAndGet();
44+
logCount = throttler.markEmptyPollAndMaybeGetCount();
45+
Assert.assertTrue(logCount.isPresent());
46+
Assert.assertEquals(3, logCount.getAsLong());
47+
}
48+
49+
@Test
50+
public void testResetMakesNextEmptyPollLoggable() {
51+
final AtomicLong ticker = new AtomicLong();
52+
final EmptyPollLogThrottler throttler = new EmptyPollLogThrottler(100, ticker::get);
53+
54+
Assert.assertTrue(throttler.markEmptyPollAndMaybeGetCount().isPresent());
55+
Assert.assertFalse(throttler.markEmptyPollAndMaybeGetCount().isPresent());
56+
57+
throttler.reset();
58+
59+
final OptionalLong logCount = throttler.markEmptyPollAndMaybeGetCount();
60+
Assert.assertTrue(logCount.isPresent());
61+
Assert.assertEquals(1, logCount.getAsLong());
62+
}
63+
}

0 commit comments

Comments
 (0)