Commit 7f1f29877449ecedfd448c3d0f628acc1c39a8a9
Committed by
Andrew Shvayka
1 parent
a852e11a
cassandra buffered rate executor: separate beans for read and write operations
Showing
9 changed files
with
219 additions
and
87 deletions
... | ... | @@ -59,7 +59,8 @@ import org.thingsboard.server.dao.edge.EdgeEventService; |
59 | 59 | import org.thingsboard.server.dao.edge.EdgeService; |
60 | 60 | import org.thingsboard.server.dao.entityview.EntityViewService; |
61 | 61 | import org.thingsboard.server.dao.event.EventService; |
62 | -import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor; | |
62 | +import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; | |
63 | +import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; | |
63 | 64 | import org.thingsboard.server.dao.ota.OtaPackageService; |
64 | 65 | import org.thingsboard.server.dao.relation.RelationService; |
65 | 66 | import org.thingsboard.server.dao.resource.ResourceService; |
... | ... | @@ -85,7 +86,6 @@ import org.thingsboard.server.cluster.TbClusterService; |
85 | 86 | import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; |
86 | 87 | import org.thingsboard.server.service.rpc.TbRpcService; |
87 | 88 | import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; |
88 | -import org.thingsboard.server.service.script.JsExecutorService; | |
89 | 89 | import org.thingsboard.server.service.script.JsInvokeService; |
90 | 90 | import org.thingsboard.server.service.session.DeviceSessionCacheService; |
91 | 91 | import org.thingsboard.server.service.sms.SmsExecutorService; |
... | ... | @@ -421,7 +421,11 @@ public class ActorSystemContext { |
421 | 421 | |
422 | 422 | @Autowired(required = false) |
423 | 423 | @Getter |
424 | - private CassandraBufferedRateExecutor cassandraBufferedRateExecutor; | |
424 | + private CassandraBufferedRateReadExecutor cassandraBufferedRateReadExecutor; | |
425 | + | |
426 | + @Autowired(required = false) | |
427 | + @Getter | |
428 | + private CassandraBufferedRateWriteExecutor cassandraBufferedRateWriteExecutor; | |
425 | 429 | |
426 | 430 | @Autowired(required = false) |
427 | 431 | @Getter | ... | ... |
... | ... | @@ -557,8 +557,13 @@ class DefaultTbContext implements TbContext { |
557 | 557 | } |
558 | 558 | |
559 | 559 | @Override |
560 | - public TbResultSetFuture submitCassandraTask(CassandraStatementTask task) { | |
561 | - return mainCtx.getCassandraBufferedRateExecutor().submit(task); | |
560 | + public TbResultSetFuture submitCassandraReadTask(CassandraStatementTask task) { | |
561 | + return mainCtx.getCassandraBufferedRateReadExecutor().submit(task); | |
562 | + } | |
563 | + | |
564 | + @Override | |
565 | + public TbResultSetFuture submitCassandraWriteTask(CassandraStatementTask task) { | |
566 | + return mainCtx.getCassandraBufferedRateWriteExecutor().submit(task); | |
562 | 567 | } |
563 | 568 | |
564 | 569 | @Override | ... | ... |
... | ... | @@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Qualifier; |
26 | 26 | import org.thingsboard.server.common.data.id.TenantId; |
27 | 27 | import org.thingsboard.server.dao.cassandra.CassandraCluster; |
28 | 28 | import org.thingsboard.server.dao.cassandra.guava.GuavaSession; |
29 | +import org.thingsboard.server.dao.util.BufferedRateExecutor; | |
29 | 30 | |
30 | 31 | import java.util.concurrent.ConcurrentHashMap; |
31 | 32 | import java.util.concurrent.ConcurrentMap; |
... | ... | @@ -40,7 +41,10 @@ public abstract class CassandraAbstractDao { |
40 | 41 | private ConcurrentMap<String, PreparedStatement> preparedStatementMap = new ConcurrentHashMap<>(); |
41 | 42 | |
42 | 43 | @Autowired |
43 | - private CassandraBufferedRateExecutor rateLimiter; | |
44 | + private CassandraBufferedRateReadExecutor rateReadLimiter; | |
45 | + | |
46 | + @Autowired | |
47 | + private CassandraBufferedRateWriteExecutor rateWriteLimiter; | |
44 | 48 | |
45 | 49 | private GuavaSession session; |
46 | 50 | |
... | ... | @@ -61,36 +65,38 @@ public abstract class CassandraAbstractDao { |
61 | 65 | } |
62 | 66 | |
63 | 67 | protected AsyncResultSet executeRead(TenantId tenantId, Statement statement) { |
64 | - return execute(tenantId, statement, defaultReadLevel); | |
68 | + return execute(tenantId, statement, defaultReadLevel, rateReadLimiter); | |
65 | 69 | } |
66 | 70 | |
67 | 71 | protected AsyncResultSet executeWrite(TenantId tenantId, Statement statement) { |
68 | - return execute(tenantId, statement, defaultWriteLevel); | |
72 | + return execute(tenantId, statement, defaultWriteLevel, rateWriteLimiter); | |
69 | 73 | } |
70 | 74 | |
71 | 75 | protected TbResultSetFuture executeAsyncRead(TenantId tenantId, Statement statement) { |
72 | - return executeAsync(tenantId, statement, defaultReadLevel); | |
76 | + return executeAsync(tenantId, statement, defaultReadLevel, rateReadLimiter); | |
73 | 77 | } |
74 | 78 | |
75 | 79 | protected TbResultSetFuture executeAsyncWrite(TenantId tenantId, Statement statement) { |
76 | - return executeAsync(tenantId, statement, defaultWriteLevel); | |
80 | + return executeAsync(tenantId, statement, defaultWriteLevel, rateWriteLimiter); | |
77 | 81 | } |
78 | 82 | |
79 | - private AsyncResultSet execute(TenantId tenantId, Statement statement, ConsistencyLevel level) { | |
83 | + private AsyncResultSet execute(TenantId tenantId, Statement statement, ConsistencyLevel level, | |
84 | + BufferedRateExecutor<CassandraStatementTask, TbResultSetFuture> rateExecutor) { | |
80 | 85 | if (log.isDebugEnabled()) { |
81 | 86 | log.debug("Execute cassandra statement {}", statementToString(statement)); |
82 | 87 | } |
83 | - return executeAsync(tenantId, statement, level).getUninterruptibly(); | |
88 | + return executeAsync(tenantId, statement, level, rateExecutor).getUninterruptibly(); | |
84 | 89 | } |
85 | 90 | |
86 | - private TbResultSetFuture executeAsync(TenantId tenantId, Statement statement, ConsistencyLevel level) { | |
91 | + private TbResultSetFuture executeAsync(TenantId tenantId, Statement statement, ConsistencyLevel level, | |
92 | + BufferedRateExecutor<CassandraStatementTask, TbResultSetFuture> rateExecutor) { | |
87 | 93 | if (log.isDebugEnabled()) { |
88 | 94 | log.debug("Execute cassandra async statement {}", statementToString(statement)); |
89 | 95 | } |
90 | 96 | if (statement.getConsistencyLevel() == null) { |
91 | 97 | statement.setConsistencyLevel(level); |
92 | 98 | } |
93 | - return rateLimiter.submit(new CassandraStatementTask(tenantId, getSession(), statement)); | |
99 | + return rateExecutor.submit(new CassandraStatementTask(tenantId, getSession(), statement)); | |
94 | 100 | } |
95 | 101 | |
96 | 102 | private static String statementToString(Statement statement) { | ... | ... |
dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java
renamed from
dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateExecutor.java
... | ... | @@ -22,9 +22,6 @@ import org.springframework.beans.factory.annotation.Autowired; |
22 | 22 | import org.springframework.beans.factory.annotation.Value; |
23 | 23 | import org.springframework.scheduling.annotation.Scheduled; |
24 | 24 | import org.springframework.stereotype.Component; |
25 | -import org.thingsboard.server.common.data.id.TenantId; | |
26 | -import org.thingsboard.server.common.stats.DefaultCounter; | |
27 | -import org.thingsboard.server.common.stats.StatsCounter; | |
28 | 25 | import org.thingsboard.server.common.stats.StatsFactory; |
29 | 26 | import org.thingsboard.server.dao.entity.EntityService; |
30 | 27 | import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; |
... | ... | @@ -32,8 +29,6 @@ import org.thingsboard.server.dao.util.AsyncTaskContext; |
32 | 29 | import org.thingsboard.server.dao.util.NoSqlAnyDao; |
33 | 30 | |
34 | 31 | import javax.annotation.PreDestroy; |
35 | -import java.util.HashMap; | |
36 | -import java.util.Map; | |
37 | 32 | |
38 | 33 | /** |
39 | 34 | * Created by ashvayka on 24.10.18. |
... | ... | @@ -41,15 +36,11 @@ import java.util.Map; |
41 | 36 | @Component |
42 | 37 | @Slf4j |
43 | 38 | @NoSqlAnyDao |
44 | -public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor<CassandraStatementTask, TbResultSetFuture, TbResultSet> { | |
39 | +public class CassandraBufferedRateReadExecutor extends AbstractBufferedRateExecutor<CassandraStatementTask, TbResultSetFuture, TbResultSet> { | |
45 | 40 | |
46 | - @Autowired | |
47 | - private EntityService entityService; | |
48 | - private Map<TenantId, String> tenantNamesCache = new HashMap<>(); | |
41 | + static final String BUFFER_NAME = "Read"; | |
49 | 42 | |
50 | - private boolean printTenantNames; | |
51 | - | |
52 | - public CassandraBufferedRateExecutor( | |
43 | + public CassandraBufferedRateReadExecutor( | |
53 | 44 | @Value("${cassandra.query.buffer_size}") int queueLimit, |
54 | 45 | @Value("${cassandra.query.concurrent_limit}") int concurrencyLimit, |
55 | 46 | @Value("${cassandra.query.permit_max_wait_time}") long maxWaitTime, |
... | ... | @@ -60,57 +51,16 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor< |
60 | 51 | @Value("${cassandra.query.tenant_rate_limits.configuration}") String tenantRateLimitsConfiguration, |
61 | 52 | @Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames, |
62 | 53 | @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq, |
63 | - @Autowired StatsFactory statsFactory) { | |
64 | - super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, tenantRateLimitsEnabled, tenantRateLimitsConfiguration, printQueriesFreq, statsFactory); | |
65 | - this.printTenantNames = printTenantNames; | |
54 | + @Autowired StatsFactory statsFactory, | |
55 | + @Autowired EntityService entityService) { | |
56 | + super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, tenantRateLimitsEnabled, tenantRateLimitsConfiguration, printQueriesFreq, statsFactory, | |
57 | + entityService, printTenantNames); | |
66 | 58 | } |
67 | 59 | |
68 | 60 | @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") |
61 | + @Override | |
69 | 62 | public void printStats() { |
70 | - int queueSize = getQueueSize(); | |
71 | - int rateLimitedTenantsCount = (int) stats.getRateLimitedTenants().values().stream() | |
72 | - .filter(defaultCounter -> defaultCounter.get() > 0) | |
73 | - .count(); | |
74 | - | |
75 | - if (queueSize > 0 | |
76 | - || rateLimitedTenantsCount > 0 | |
77 | - || concurrencyLevel.get() > 0 | |
78 | - || stats.getStatsCounters().stream().anyMatch(counter -> counter.get() > 0) | |
79 | - ) { | |
80 | - StringBuilder statsBuilder = new StringBuilder(); | |
81 | - | |
82 | - statsBuilder.append("queueSize").append(" = [").append(queueSize).append("] "); | |
83 | - stats.getStatsCounters().forEach(counter -> { | |
84 | - statsBuilder.append(counter.getName()).append(" = [").append(counter.get()).append("] "); | |
85 | - }); | |
86 | - statsBuilder.append("totalRateLimitedTenants").append(" = [").append(rateLimitedTenantsCount).append("] "); | |
87 | - statsBuilder.append(CONCURRENCY_LEVEL).append(" = [").append(concurrencyLevel.get()).append("] "); | |
88 | - | |
89 | - stats.getStatsCounters().forEach(StatsCounter::clear); | |
90 | - log.info("Permits {}", statsBuilder); | |
91 | - } | |
92 | - | |
93 | - stats.getRateLimitedTenants().entrySet().stream() | |
94 | - .filter(entry -> entry.getValue().get() > 0) | |
95 | - .forEach(entry -> { | |
96 | - TenantId tenantId = entry.getKey(); | |
97 | - DefaultCounter counter = entry.getValue(); | |
98 | - int rateLimitedRequests = counter.get(); | |
99 | - counter.clear(); | |
100 | - if (printTenantNames) { | |
101 | - String name = tenantNamesCache.computeIfAbsent(tenantId, tId -> { | |
102 | - try { | |
103 | - return entityService.fetchEntityNameAsync(TenantId.SYS_TENANT_ID, tenantId).get(); | |
104 | - } catch (Exception e) { | |
105 | - log.error("[{}] Failed to get tenant name", tenantId, e); | |
106 | - return "N/A"; | |
107 | - } | |
108 | - }); | |
109 | - log.info("[{}][{}] Rate limited requests: {}", tenantId, name, rateLimitedRequests); | |
110 | - } else { | |
111 | - log.info("[{}] Rate limited requests: {}", tenantId, rateLimitedRequests); | |
112 | - } | |
113 | - }); | |
63 | + super.printStats(); | |
114 | 64 | } |
115 | 65 | |
116 | 66 | @PreDestroy |
... | ... | @@ -119,6 +69,11 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor< |
119 | 69 | } |
120 | 70 | |
121 | 71 | @Override |
72 | + public String getBufferName() { | |
73 | + return BUFFER_NAME; | |
74 | + } | |
75 | + | |
76 | + @Override | |
122 | 77 | protected SettableFuture<TbResultSet> create() { |
123 | 78 | return SettableFuture.create(); |
124 | 79 | } |
... | ... | @@ -133,7 +88,7 @@ public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor< |
133 | 88 | CassandraStatementTask task = taskCtx.getTask(); |
134 | 89 | return task.executeAsync( |
135 | 90 | statement -> |
136 | - this.submit(new CassandraStatementTask(task.getTenantId(), task.getSession(), statement)) | |
91 | + this.submit(new CassandraStatementTask(task.getTenantId(), task.getSession(), statement)) | |
137 | 92 | ); |
138 | 93 | } |
139 | 94 | ... | ... |
dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.dao.nosql; | |
17 | + | |
18 | +import com.google.common.util.concurrent.ListenableFuture; | |
19 | +import com.google.common.util.concurrent.SettableFuture; | |
20 | +import lombok.extern.slf4j.Slf4j; | |
21 | +import org.springframework.beans.factory.annotation.Autowired; | |
22 | +import org.springframework.beans.factory.annotation.Value; | |
23 | +import org.springframework.scheduling.annotation.Scheduled; | |
24 | +import org.springframework.stereotype.Component; | |
25 | +import org.thingsboard.server.common.stats.StatsFactory; | |
26 | +import org.thingsboard.server.dao.entity.EntityService; | |
27 | +import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; | |
28 | +import org.thingsboard.server.dao.util.AsyncTaskContext; | |
29 | +import org.thingsboard.server.dao.util.NoSqlAnyDao; | |
30 | + | |
31 | +import javax.annotation.PreDestroy; | |
32 | + | |
33 | +/** | |
34 | + * Created by ashvayka on 24.10.18. | |
35 | + */ | |
36 | +@Component | |
37 | +@Slf4j | |
38 | +@NoSqlAnyDao | |
39 | +public class CassandraBufferedRateWriteExecutor extends AbstractBufferedRateExecutor<CassandraStatementTask, TbResultSetFuture, TbResultSet> { | |
40 | + | |
41 | + static final String BUFFER_NAME = "Write"; | |
42 | + | |
43 | + public CassandraBufferedRateWriteExecutor( | |
44 | + @Value("${cassandra.query.buffer_size}") int queueLimit, | |
45 | + @Value("${cassandra.query.concurrent_limit}") int concurrencyLimit, | |
46 | + @Value("${cassandra.query.permit_max_wait_time}") long maxWaitTime, | |
47 | + @Value("${cassandra.query.dispatcher_threads:2}") int dispatcherThreads, | |
48 | + @Value("${cassandra.query.callback_threads:4}") int callbackThreads, | |
49 | + @Value("${cassandra.query.poll_ms:50}") long pollMs, | |
50 | + @Value("${cassandra.query.tenant_rate_limits.enabled}") boolean tenantRateLimitsEnabled, | |
51 | + @Value("${cassandra.query.tenant_rate_limits.configuration}") String tenantRateLimitsConfiguration, | |
52 | + @Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames, | |
53 | + @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq, | |
54 | + @Autowired StatsFactory statsFactory, | |
55 | + @Autowired EntityService entityService) { | |
56 | + super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, tenantRateLimitsEnabled, tenantRateLimitsConfiguration, printQueriesFreq, statsFactory, | |
57 | + entityService, printTenantNames); | |
58 | + } | |
59 | + | |
60 | + @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") | |
61 | + @Override | |
62 | + public void printStats() { | |
63 | + super.printStats(); | |
64 | + } | |
65 | + | |
66 | + @PreDestroy | |
67 | + public void stop() { | |
68 | + super.stop(); | |
69 | + } | |
70 | + | |
71 | + @Override | |
72 | + public String getBufferName() { | |
73 | + return BUFFER_NAME; | |
74 | + } | |
75 | + | |
76 | + @Override | |
77 | + protected SettableFuture<TbResultSet> create() { | |
78 | + return SettableFuture.create(); | |
79 | + } | |
80 | + | |
81 | + @Override | |
82 | + protected TbResultSetFuture wrap(CassandraStatementTask task, SettableFuture<TbResultSet> future) { | |
83 | + return new TbResultSetFuture(future); | |
84 | + } | |
85 | + | |
86 | + @Override | |
87 | + protected ListenableFuture<TbResultSet> execute(AsyncTaskContext<CassandraStatementTask, TbResultSet> taskCtx) { | |
88 | + CassandraStatementTask task = taskCtx.getTask(); | |
89 | + return task.executeAsync( | |
90 | + statement -> | |
91 | + this.submit(new CassandraStatementTask(task.getTenantId(), task.getSession(), statement)) | |
92 | + ); | |
93 | + } | |
94 | + | |
95 | +} | ... | ... |
... | ... | @@ -31,12 +31,17 @@ import lombok.extern.slf4j.Slf4j; |
31 | 31 | import org.thingsboard.common.util.ThingsBoardExecutors; |
32 | 32 | import org.thingsboard.common.util.ThingsBoardThreadFactory; |
33 | 33 | import org.thingsboard.server.common.data.id.TenantId; |
34 | +import org.thingsboard.server.common.msg.tools.TbRateLimits; | |
35 | +import org.thingsboard.server.common.stats.DefaultCounter; | |
36 | +import org.thingsboard.server.common.stats.StatsCounter; | |
34 | 37 | import org.thingsboard.server.common.stats.StatsFactory; |
35 | 38 | import org.thingsboard.server.common.stats.StatsType; |
36 | -import org.thingsboard.server.common.msg.tools.TbRateLimits; | |
39 | +import org.thingsboard.server.dao.entity.EntityService; | |
37 | 40 | import org.thingsboard.server.dao.nosql.CassandraStatementTask; |
38 | 41 | |
39 | 42 | import javax.annotation.Nullable; |
43 | +import java.util.HashMap; | |
44 | +import java.util.Map; | |
40 | 45 | import java.util.UUID; |
41 | 46 | import java.util.concurrent.BlockingQueue; |
42 | 47 | import java.util.concurrent.ConcurrentHashMap; |
... | ... | @@ -75,22 +80,32 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend |
75 | 80 | protected final AtomicInteger concurrencyLevel; |
76 | 81 | protected final BufferedRateExecutorStats stats; |
77 | 82 | |
83 | + | |
84 | + private final EntityService entityService; | |
85 | + private final Map<TenantId, String> tenantNamesCache = new HashMap<>(); | |
86 | + | |
87 | + private final boolean printTenantNames; | |
88 | + | |
78 | 89 | public AbstractBufferedRateExecutor(int queueLimit, int concurrencyLimit, long maxWaitTime, int dispatcherThreads, int callbackThreads, long pollMs, |
79 | - boolean perTenantLimitsEnabled, String perTenantLimitsConfiguration, int printQueriesFreq, StatsFactory statsFactory) { | |
90 | + boolean perTenantLimitsEnabled, String perTenantLimitsConfiguration, int printQueriesFreq, StatsFactory statsFactory, | |
91 | + EntityService entityService, boolean printTenantNames) { | |
80 | 92 | this.maxWaitTime = maxWaitTime; |
81 | 93 | this.pollMs = pollMs; |
82 | 94 | this.concurrencyLimit = concurrencyLimit; |
83 | 95 | this.printQueriesFreq = printQueriesFreq; |
84 | 96 | this.queue = new LinkedBlockingDeque<>(queueLimit); |
85 | - this.dispatcherExecutor = Executors.newFixedThreadPool(dispatcherThreads, ThingsBoardThreadFactory.forName("nosql-dispatcher")); | |
86 | - this.callbackExecutor = ThingsBoardExecutors.newWorkStealingPool(callbackThreads, getClass()); | |
87 | - this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nosql-timeout")); | |
97 | + this.dispatcherExecutor = Executors.newFixedThreadPool(dispatcherThreads, ThingsBoardThreadFactory.forName("nosql-" + getBufferName() + "-dispatcher")); | |
98 | + this.callbackExecutor = ThingsBoardExecutors.newWorkStealingPool(callbackThreads, "nosql-" + getBufferName() + "-callback"); | |
99 | + this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nosql-" + getBufferName() + "-timeout")); | |
88 | 100 | this.perTenantLimitsEnabled = perTenantLimitsEnabled; |
89 | 101 | this.perTenantLimitsConfiguration = perTenantLimitsConfiguration; |
90 | 102 | this.stats = new BufferedRateExecutorStats(statsFactory); |
91 | - String concurrencyLevelKey = StatsType.RATE_EXECUTOR.getName() + "." + CONCURRENCY_LEVEL; | |
103 | + String concurrencyLevelKey = StatsType.RATE_EXECUTOR.getName() + "." + CONCURRENCY_LEVEL + getBufferName(); //metric name may change with buffer name suffix | |
92 | 104 | this.concurrencyLevel = statsFactory.createGauge(concurrencyLevelKey, new AtomicInteger(0)); |
93 | 105 | |
106 | + this.entityService = entityService; | |
107 | + this.printTenantNames = printTenantNames; | |
108 | + | |
94 | 109 | for (int i = 0; i < dispatcherThreads; i++) { |
95 | 110 | dispatcherExecutor.submit(this::dispatch); |
96 | 111 | } |
... | ... | @@ -144,6 +159,8 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend |
144 | 159 | |
145 | 160 | protected abstract ListenableFuture<V> execute(AsyncTaskContext<T, V> taskCtx); |
146 | 161 | |
162 | + public abstract String getBufferName(); | |
163 | + | |
147 | 164 | private void dispatch() { |
148 | 165 | log.info("Buffered rate executor thread started"); |
149 | 166 | while (!Thread.interrupted()) { |
... | ... | @@ -264,4 +281,51 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend |
264 | 281 | protected int getQueueSize() { |
265 | 282 | return queue.size(); |
266 | 283 | } |
284 | + | |
285 | + public void printStats() { | |
286 | + int queueSize = getQueueSize(); | |
287 | + int rateLimitedTenantsCount = (int) stats.getRateLimitedTenants().values().stream() | |
288 | + .filter(defaultCounter -> defaultCounter.get() > 0) | |
289 | + .count(); | |
290 | + | |
291 | + if (queueSize > 0 | |
292 | + || rateLimitedTenantsCount > 0 | |
293 | + || concurrencyLevel.get() > 0 | |
294 | + || stats.getStatsCounters().stream().anyMatch(counter -> counter.get() > 0) | |
295 | + ) { | |
296 | + StringBuilder statsBuilder = new StringBuilder(); | |
297 | + | |
298 | + statsBuilder.append("queueSize").append(" = [").append(queueSize).append("] "); | |
299 | + stats.getStatsCounters().forEach(counter -> { | |
300 | + statsBuilder.append(counter.getName()).append(" = [").append(counter.get()).append("] "); | |
301 | + }); | |
302 | + statsBuilder.append("totalRateLimitedTenants").append(" = [").append(rateLimitedTenantsCount).append("] "); | |
303 | + statsBuilder.append(CONCURRENCY_LEVEL).append(" = [").append(concurrencyLevel.get()).append("] "); | |
304 | + | |
305 | + stats.getStatsCounters().forEach(StatsCounter::clear); | |
306 | + log.info("Permits {}", statsBuilder); | |
307 | + } | |
308 | + | |
309 | + stats.getRateLimitedTenants().entrySet().stream() | |
310 | + .filter(entry -> entry.getValue().get() > 0) | |
311 | + .forEach(entry -> { | |
312 | + TenantId tenantId = entry.getKey(); | |
313 | + DefaultCounter counter = entry.getValue(); | |
314 | + int rateLimitedRequests = counter.get(); | |
315 | + counter.clear(); | |
316 | + if (printTenantNames) { | |
317 | + String name = tenantNamesCache.computeIfAbsent(tenantId, tId -> { | |
318 | + try { | |
319 | + return entityService.fetchEntityNameAsync(TenantId.SYS_TENANT_ID, tenantId).get(); | |
320 | + } catch (Exception e) { | |
321 | + log.error("[{}] Failed to get tenant name", tenantId, e); | |
322 | + return "N/A"; | |
323 | + } | |
324 | + }); | |
325 | + log.info("[{}][{}] Rate limited requests: {}", tenantId, name, rateLimitedRequests); | |
326 | + } else { | |
327 | + log.info("[{}] Rate limited requests: {}", tenantId, rateLimitedRequests); | |
328 | + } | |
329 | + }); | |
330 | + } | |
267 | 331 | } | ... | ... |
... | ... | @@ -20,6 +20,7 @@ import com.datastax.oss.driver.api.core.cql.BoundStatement; |
20 | 20 | import com.datastax.oss.driver.api.core.cql.PreparedStatement; |
21 | 21 | import com.datastax.oss.driver.api.core.cql.Statement; |
22 | 22 | import com.google.common.util.concurrent.Futures; |
23 | +import com.google.common.util.concurrent.SettableFuture; | |
23 | 24 | import org.junit.Before; |
24 | 25 | import org.junit.Test; |
25 | 26 | import org.junit.runner.RunWith; |
... | ... | @@ -38,6 +39,7 @@ import java.util.UUID; |
38 | 39 | import static org.mockito.ArgumentMatchers.any; |
39 | 40 | import static org.mockito.ArgumentMatchers.anyInt; |
40 | 41 | import static org.mockito.ArgumentMatchers.anyString; |
42 | +import static org.mockito.BDDMockito.willReturn; | |
41 | 43 | import static org.mockito.Mockito.doReturn; |
42 | 44 | import static org.mockito.Mockito.times; |
43 | 45 | import static org.mockito.Mockito.verify; |
... | ... | @@ -59,9 +61,6 @@ public class CassandraPartitionsCacheTest { |
59 | 61 | private Environment environment; |
60 | 62 | |
61 | 63 | @Mock |
62 | - private CassandraBufferedRateExecutor rateLimiter; | |
63 | - | |
64 | - @Mock | |
65 | 64 | private CassandraCluster cluster; |
66 | 65 | |
67 | 66 | @Mock |
... | ... | @@ -74,7 +73,6 @@ public class CassandraPartitionsCacheTest { |
74 | 73 | ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "systemTtl", 0); |
75 | 74 | ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "setNullValuesEnabled", false); |
76 | 75 | ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "environment", environment); |
77 | - ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "rateLimiter", rateLimiter); | |
78 | 76 | ReflectionTestUtils.setField(cassandraBaseTimeseriesDao, "cluster", cluster); |
79 | 77 | |
80 | 78 | when(cluster.getDefaultReadConsistencyLevel()).thenReturn(ConsistencyLevel.ONE); |
... | ... | @@ -88,7 +86,9 @@ public class CassandraPartitionsCacheTest { |
88 | 86 | when(boundStatement.setUuid(anyInt(), any(UUID.class))).thenReturn(boundStatement); |
89 | 87 | when(boundStatement.setLong(anyInt(), any(Long.class))).thenReturn(boundStatement); |
90 | 88 | |
91 | - doReturn(Futures.immediateFuture(0)).when(cassandraBaseTimeseriesDao).getFuture(any(TbResultSetFuture.class), any()); | |
89 | + willReturn(new TbResultSetFuture(SettableFuture.create())).given(cassandraBaseTimeseriesDao).executeAsyncWrite(any(), any()); | |
90 | + | |
91 | + doReturn(Futures.immediateFuture(0)).when(cassandraBaseTimeseriesDao).getFuture(any(), any()); | |
92 | 92 | } |
93 | 93 | |
94 | 94 | @Test |
... | ... | @@ -107,4 +107,5 @@ public class CassandraPartitionsCacheTest { |
107 | 107 | } |
108 | 108 | verify(cassandraBaseTimeseriesDao, times(60000)).executeAsyncWrite(any(TenantId.class), any(Statement.class)); |
109 | 109 | } |
110 | + | |
110 | 111 | } | ... | ... |
... | ... | @@ -245,7 +245,9 @@ public interface TbContext { |
245 | 245 | |
246 | 246 | CassandraCluster getCassandraCluster(); |
247 | 247 | |
248 | - TbResultSetFuture submitCassandraTask(CassandraStatementTask task); | |
248 | + TbResultSetFuture submitCassandraReadTask(CassandraStatementTask task); | |
249 | + | |
250 | + TbResultSetFuture submitCassandraWriteTask(CassandraStatementTask task); | |
249 | 251 | |
250 | 252 | PageData<RuleNodeState> findRuleNodeStates(PageLink pageLink); |
251 | 253 | ... | ... |
... | ... | @@ -213,7 +213,7 @@ public class TbSaveToCustomCassandraTableNode implements TbNode { |
213 | 213 | if (statement.getConsistencyLevel() == null) { |
214 | 214 | statement.setConsistencyLevel(level); |
215 | 215 | } |
216 | - return ctx.submitCassandraTask(new CassandraStatementTask(ctx.getTenantId(), getSession(), statement)); | |
216 | + return ctx.submitCassandraWriteTask(new CassandraStatementTask(ctx.getTenantId(), getSession(), statement)); | |
217 | 217 | } |
218 | 218 | |
219 | 219 | private static String statementToString(Statement statement) { | ... | ... |