diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/constants/DeprecatedFieldName.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/constants/DeprecatedFieldName.java index 687b541dc515..84dd232a7b51 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/constants/DeprecatedFieldName.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/constants/DeprecatedFieldName.java @@ -1,5 +1,6 @@ package com.appsmith.server.migrations.constants; public class DeprecatedFieldName { - public static String POLICIES = "policies"; + public static final String POLICIES = "policies"; + public static final String DELETED = "deleted"; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/constants/FieldName.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/constants/FieldName.java index bbb38691d970..ee6fe668ced7 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/constants/FieldName.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/constants/FieldName.java @@ -2,4 +2,7 @@ public class FieldName { public static final String POLICY_MAP = "policyMap"; + public static final String DELETED_AT = "deletedAt"; + public static final String TENANT_ID = "tenantId"; + public static final String PERMISSION_GROUPS = "permissionGroups"; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration060AddIndexesForPolicyMap.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration060AddIndexesForPolicyMap.java new file mode 100644 index 000000000000..980b77ee0909 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration060AddIndexesForPolicyMap.java @@ -0,0 +1,93 @@ +package com.appsmith.server.migrations.db.ce; + +import com.appsmith.server.domains.Workspace; +import io.mongock.api.annotations.ChangeUnit; +import io.mongock.api.annotations.Execution; +import io.mongock.api.annotations.RollbackExecution; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.mongodb.UncategorizedMongoDbException; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.index.Index; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +import static com.appsmith.external.helpers.StringUtils.dotted; +import static com.appsmith.server.migrations.DatabaseChangelog1.dropIndexIfExists; +import static com.appsmith.server.migrations.DatabaseChangelog1.ensureIndexes; +import static com.appsmith.server.migrations.DatabaseChangelog1.makeIndex; +import static com.appsmith.server.migrations.constants.DeprecatedFieldName.DELETED; +import static com.appsmith.server.migrations.constants.FieldName.DELETED_AT; +import static com.appsmith.server.migrations.constants.FieldName.PERMISSION_GROUPS; +import static com.appsmith.server.migrations.constants.FieldName.POLICY_MAP; +import static com.appsmith.server.migrations.constants.FieldName.TENANT_ID; + +/** + * This migration adds indexes to the policyMap fields within application and workspace collection to speed up queries. + * This migration also adds a compound index on the deleted and deletedAt fields to speed up queries that filter on + * these fields. + * Ideally we should rely on @see Wildcard Indexes, + * but this may end up hogging a lot of memory as it recursively create index on policyMap, hence we are just creating + * the compound index which have the most impact. + */ +@RequiredArgsConstructor +@Slf4j +@ChangeUnit(order = "060", id = "add-idx-policy-map", author = " ") +public class Migration060AddIndexesForPolicyMap { + private final MongoTemplate mongoTemplate; + + @RollbackExecution + public void rollbackExecution() {} + + @Execution + public void executeMigration() { + String readWorkspaceTanantIdCompoundIndex = "policy_read_workspace_tanantId_compound_index"; + String policyMapReadWorkspacePath = dotted(POLICY_MAP, "read:workspaces", PERMISSION_GROUPS); + + String manageWorkspaceTanantIdCompoundIndex = "policy_manage_workspace_tanantId_compound_index"; + String policyMapManageWorkspacePath = dotted(POLICY_MAP, "manage:workspaces", PERMISSION_GROUPS); + + Mono readWorkspaceMono = Mono.fromCallable(() -> { + log.debug("Adding read workspace policy map indices"); + createAndApplyIndex( + readWorkspaceTanantIdCompoundIndex, + Workspace.class, + policyMapReadWorkspacePath, + TENANT_ID, + DELETED, + DELETED_AT); + return true; + }) + .subscribeOn(Schedulers.boundedElastic()); + + Mono manageWorkspaceMono = Mono.fromCallable(() -> { + log.debug("Adding manage workspace policy map indices"); + createAndApplyIndex( + manageWorkspaceTanantIdCompoundIndex, + Workspace.class, + policyMapManageWorkspacePath, + TENANT_ID, + DELETED, + DELETED_AT); + return true; + }) + .subscribeOn(Schedulers.boundedElastic()); + + Mono.zip(readWorkspaceMono, manageWorkspaceMono).block(); + } + + private void createAndApplyIndex(String indexName, Class clazz, String... fields) { + try { + Index index = makeIndex(fields).named(indexName); + dropIndexIfExists(mongoTemplate, clazz, indexName); + ensureIndexes(mongoTemplate, clazz, index); + } catch (UncategorizedMongoDbException exception) { + log.error( + "An error occurred while creating the index : {}, skipping the addition of index because of {}.", + indexName, + exception.getMessage()); + } catch (Exception e) { + log.error("An error occurred while creating the index : {}", indexName, e); + } + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration061TenantPolicySetToPolicyMap.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration061TenantPolicySetToPolicyMap.java new file mode 100644 index 000000000000..cb966fd6b810 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/db/ce/Migration061TenantPolicySetToPolicyMap.java @@ -0,0 +1,58 @@ +package com.appsmith.server.migrations.db.ce; + +import com.appsmith.external.models.Policy; +import com.appsmith.server.domains.Tenant; +import com.appsmith.server.helpers.CollectionUtils; +import com.appsmith.server.repositories.CacheableRepositoryHelper; +import io.mongock.api.annotations.ChangeUnit; +import io.mongock.api.annotations.Execution; +import io.mongock.api.annotations.RollbackExecution; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Query; + +import java.util.HashMap; +import java.util.Map; + +import static com.appsmith.server.constants.ce.FieldNameCE.DEFAULT; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +@Slf4j +@ChangeUnit(order = "061", id = "migrate-policy-set-to-map-tenant", author = " ") +public class Migration061TenantPolicySetToPolicyMap { + private final MongoTemplate mongoTemplate; + private final CacheableRepositoryHelper cacheableRepositoryHelper; + + public Migration061TenantPolicySetToPolicyMap( + MongoTemplate mongoTemplate, CacheableRepositoryHelper cacheableRepositoryHelper) { + this.mongoTemplate = mongoTemplate; + this.cacheableRepositoryHelper = cacheableRepositoryHelper; + } + + @RollbackExecution + public void rollbackExecution() {} + + @Execution + public void executeMigration() { + // Fetch default tenant and verify earlier migration has updated the policyMap field + Query tenantQuery = new Query(); + tenantQuery.addCriteria(where(Tenant.Fields.slug).is(DEFAULT)); + Tenant defaultTenant = mongoTemplate.findOne(tenantQuery, Tenant.class); + if (defaultTenant == null) { + log.error( + "No default tenant found. Aborting migration to update policy set to map in tenant Migration061TenantPolicySetToPolicyMap."); + return; + } + // Evict the tenant to avoid any cache inconsistencies + if (CollectionUtils.isNullOrEmpty(defaultTenant.getPolicyMap())) { + Map policyMap = new HashMap<>(); + defaultTenant.getPolicies().forEach(policy -> policyMap.put(policy.getPermission(), policy)); + defaultTenant.setPolicyMap(policyMap); + mongoTemplate.save(defaultTenant); + cacheableRepositoryHelper.evictCachedTenant(defaultTenant.getId()).block(); + } else { + log.info( + "Tenant already has policyMap set. Skipping migration to update policy set to map in tenant Migration061TenantPolicySetToPolicyMap."); + } + } +}