This is an automated email from the ASF dual-hosted git repository.
JackieTien97 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new 142d3b14fce Pipe: Cache table-model pattern matches by table (#18648)
142d3b14fce is described below
commit 142d3b14fce9d2dc7787777b52ddb2a1a1a1a5d2
Author: shuwenwei <[email protected]>
AuthorDate: Mon Sep 21 12:08:53 2026 +0800
Pipe: Cache table-model pattern matches by table (#18648)
---
.../org/apache/iotdb/db/auth/AuthorityChecker.java | 6 +-
.../matcher/CachedSchemaPatternMatcher.java | 41 ++++++-----
.../pattern/CachedSchemaPatternMatcherTest.java | 83 ++++++++++++++++++++++
3 files changed, 110 insertions(+), 20 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
index 0593db10840..951444d2028 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java
@@ -131,13 +131,15 @@ public class AuthorityChecker {
}
public static boolean invalidateCache(String username, String roleName) {
+ final boolean invalidated =
+ authorityFetcher.get().getAuthorCache().invalidateCache(username,
roleName);
PipeInsertionDataNodeListener.getInstance().invalidateAllCache();
- return authorityFetcher.get().getAuthorCache().invalidateCache(username,
roleName);
+ return invalidated;
}
public static void invalidateAllCache() {
- PipeInsertionDataNodeListener.getInstance().invalidateAllCache();
authorityFetcher.get().getAuthorCache().invalidAllCache();
+ PipeInsertionDataNodeListener.getInstance().invalidateAllCache();
}
public static User getUser(String username) {
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/matcher/CachedSchemaPatternMatcher.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/matcher/CachedSchemaPatternMatcher.java
index e9d2e98c5d0..d8f427868f0 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/matcher/CachedSchemaPatternMatcher.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/matcher/CachedSchemaPatternMatcher.java
@@ -61,7 +61,7 @@ public class CachedSchemaPatternMatcher implements
PipeDataRegionMatcher {
// Use full cache to avoid queue stuck and block insertion
protected final Map<IDeviceID, Set<PipeRealtimeDataRegionSource>>
deviceToSourcesCache;
- protected final Map<Pair<String, IDeviceID>,
Set<PipeRealtimeDataRegionSource>>
+ protected final Map<Pair<String, String>, Set<PipeRealtimeDataRegionSource>>
databaseAndTableToSourcesCache;
public CachedSchemaPatternMatcher() {
@@ -102,7 +102,7 @@ public class CachedSchemaPatternMatcher implements
PipeDataRegionMatcher {
public void invalidateCache() {
lock.writeLock().lock();
try {
- // Will invalidate device cache
+ // The table-model cache also depends on access control, so it must be
invalidated separately.
databaseAndTableToSourcesCache.clear();
} finally {
lock.writeLock().unlock();
@@ -144,6 +144,11 @@ public class CachedSchemaPatternMatcher implements
PipeDataRegionMatcher {
return new Pair<>(matchedSources,
findUnmatchedSources(matchedSources));
}
+ // tableNames is also used for privilege checks on table-model TsFile
events, so it must be
+ // complete even after every source has already matched.
+ final boolean isTableModelTsFileEvent =
+ event.getEvent() instanceof PipeTsFileInsertionEvent
+ && ((PipeTsFileInsertionEvent)
event.getEvent()).isTableModelEvent();
final Set<String> tableNames = new HashSet<>();
for (final Map.Entry<IDeviceID, String[]> entry :
event.getSchemaInfo().entrySet()) {
final IDeviceID deviceID = entry.getKey();
@@ -154,16 +159,17 @@ public class CachedSchemaPatternMatcher implements
PipeDataRegionMatcher {
|| deviceID.getTableName().equals(PATH_ROOT)) {
matchTreeModelEvent(deviceID, entry.getValue(), matchedSources);
} else {
- tableNames.add(deviceID.getTableName());
- matchTableModelEvent(
- event.getEvent() instanceof PipeInsertionEvent
- ? ((PipeInsertionEvent)
event.getEvent()).getTableModelDatabaseName()
- : null,
- deviceID,
- matchedSources);
+ final String tableName = deviceID.getTableName();
+ if (tableNames.add(tableName) && matchedSources.size() <
sources.size()) {
+ final String tableModelDatabaseName =
+ event.getEvent() instanceof PipeInsertionEvent
+ ? ((PipeInsertionEvent)
event.getEvent()).getTableModelDatabaseName()
+ : null;
+ matchTableModelEvent(tableModelDatabaseName, tableName,
matchedSources);
+ }
}
- if (matchedSources.size() == sources.size()) {
+ if (matchedSources.size() == sources.size() &&
!isTableModelTsFileEvent) {
break;
}
}
@@ -171,7 +177,7 @@ public class CachedSchemaPatternMatcher implements
PipeDataRegionMatcher {
if (event.getEvent() instanceof PipeTsFileInsertionEvent) {
final PipeTsFileInsertionEvent tsFileInsertionEvent =
(PipeTsFileInsertionEvent) event.getEvent();
- if (tsFileInsertionEvent.isTableModelEvent()) {
+ if (isTableModelTsFileEvent) {
tsFileInsertionEvent.setTableNames(tableNames);
} else {
tsFileInsertionEvent.setTreeSchemaMap(event.getSchemaInfo());
@@ -273,7 +279,7 @@ public class CachedSchemaPatternMatcher implements
PipeDataRegionMatcher {
protected void matchTableModelEvent(
final String databaseName,
- final IDeviceID tableName,
+ final String tableName,
final Set<PipeRealtimeDataRegionSource> matchedSources) {
// this would not happen
if (databaseName == null) {
@@ -294,7 +300,7 @@ public class CachedSchemaPatternMatcher implements
PipeDataRegionMatcher {
}
protected Set<PipeRealtimeDataRegionSource> filterSourcesByDatabaseAndTable(
- final Pair<String, IDeviceID> databaseNameAndTableName) {
+ final Pair<String, String> databaseNameAndTableName) {
final Set<PipeRealtimeDataRegionSource> filteredSources = new HashSet<>();
for (final PipeRealtimeDataRegionSource source : sources) {
@@ -317,21 +323,20 @@ public class CachedSchemaPatternMatcher implements
PipeDataRegionMatcher {
}
private boolean matchesTablePattern(
- final TablePattern tablePattern, final Pair<String, IDeviceID>
databaseNameAndTableName) {
+ final TablePattern tablePattern, final Pair<String, String>
databaseNameAndTableName) {
return Objects.isNull(tablePattern)
|| (tablePattern.isTableModelDataAllowedToBeCaptured()
&& tablePattern.matchesDatabase(databaseNameAndTableName.getLeft())
- &&
tablePattern.matchesTable(databaseNameAndTableName.getRight().getTableName()));
+ && tablePattern.matchesTable(databaseNameAndTableName.getRight()));
}
private boolean notFilteredByAccess(
- final UserEntity userEntity, final Pair<String, IDeviceID>
databaseNameAndTableName) {
+ final UserEntity userEntity, final Pair<String, String>
databaseNameAndTableName) {
return AuthorityChecker.getAccessControl()
.checkCanSelectFromTable4Pipe(
userEntity.getUsername(),
new QualifiedObjectName(
- databaseNameAndTableName.getLeft(),
- databaseNameAndTableName.getRight().getTableName()),
+ databaseNameAndTableName.getLeft(),
databaseNameAndTableName.getRight()),
userEntity);
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/pattern/CachedSchemaPatternMatcherTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/pattern/CachedSchemaPatternMatcherTest.java
index 61d5232a3d8..832bae39352 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/pattern/CachedSchemaPatternMatcherTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/pattern/CachedSchemaPatternMatcherTest.java
@@ -25,6 +25,8 @@ import
org.apache.iotdb.commons.pipe.config.plugin.env.PipeTaskSourceRuntimeEnvi
import org.apache.iotdb.commons.pipe.datastructure.pattern.PrefixTreePattern;
import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
+import org.apache.iotdb.db.pipe.event.common.PipeInsertionEvent;
+import org.apache.iotdb.db.pipe.event.common.tsfile.PipeTsFileInsertionEvent;
import org.apache.iotdb.db.pipe.event.realtime.PipeRealtimeEvent;
import
org.apache.iotdb.db.pipe.source.dataregion.realtime.PipeRealtimeDataRegionSource;
import org.apache.iotdb.db.pipe.source.dataregion.realtime.epoch.TsFileEpoch;
@@ -39,12 +41,18 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -73,6 +81,35 @@ public class CachedSchemaPatternMatcherTest {
}
}
+ private static class CountingCachedSchemaPatternMatcher extends
CachedSchemaPatternMatcher {
+
+ private final int sourcesToAddOnEachMatch;
+ private int tableMatchCount;
+
+ private CountingCachedSchemaPatternMatcher(final int
sourcesToAddOnEachMatch) {
+ this.sourcesToAddOnEachMatch = sourcesToAddOnEachMatch;
+ }
+
+ @Override
+ protected void matchTableModelEvent(
+ final String databaseName,
+ final String tableName,
+ final Set<PipeRealtimeDataRegionSource> matchedSources) {
+ ++tableMatchCount;
+ int addedSourceCount = 0;
+ for (final PipeRealtimeDataRegionSource source : sources) {
+ matchedSources.add(source);
+ if (++addedSourceCount >= sourcesToAddOnEachMatch) {
+ break;
+ }
+ }
+ }
+
+ private int getTableMatchCount() {
+ return tableMatchCount;
+ }
+ }
+
private CachedSchemaPatternMatcher matcher;
private ExecutorService executorService;
private List<PipeRealtimeDataRegionSource> extractors;
@@ -178,6 +215,52 @@ public class CachedSchemaPatternMatcherTest {
future.get();
}
+ @Test
+ public void testTableModelMatchesEachTableOncePerEvent() throws Exception {
+ final CountingCachedSchemaPatternMatcher countingMatcher =
+ new CountingCachedSchemaPatternMatcher(1);
+ countingMatcher.register(new PipeRealtimeDataRegionFakeSource());
+ countingMatcher.register(new PipeRealtimeDataRegionFakeSource());
+
+ final PipeInsertionEvent insertionEvent =
Mockito.mock(PipeInsertionEvent.class);
+ Mockito.when(insertionEvent.getTableModelDatabaseName()).thenReturn("db");
+ final Map<IDeviceID, String[]> schemaInfo = new LinkedHashMap<>();
+ schemaInfo.put(new StringArrayDeviceID("table1", "tag1"), new String[0]);
+ schemaInfo.put(new StringArrayDeviceID("table1", "tag2"), new String[0]);
+
+ Assert.assertEquals(
+ 1,
+ countingMatcher
+ .match(new MockedPipeRealtimeEvent(insertionEvent, null,
schemaInfo))
+ .getLeft()
+ .size());
+ Assert.assertEquals(1, countingMatcher.getTableMatchCount());
+ }
+
+ @Test
+ public void
testMultiTableTsFileCollectsAllTableNamesAfterAllSourcesMatched() throws
Exception {
+ final CountingCachedSchemaPatternMatcher countingMatcher =
+ new CountingCachedSchemaPatternMatcher(Integer.MAX_VALUE);
+ countingMatcher.register(new PipeRealtimeDataRegionFakeSource());
+ countingMatcher.register(new PipeRealtimeDataRegionFakeSource());
+
+ final PipeTsFileInsertionEvent tsFileInsertionEvent =
+ Mockito.mock(PipeTsFileInsertionEvent.class);
+ Mockito.when(tsFileInsertionEvent.isTableModelEvent()).thenReturn(true);
+
Mockito.when(tsFileInsertionEvent.getTableModelDatabaseName()).thenReturn("db");
+ final Map<IDeviceID, String[]> schemaInfo = new LinkedHashMap<>();
+ schemaInfo.put(new StringArrayDeviceID("table1", "tag1"), new String[0]);
+ schemaInfo.put(new StringArrayDeviceID("table2", "tag2"), new String[0]);
+
+ countingMatcher.match(new MockedPipeRealtimeEvent(tsFileInsertionEvent,
null, schemaInfo));
+
+ final ArgumentCaptor<Set<String>> tableNamesCaptor =
ArgumentCaptor.forClass(Set.class);
+
Mockito.verify(tsFileInsertionEvent).setTableNames(tableNamesCaptor.capture());
+ Assert.assertEquals(
+ new HashSet<>(Arrays.asList("table1", "table2")),
tableNamesCaptor.getValue());
+ Assert.assertEquals(1, countingMatcher.getTableMatchCount());
+ }
+
public static class PipeRealtimeDataRegionFakeSource extends
PipeRealtimeDataRegionSource {
public PipeRealtimeDataRegionFakeSource() {