This is an automated email from the ASF dual-hosted git repository.

lahirujayathilake pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airavata-data-catalog.git


The following commit(s) were added to refs/heads/main by this push:
     new 15eee7e  Implemented caching layer for data product retrievals
15eee7e is described below

commit 15eee7e3c8e4c9de0dab7d79a32840031ed88b66
Author: lahiruj <[email protected]>
AuthorDate: Fri Feb 27 11:19:41 2026 -0500

    Implemented caching layer for data product retrievals
---
 data-catalog-api/server/service/pom.xml            |  8 ++++
 .../api/DataCatalogApiServiceApplication.java      |  2 +
 .../datacatalog/api/mapper/DataProductMapper.java  | 30 +++++++++------
 .../impl/MetadataSchemaQueryExecutorImpl.java      | 43 ++++++++++++++--------
 .../api/service/impl/DataCatalogServiceImpl.java   |  5 +++
 .../src/main/resources/application.properties      | 13 +++++++
 .../resources/db/changelog/db.changelog-master.xml |  1 +
 7 files changed, 76 insertions(+), 26 deletions(-)

diff --git a/data-catalog-api/server/service/pom.xml 
b/data-catalog-api/server/service/pom.xml
index e83a61c..1e2601e 100644
--- a/data-catalog-api/server/service/pom.xml
+++ b/data-catalog-api/server/service/pom.xml
@@ -62,6 +62,14 @@
             <groupId>org.liquibase</groupId>
             <artifactId>liquibase-core</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git 
a/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/DataCatalogApiServiceApplication.java
 
b/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/DataCatalogApiServiceApplication.java
index d00a704..a630c9c 100644
--- 
a/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/DataCatalogApiServiceApplication.java
+++ 
b/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/DataCatalogApiServiceApplication.java
@@ -12,11 +12,13 @@ import 
org.springframework.boot.builder.SpringApplicationBuilder;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Primary;
+import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
 import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
 
 @ComponentScan(basePackages = { "org.apache.airavata.datacatalog.api", 
"org.apache.custos.sharing.core" })
 @SpringBootApplication
+@EnableCaching
 @EnableJpaRepositories({ 
"org.apache.custos.sharing.core.persistance.repository",
         "org.apache.airavata.datacatalog.api.repository" })
 @EnableJpaAuditing
diff --git 
a/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/mapper/DataProductMapper.java
 
b/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/mapper/DataProductMapper.java
index 218727f..df33532 100644
--- 
a/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/mapper/DataProductMapper.java
+++ 
b/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/mapper/DataProductMapper.java
@@ -1,5 +1,8 @@
 package org.apache.airavata.datacatalog.api.mapper;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.airavata.datacatalog.api.DataProduct;
 import org.apache.airavata.datacatalog.api.exception.EntityNotFoundException;
 import org.apache.airavata.datacatalog.api.model.DataProductEntity;
@@ -9,10 +12,6 @@ import 
org.apache.airavata.datacatalog.api.repository.MetadataSchemaRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
 /**
  * Map to/from
  * {@link org.apache.airavata.datacatalog.api.model.DataProductEntity}
@@ -64,18 +63,29 @@ public class DataProductMapper {
     }
 
     public void mapEntityToModel(DataProductEntity dataProductEntity, 
DataProduct.Builder dataProductBuilder) {
+        mapEagerFields(dataProductEntity, dataProductBuilder);
+        if (dataProductEntity.getMetadataSchemas() != null) {
+            for (MetadataSchemaEntity metadataSchema : 
dataProductEntity.getMetadataSchemas()) {
+                
dataProductBuilder.addMetadataSchemas(metadataSchema.getSchemaName());
+            }
+        }
+        userInfoMapper.mapEntityToModel(dataProductEntity.getOwner(), 
dataProductBuilder.getOwnerBuilder());
+    }
+
+    // Lightweight mapping for search list results. Skips metadataSchemas and 
owner
+    public void mapEntityToModelForList(DataProductEntity dataProductEntity, 
DataProduct.Builder dataProductBuilder) {
+        mapEagerFields(dataProductEntity, dataProductBuilder);
+        // metadataSchemas and owner intentionally skipped as they are not 
needed for list views
+    }
 
+    // Maps fields that do not trigger lazy loads -> id, name, parentId, 
metadata
+    private void mapEagerFields(DataProductEntity dataProductEntity, 
DataProduct.Builder dataProductBuilder) {
         dataProductBuilder
                 .setDataProductId(dataProductEntity.getExternalId())
                 .setName(dataProductEntity.getName());
         if (dataProductEntity.getParentDataProductEntity() != null) {
             
dataProductBuilder.setParentDataProductId(dataProductEntity.getParentDataProductEntity().getExternalId());
         }
-        if (dataProductEntity.getMetadataSchemas() != null) {
-            for (MetadataSchemaEntity metadataSchema : 
dataProductEntity.getMetadataSchemas()) {
-                
dataProductBuilder.addMetadataSchemas(metadataSchema.getSchemaName());
-            }
-        }
         if (dataProductEntity.getMetadata() != null) {
             ObjectMapper mapper = new ObjectMapper();
             try {
@@ -84,7 +94,5 @@ public class DataProductMapper {
                 throw new RuntimeException(e);
             }
         }
-
-        userInfoMapper.mapEntityToModel(dataProductEntity.getOwner(), 
dataProductBuilder.getOwnerBuilder());
     }
 }
diff --git 
a/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/query/impl/MetadataSchemaQueryExecutorImpl.java
 
b/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/query/impl/MetadataSchemaQueryExecutorImpl.java
index c1dee8d..d73f6c3 100644
--- 
a/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/query/impl/MetadataSchemaQueryExecutorImpl.java
+++ 
b/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/query/impl/MetadataSchemaQueryExecutorImpl.java
@@ -68,12 +68,18 @@ public class MetadataSchemaQueryExecutorImpl implements 
MetadataSchemaQueryExecu
     @Autowired
     DataProductMapper dataProductMapper;
 
-    @Override
-    public MetadataSchemaQueryResult execute(UserEntity userEntity, String 
sql, int page, int pageSize)
-            throws MetadataSchemaSqlParseException, 
MetadataSchemaSqlValidateException {
+    private volatile FrameworkConfig cachedConfig = null;
+    private volatile List<MetadataSchemaEntity> lastSchemas = null;
+
+    private synchronized FrameworkConfig 
getOrBuildConfig(List<MetadataSchemaEntity> schemas) {
+        if (cachedConfig == null || lastSchemas != schemas) {
+            cachedConfig = buildFrameworkConfig(schemas);
+            lastSchemas = schemas;
+        }
+        return cachedConfig;
+    }
 
-        // Create a schema that contains the data_product table and all of the 
metadata
-        // schemas
+    private FrameworkConfig buildFrameworkConfig(List<MetadataSchemaEntity> 
metadataSchemas) {
         SchemaPlus schema = Frameworks.createRootSchema(true);
         schema.add("data_product", new AbstractTable() {
             @Override
@@ -88,10 +94,7 @@ public class MetadataSchemaQueryExecutorImpl implements 
MetadataSchemaQueryExecu
             }
         });
 
-        // TODO: limit by tenant id
-        List<MetadataSchemaEntity> metadataSchemas = 
metadataSchemaRepository.findAll();
         for (MetadataSchemaEntity metadataSchema : metadataSchemas) {
-
             schema.add(metadataSchema.getSchemaName(), new AbstractTable() {
                 @Override
                 public RelDataType getRowType(RelDataTypeFactory typeFactory) {
@@ -114,14 +117,25 @@ public class MetadataSchemaQueryExecutorImpl implements 
MetadataSchemaQueryExecu
             });
         }
 
-        FrameworkConfig config = Frameworks.newConfigBuilder()
+        return Frameworks.newConfigBuilder()
                 .defaultSchema(schema)
                 
.parserConfig(SqlParser.Config.DEFAULT.withUnquotedCasing(Casing.TO_LOWER))
                 .build();
+    }
+
+    @Override
+    public MetadataSchemaQueryResult execute(UserEntity userEntity, String 
sql, int page, int pageSize)
+            throws MetadataSchemaSqlParseException, 
MetadataSchemaSqlValidateException {
+
+        // TODO: limit by tenant id
+        List<MetadataSchemaEntity> metadataSchemas = 
metadataSchemaRepository.findAll();
+
+        FrameworkConfig config = getOrBuildConfig(metadataSchemas);
         Planner planner = Frameworks.getPlanner(config);
 
         SqlNode sqlNode = parse(planner, sql);
 
+        SchemaPlus schema = config.getDefaultSchema();
         SqlValidator validator = getValidator(schema, config, planner);
 
         // Validate the query
@@ -172,10 +186,10 @@ public class MetadataSchemaQueryExecutorImpl implements 
MetadataSchemaQueryExecu
         Number countResult = (Number) 
entityManager.createNativeQuery(countSql).getSingleResult();
         int totalCount = countResult.intValue();
 
-        if (page > 0 && pageSize >0) {
-        // offset
-        int offset = (page - 1) * pageSize;
-        finalSql = finalSql + " LIMIT " + pageSize + " OFFSET " + offset;
+        if (page > 0 && pageSize > 0) {
+            // offset
+            int offset = (page - 1) * pageSize;
+            finalSql = finalSql + " LIMIT " + pageSize + " OFFSET " + offset;
         }
 
         List<DataProductEntity> dataProductEntities = 
entityManager.createNativeQuery(finalSql, DataProductEntity.class)
@@ -183,9 +197,8 @@ public class MetadataSchemaQueryExecutorImpl implements 
MetadataSchemaQueryExecu
 
         List<DataProduct> dataProducts = new ArrayList<>();
         for (DataProductEntity dataProductEntity : dataProductEntities) {
-
             org.apache.airavata.datacatalog.api.DataProduct.Builder dpBuilder 
= DataProduct.newBuilder();
-            dataProductMapper.mapEntityToModel(dataProductEntity, dpBuilder);
+            dataProductMapper.mapEntityToModelForList(dataProductEntity, 
dpBuilder);
             dataProducts.add(dpBuilder.build());
         }
         return new MetadataSchemaQueryResult(dataProducts, totalCount);
diff --git 
a/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/service/impl/DataCatalogServiceImpl.java
 
b/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/service/impl/DataCatalogServiceImpl.java
index d164d92..28a0a2d 100644
--- 
a/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/service/impl/DataCatalogServiceImpl.java
+++ 
b/data-catalog-api/server/service/src/main/java/org/apache/airavata/datacatalog/api/service/impl/DataCatalogServiceImpl.java
@@ -29,6 +29,8 @@ import 
org.apache.airavata.datacatalog.api.service.DataCatalogService;
 import org.apache.airavata.datacatalog.api.sharing.SharingManager;
 import org.apache.airavata.datacatalog.api.sharing.exception.SharingException;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -145,6 +147,7 @@ public class DataCatalogServiceImpl implements 
DataCatalogService {
     }
 
     @Override
+    @Cacheable(value = "metadataSchemas", key = "'all'")
     public List<MetadataSchema> getMetadataSchemas() {
         return metadataSchemaRepository.findAll().stream()
                 .map(this::toMetadataSchema)
@@ -152,6 +155,7 @@ public class DataCatalogServiceImpl implements 
DataCatalogService {
     }
 
     @Override
+    @CacheEvict(value = "metadataSchemas", allEntries = true)
     public MetadataSchema createMetadataSchema(MetadataSchema metadataSchema) {
 
         MetadataSchemaEntity metadataSchemaEntity = new MetadataSchemaEntity();
@@ -181,6 +185,7 @@ public class DataCatalogServiceImpl implements 
DataCatalogService {
     }
 
     @Override
+    @CacheEvict(value = "metadataSchemas", allEntries = true)
     public void deleteMetadataSchema(MetadataSchema metadataSchema) {
         // TODO: check that user has write access on metadata schema
         // TODO: handle metadata schema not found
diff --git 
a/data-catalog-api/server/service/src/main/resources/application.properties 
b/data-catalog-api/server/service/src/main/resources/application.properties
index 21051db..f3975f0 100644
--- a/data-catalog-api/server/service/src/main/resources/application.properties
+++ b/data-catalog-api/server/service/src/main/resources/application.properties
@@ -6,6 +6,19 @@ spring.jpa.hibernate.ddl-auto=validate
 spring.jpa.show-sql=true
 spring.liquibase.change-log=classpath:/db/changelog/db.changelog-master.xml
 
+# HikariCP connection pool tuning
+spring.datasource.hikari.maximum-pool-size=20
+spring.datasource.hikari.minimum-idle=5
+spring.datasource.hikari.connection-timeout=30000
+spring.datasource.hikari.idle-timeout=600000
+spring.datasource.hikari.max-lifetime=1800000
+spring.datasource.hikari.keepalive-time=30000
+spring.datasource.hikari.leak-detection-threshold=30000
+
+# Spring cache (Caffeine)
+spring.cache.type=caffeine
+spring.cache.caffeine.spec=maximumSize=100,expireAfterWrite=300s
+
 # Sharing configuration
 
 ## Simple Sharing
diff --git 
a/data-catalog-api/server/service/src/main/resources/db/changelog/db.changelog-master.xml
 
b/data-catalog-api/server/service/src/main/resources/db/changelog/db.changelog-master.xml
index 6f80e3a..8d4f585 100644
--- 
a/data-catalog-api/server/service/src/main/resources/db/changelog/db.changelog-master.xml
+++ 
b/data-catalog-api/server/service/src/main/resources/db/changelog/db.changelog-master.xml
@@ -6,4 +6,5 @@
     <include file="db/changelog/2023/08/2023-08-31-init-changelog.xml"/>
     <include file="db/changelog/2023/08/2023-08-31-init-custos-changelog.xml"/>
     <include file="db/changelog/2023/09/2023-09-01-changelog.xml"/>
+    <include file="db/changelog/2026/02/2026-02-25-performance-indexes.xml" 
relativeToChangelogFile="false"/>
 </databaseChangeLog>

Reply via email to