yuqi1129 commented on code in PR #8297:
URL: https://github.com/apache/gravitino/pull/8297#discussion_r2315808923


##########
core/src/main/java/org/apache/gravitino/cache/ReverseIndexCache.java:
##########
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.cache;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.googlecode.concurrenttrees.radix.ConcurrentRadixTree;
+import com.googlecode.concurrenttrees.radix.RadixTree;
+import 
com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.meta.GroupEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.meta.UserEntity;
+
+/**
+ * Reverse index cache for managing entity relationships. This cache uses a 
radix tree to
+ * efficiently store and retrieve relationships between entities based on 
their keys.
+ */
+public class ReverseIndexCache {
+  private RadixTree<EntityCacheKey> reverseIndex;
+  /** Registers a reverse index processor for a specific entity class. */
+  private final Map<Class<? extends Entity>, ReverseIndexRule> 
reverseIndexRules = new HashMap<>();
+
+  public ReverseIndexCache() {
+    this.reverseIndex = new ConcurrentRadixTree<>(new 
DefaultCharArrayNodeFactory());
+
+    registerReverseRule(UserEntity.class, ReverseIndexRules.USER_REVERSE_RULE);
+    registerReverseRule(GroupEntity.class, 
ReverseIndexRules.GROUP_REVERSE_RULE);
+    registerReverseRule(RoleEntity.class, ReverseIndexRules.ROLE_REVERSE_RULE);
+  }
+
+  public void clean() {
+    this.reverseIndex = new ConcurrentRadixTree<>(new 
DefaultCharArrayNodeFactory());

Review Comment:
   ditto



##########
core/src/main/java/org/apache/gravitino/cache/ReverseIndexCache.java:
##########
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.cache;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.googlecode.concurrenttrees.radix.ConcurrentRadixTree;
+import com.googlecode.concurrenttrees.radix.RadixTree;
+import 
com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.HasIdentifier;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.meta.GroupEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.meta.UserEntity;
+
+/**
+ * Reverse index cache for managing entity relationships. This cache uses a 
radix tree to
+ * efficiently store and retrieve relationships between entities based on 
their keys.
+ */
+public class ReverseIndexCache {
+  private RadixTree<EntityCacheKey> reverseIndex;
+  /** Registers a reverse index processor for a specific entity class. */
+  private final Map<Class<? extends Entity>, ReverseIndexRule> 
reverseIndexRules = new HashMap<>();
+
+  public ReverseIndexCache() {
+    this.reverseIndex = new ConcurrentRadixTree<>(new 
DefaultCharArrayNodeFactory());
+
+    registerReverseRule(UserEntity.class, ReverseIndexRules.USER_REVERSE_RULE);
+    registerReverseRule(GroupEntity.class, 
ReverseIndexRules.GROUP_REVERSE_RULE);
+    registerReverseRule(RoleEntity.class, ReverseIndexRules.ROLE_REVERSE_RULE);
+  }
+
+  public void clean() {
+    this.reverseIndex = new ConcurrentRadixTree<>(new 
DefaultCharArrayNodeFactory());
+  }
+
+  public boolean remove(EntityCacheKey key) {
+    return reverseIndex.remove(key.toString());
+  }
+
+  public Iterable<EntityCacheKey> getValuesForKeysStartingWith(String 
keyPrefix) {
+    return reverseIndex.getValuesForKeysStartingWith(keyPrefix);
+  }
+
+  public Iterable<CharSequence> getKeysStartingWith(String keyPrefix) {
+    return reverseIndex.getKeysStartingWith(keyPrefix);
+  }
+
+  public boolean remove(String key) {
+    return reverseIndex.remove(key);
+  }
+
+  public int size() {
+    return reverseIndex.size();
+  }
+
+  public void put(
+      NameIdentifier nameIdentifier, Entity.EntityType type, 
EntityCacheRelationKey key) {
+    EntityCacheKey entityCacheKey = EntityCacheKey.of(nameIdentifier, type);
+    String strEntityCacheKey = entityCacheKey.toString();
+    List<EntityCacheKey> entityKeys =
+        
Lists.newArrayList(reverseIndex.getValuesForKeysStartingWith(strEntityCacheKey));
+    String strEntityCacheKeySerialNumber =
+        String.format("%s-%d", strEntityCacheKey, entityKeys.size());
+    reverseIndex.put(strEntityCacheKeySerialNumber, key);
+  }
+
+  public void put(Entity entity, EntityCacheRelationKey key) {
+    Preconditions.checkArgument(entity != null, "EntityCacheRelationKey cannot 
be null");
+
+    if (entity instanceof HasIdentifier) {
+      NameIdentifier nameIdent = ((HasIdentifier) entity).nameIdentifier();
+      put(nameIdent, entity.type(), key);
+    }
+  }
+
+  public void registerReverseRule(Class<? extends Entity> entityClass, 
ReverseIndexRule rule) {
+    reverseIndexRules.put(entityClass, rule);
+  }
+
+  /** Processes an entity and updates the reverse index accordingly. */
+  public void indexEntity(Entity entity, EntityCacheRelationKey key) {
+    ReverseIndexRule rule = reverseIndexRules.get(entity.getClass());
+    if (rule != null) {
+      rule.indexEntity(entity, key, this);
+    }
+  }
+
+  /** Functional interface for processing reverse index rules. */
+  @FunctionalInterface
+  public interface ReverseIndexRule {

Review Comment:
   It can be package access level.



##########
core/src/main/java/org/apache/gravitino/cache/CaffeineEntityCache.java:
##########
@@ -194,6 +207,7 @@ public void clear() {
     withLock(
         () -> {
           cacheData.invalidateAll();
+          reverseIndex = new ReverseIndexCache();
           cacheIndex = new ConcurrentRadixTree<>(new 
DefaultCharArrayNodeFactory());

Review Comment:
   It seems that we will only clear the cache if the Gravitino server is going 
to shut down, perhaps there is no need to create a new instance here. 



##########
core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java:
##########
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.cache;
+
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.meta.GroupEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.meta.UserEntity;
+import org.apache.gravitino.utils.NamespaceUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Reverse index rules for different entity types. This class defines how to 
process reverse
+ * indexing for UserEntity, GroupEntity, and RoleEntity. <br>
+ * For example: <br>
+ * - UserEntity role is 
{metalake-name}.system.user.{user-name}:USER-{serial-number} <br>
+ * - GroupEntity role is 
{metalake-name}.system.group.{group-name}:GROUP-{serial-number} <br>
+ * - RoleEntity role is 
{metalake-name}.system.role.{role-name}:ROLE-{serial-number} <br>
+ */
+public class ReverseIndexRules {
+  private static final Logger LOG = 
LoggerFactory.getLogger(ReverseIndexRules.class.getName());

Review Comment:
   ReverseIndexRules.class.getName() -> ReverseIndexRules.class



##########
core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java:
##########
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.cache;
+
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.meta.GroupEntity;
+import org.apache.gravitino.meta.RoleEntity;
+import org.apache.gravitino.meta.UserEntity;
+import org.apache.gravitino.utils.NamespaceUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Reverse index rules for different entity types. This class defines how to 
process reverse
+ * indexing for UserEntity, GroupEntity, and RoleEntity. <br>
+ * For example: - UserEntity role is 
{metalake-name}.system.user.{user-name}:USER-{serial-number} -
+ * UserEntity role is 
{metalake-name}.system.group.{group-name}:GROUP-{serial-number} - RoleEntity
+ * role is {metalake-name}.system.role.{role-name}:ROLE-{serial-number} - 
{serial-number}: Because
+ */
+public class ReverseIndexRules {
+  private static final Logger LOG = 
LoggerFactory.getLogger(ReverseIndexRules.class.getName());
+
+  /** UserEntity reverse index processor */
+  public static final ReverseIndexCache.ReverseIndexRule USER_REVERSE_RULE =
+      (entity, key, reverseIndexCache) -> {
+        UserEntity userEntity = (UserEntity) entity;
+        if (userEntity.roleNames() != null) {
+          userEntity
+              .roleNames()
+              .forEach(
+                  role -> {
+                    Namespace ns = 
NamespaceUtil.ofRole(userEntity.namespace().level(0));
+                    NameIdentifier nameIdentifier = NameIdentifier.of(ns, 
role);
+                    reverseIndexCache.put(nameIdentifier, 
Entity.EntityType.ROLE, key);
+                  });
+        }
+      };
+
+  /** GroupEntity reverse index processor */
+  public static final ReverseIndexCache.ReverseIndexRule GROUP_REVERSE_RULE =
+      (entity, key, reverseIndexCache) -> {
+        GroupEntity groupEntity = (GroupEntity) entity;
+        if (groupEntity.roleNames() != null) {
+          groupEntity
+              .roleNames()
+              .forEach(
+                  role -> {
+                    Namespace ns = 
NamespaceUtil.ofRole(groupEntity.namespace().level(0));
+                    NameIdentifier nameIdentifier = NameIdentifier.of(ns, 
role);
+                    reverseIndexCache.put(nameIdentifier, 
Entity.EntityType.ROLE, key);
+                  });
+        }
+      };
+
+  /** * RoleEntity reverse index processor */
+  public static final ReverseIndexCache.ReverseIndexRule ROLE_REVERSE_RULE =
+      (entity, key, reverseIndexCache) -> {
+        RoleEntity roleEntity = (RoleEntity) entity;
+        if (roleEntity.securableObjects() != null) {
+          roleEntity
+              .securableObjects()
+              .forEach(
+                  securableObject -> {
+                    Namespace namespace = Namespace.empty();
+                    Entity.EntityType entityType = Entity.EntityType.METALAKE;
+                    switch (securableObject.type()) {
+                      case METALAKE:
+                        entityType = Entity.EntityType.METALAKE;
+                        namespace = NamespaceUtil.ofMetalake();
+                        break;
+                      case CATALOG:
+                        entityType = Entity.EntityType.CATALOG;
+                        namespace = 
NamespaceUtil.ofCatalog(roleEntity.namespace().level(0));
+                        break;
+                      case FILESET:
+                        entityType = Entity.EntityType.FILESET;
+                        Namespace nsParent = 
Namespace.fromString(securableObject.parent());
+                        namespace =
+                            NamespaceUtil.ofFileset(
+                                roleEntity.namespace().level(0),
+                                nsParent.level(0),
+                                nsParent.level(1));
+                        break;
+                      default:

Review Comment:
   I see



##########
core/src/main/java/org/apache/gravitino/cache/EntityCacheRelationKey.java:
##########
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.cache;
+
+import java.util.Objects;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.SupportsRelationOperations;
+
+/** Key for Entity cache. */
+public class EntityCacheRelationKey extends EntityCacheKey {
+  private final SupportsRelationOperations.Type relationType;
+
+  /**
+   * Creates a new instance of {@link EntityCacheRelationKey} with the given 
arguments.
+   *
+   * @param ident The identifier of the entity.
+   * @param type The type of the entity.
+   * @param relationType The type of the relation, it can be null.
+   * @return A new instance of {@link EntityCacheRelationKey}.
+   */
+  public static EntityCacheRelationKey of(
+      NameIdentifier ident, Entity.EntityType type, 
SupportsRelationOperations.Type relationType) {
+    return new EntityCacheRelationKey(ident, type, relationType);
+  }
+
+  /**
+   * Creates a new instance of {@link EntityCacheRelationKey} with the given 
arguments.
+   *
+   * @param ident The identifier of the entity.
+   * @param type The type of the entity.
+   * @return A new instance of {@link EntityCacheRelationKey}.
+   */
+  public static EntityCacheRelationKey of(NameIdentifier ident, 
Entity.EntityType type) {
+    return new EntityCacheRelationKey(ident, type, null);
+  }
+
+  /**
+   * Creates a new instance of {@link EntityCacheRelationKey} with the given 
parameters.
+   *
+   * @param identifier The identifier of the entity.
+   * @param type The type of the entity.
+   * @param relationType The type of the relation.
+   */
+  private EntityCacheRelationKey(
+      NameIdentifier identifier,
+      Entity.EntityType type,
+      SupportsRelationOperations.Type relationType) {
+    super(identifier, type);
+    this.relationType = relationType;
+  }
+
+  /**
+   * Returns the type of the relation.
+   *
+   * @return The type of the relation.
+   */
+  public SupportsRelationOperations.Type relationType() {
+    return relationType;
+  }
+
+  /**
+   * Compares two instances of {@link EntityCacheRelationKey} for equality. 
The comparison is done
+   * by comparing the identifier, type, and relationType of the instances.
+   *
+   * @param obj The object to compare to.
+   * @return {@code true} if the objects are equal, {@code false} otherwise.
+   */
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == this) return true;
+    if (!(obj instanceof EntityCacheRelationKey)) return false;
+    EntityCacheRelationKey other = (EntityCacheRelationKey) obj;
+
+    return Objects.equals(super.identifier(), other.identifier())

Review Comment:
       return super.equals(obj) && Objects.equals(relationType, 
other.relationType);
   



##########
core/src/main/java/org/apache/gravitino/cache/CaffeineEntityCache.java:
##########
@@ -331,18 +346,62 @@ private <KEY, VALUE> Caffeine<KEY, VALUE> 
newBaseBuilder(Config cacheConfig) {
   }
 
   /**
-   * Invalidates the entities by the given cache key.
+   * Invalidate entities with iterative BFS algorithm.
    *
    * @param identifier The identifier of the entity to invalidate
    */
-  private boolean invalidateEntities(NameIdentifier identifier) {
-    List<EntityCacheKey> entityKeysToRemove =
-        
Lists.newArrayList(cacheIndex.getValuesForKeysStartingWith(identifier.toString()));
+  private boolean invalidateEntities(
+      NameIdentifier identifier,
+      Entity.EntityType type,
+      Optional<SupportsRelationOperations.Type> relTypeOpt) {
+    Queue<EntityCacheKey> queue = new ArrayDeque<>();
+
+    EntityCacheKey valueForExactKey =
+        cacheIndex.getValueForExactKey(
+            relTypeOpt.isEmpty()
+                ? EntityCacheKey.of(identifier, type).toString()
+                : EntityCacheRelationKey.of(identifier, type, 
relTypeOpt.get()).toString());
+
+    if (valueForExactKey == null) {
+      // No key to remove
+      return false;
+    }
 
-    cacheData.invalidateAll(entityKeysToRemove);
-    entityKeysToRemove.forEach(key -> cacheIndex.remove(key.toString()));
+    queue.offer(valueForExactKey);
+
+    while (!queue.isEmpty()) {
+      EntityCacheKey currentKeyToRemove = queue.poll();
+
+      cacheData.invalidate(currentKeyToRemove);
+      cacheIndex.remove(currentKeyToRemove.toString());
+
+      // Remove related entity keys
+      List<EntityCacheKey> relatedEntityKeysToRemove =
+          Lists.newArrayList(
+              
cacheIndex.getValuesForKeysStartingWith(currentKeyToRemove.identifier().toString()));
+      queue.addAll(relatedEntityKeysToRemove);
+
+      // Look up from reverse index to go to next depth
+      List<EntityCacheKey> reverseKeysToRemove =
+          Lists.newArrayList(
+              reverseIndex.getValuesForKeysStartingWith(
+                  currentKeyToRemove.identifier().toString()));
+      reverseKeysToRemove.forEach(
+          key -> {
+            // Remove from reverse index
+            // Convert EntityCacheRelationKey to EntityCacheKey
+            reverseIndex
+                .getKeysStartingWith(key.toString())
+                .forEach(
+                    reverseIndexKey -> {
+                      reverseIndex.remove(reverseIndexKey.toString());
+                    });
+          });
+
+      reverseKeysToRemove.forEach(queue::offer);

Review Comment:
   This line can also be optimized as L382



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to