-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Pipe: Cache table-model pattern matches by table #18648
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
base: master
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -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>> | ||
|
Collaborator
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. [P2] Prevent stale authorization results from refilling this cache
Please invalidate the authority cache before this matcher (also in |
||
| databaseAndTableToSourcesCache; | ||
|
|
||
| public CachedSchemaPatternMatcher() { | ||
|
|
@@ -102,7 +102,7 @@ public void deregister(final PipeRealtimeDataRegionSource source) { | |
| 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 Pair<Set<PipeRealtimeDataRegionSource>, Set<PipeRealtimeDataRegionSource> | |
| 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,24 +159,25 @@ public Pair<Set<PipeRealtimeDataRegionSource>, Set<PipeRealtimeDataRegionSource> | |
| || 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; | ||
| } | ||
| } | ||
|
|
||
| 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 @@ protected Set<PipeRealtimeDataRegionSource> filterSourcesByDevice(final IDeviceI | |
|
|
||
| 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 @@ protected void matchTableModelEvent( | |
| } | ||
|
|
||
| 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 @@ protected Set<PipeRealtimeDataRegionSource> filterSourcesByDatabaseAndTable( | |
| } | ||
|
|
||
| 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); | ||
| } | ||
|
|
||
|
|
||
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.
[P1] Guard authority-cache refills with an invalidation generation
Reordering these invalidations only removes matcher entries produced by an in-flight match; it does not prevent the underlying authority cache from being refilled after its invalidation. A matcher miss holds the matcher read lock while
checkCanSelectFromTable4Pipe()may issue a ConfigNode RPC. If a pre-revocation successful response returns after the author cache is cleared,ClusterAuthorityFetcher.checkPrivilegeFromConfigNode()can callputUserCache()with the oldUserwhile this thread is waiting for the matcher write lock. The following matcher invalidation then clears only the matcher entry, leaving the stale authority entry behind; the next event repopulates the matcher and a busy pipe can continue passing a revoked user.Please add a generation/epoch to authority-cache loads (capture it before the RPC and only install the response if unchanged), or otherwise coordinate refills atomically with both invalidations. A latch-based test for this exact interleaving would prevent regression.
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.
Thanks for the detailed analysis. We understand the concern about authority-cache refill after invalidation.
This refill race is pre-existing and orthogonal to this PR: the unconditional putUserCache() path in ClusterAuthorityFetcher / BasicAuthorityCache is unchanged, and this PR is scoped to table-level matcher caching and its invalidation behavior. We do not plan to expand this PR into an authority-cache generation/epoch redesign.
If the maintainers consider it necessary, a separate PR can be opened later to handle authority-cache refill coordination and add the latch-based interleaving test there. For this PR, we would prefer to keep the matcher-related invalidation ordering fix and focused matcher tests.