Temp Table Creation improvements
- extracted methods for more generic temp table creation
- optimized
- added perf test on use case that depends on temp table creation
- had some doubts about stopwatch use in recent tests, but was all OK, just renamed some
Issue-ID: CPS-1422
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
Change-Id: I22cabb9b0ba1b0aa8576a2d756d77af46eebc1b0
diff --git a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy
index df2e9d7..f1a878d 100644
--- a/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy
+++ b/cps-path-parser/src/test/groovy/org/onap/cps/cpspath/parser/CpsPathUtilSpec.groovy
@@ -74,16 +74,16 @@
def 'CPS Path Processing Performance Test.'() {
when: '200,000 paths are processed'
- def setupStopWatch = new StopWatch()
- setupStopWatch.start()
+ def stopWatch = new StopWatch()
+ stopWatch.start()
(1..100000).each {
CpsPathUtil.getNormalizedXpath('/long/path/to/see/if/it/adds/paring/time/significantly/parent/child[@common-leaf-name="123"]')
CpsPathUtil.getNormalizedXpath('//child[@other-leaf=1]/leaf-name[text()="search"]/ancestor::parent')
}
- setupStopWatch.stop()
+ stopWatch.stop()
then: 'it takes less then 10,000 milliseconds'
// In CI this actually takes about 3-5 sec which is approx. 50+ parser executions per millisecond!
- assert setupStopWatch.getTotalTimeMillis() < 10000
+ assert stopWatch.getTotalTimeMillis() < 10000
}
}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java
index 5e4de7f..00e53aa 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceQuery.java
@@ -28,6 +28,5 @@
*/
public interface ModuleReferenceQuery {
- Collection<ModuleReference> identifyNewModuleReferences(
- final Collection<ModuleReference> moduleReferencesToCheck);
+ Collection<ModuleReference> identifyNewModuleReferences(final Collection<ModuleReference> moduleReferencesToCheck);
}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepository.java
index f70e218..ef701bc 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepository.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepository.java
@@ -20,16 +20,10 @@
package org.onap.cps.spi.repository;
-import java.util.Collection;
import org.onap.cps.spi.entities.YangResourceEntity;
-import org.onap.cps.spi.model.ModuleReference;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
-public interface ModuleReferenceRepository extends JpaRepository<YangResourceEntity, Long>, ModuleReferenceQuery {
+public interface ModuleReferenceRepository extends JpaRepository<YangResourceEntity, Long>, ModuleReferenceQuery {}
- Collection<ModuleReference> identifyNewModuleReferences(
- final Collection<ModuleReference> moduleReferencesToCheck);
-
-}
\ No newline at end of file
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java
index 681bbcd..48982d5 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/ModuleReferenceRepositoryImpl.java
@@ -23,8 +23,8 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
-import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import lombok.AllArgsConstructor;
@@ -41,6 +41,8 @@
@PersistenceContext
private EntityManager entityManager;
+ private TempTableCreator tempTableCreator;
+
@Override
@SneakyThrows
public Collection<ModuleReference> identifyNewModuleReferences(
@@ -50,42 +52,18 @@
return Collections.emptyList();
}
- final String tempTableName = "moduleReferencesToCheckTemp"
- + UUID.randomUUID().toString().replace("-", "");
-
- createTemporaryTable(tempTableName);
- insertDataIntoTable(tempTableName, moduleReferencesToCheck);
-
- return identifyNewModuleReferencesForCmHandle(tempTableName);
- }
-
- private void createTemporaryTable(final String tempTableName) {
- final StringBuilder sqlStringBuilder = new StringBuilder("CREATE TEMPORARY TABLE " + tempTableName + "(");
- sqlStringBuilder.append(" id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,");
- sqlStringBuilder.append(" module_name varchar NOT NULL,");
- sqlStringBuilder.append(" revision varchar NOT NULL");
- sqlStringBuilder.append(");");
-
- entityManager.createNativeQuery(sqlStringBuilder.toString()).executeUpdate();
- }
-
- private void insertDataIntoTable(final String tempTableName, final Collection<ModuleReference> moduleReferences) {
- final StringBuilder sqlStringBuilder = new StringBuilder("INSERT INTO " + tempTableName);
- sqlStringBuilder.append(" (module_name, revision) ");
- sqlStringBuilder.append(" VALUES ");
-
- for (final ModuleReference moduleReference : moduleReferences) {
- sqlStringBuilder.append("('");
- sqlStringBuilder.append(moduleReference.getModuleName());
- sqlStringBuilder.append("', '");
- sqlStringBuilder.append(moduleReference.getRevision());
- sqlStringBuilder.append("'),");
+ final Collection<List<String>> sqlData = new HashSet<>(moduleReferencesToCheck.size());
+ for (final ModuleReference moduleReference : moduleReferencesToCheck) {
+ final List<String> row = new ArrayList<>(2);
+ row.add(moduleReference.getModuleName());
+ row.add(moduleReference.getRevision());
+ sqlData.add(row);
}
- // replace last ',' with ';'
- sqlStringBuilder.replace(sqlStringBuilder.length() - 1, sqlStringBuilder.length(), ";");
+ final String tempTableName = tempTableCreator.createTemporaryTable(
+ "moduleReferencesToCheckTemp", sqlData, "module_name", "revision");
- entityManager.createNativeQuery(sqlStringBuilder.toString()).executeUpdate();
+ return identifyNewModuleReferencesForCmHandle(tempTableName);
}
private Collection<ModuleReference> identifyNewModuleReferencesForCmHandle(final String tempTableName) {
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
new file mode 100644
index 0000000..8cad9f5
--- /dev/null
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
@@ -0,0 +1,97 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation.
+ * ================================================================================
+ * Licensed 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.repository;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Slf4j
+@Transactional
+@AllArgsConstructor
+@Component
+public class TempTableCreator {
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ /**
+ * Create a uniquely named temporary table.
+ *
+ * @param prefix prefix for the table name (so you can recognize it)
+ * @param sqlData data to insert (strings only) the inner List present a row of data
+ * @param columnNames column names (in same order as data in rows in sqlData)
+ * @return a unique temporary table name with given prefix
+ */
+ public String createTemporaryTable(final String prefix,
+ final Collection<List<String>> sqlData,
+ final String... columnNames) {
+ final String tempTableName = prefix + UUID.randomUUID().toString().replace("-", "");
+ final StringBuilder sqlStringBuilder = new StringBuilder("CREATE TEMPORARY TABLE ");
+ sqlStringBuilder.append(tempTableName);
+ defineColumns(sqlStringBuilder, columnNames);
+ insertData(sqlStringBuilder, tempTableName, columnNames, sqlData);
+ entityManager.createNativeQuery(sqlStringBuilder.toString()).executeUpdate();
+ return tempTableName;
+ }
+
+ private static void defineColumns(final StringBuilder sqlStringBuilder, final String[] columnNames) {
+ sqlStringBuilder.append('(');
+ final Iterator<String> it = Arrays.stream(columnNames).iterator();
+ while (it.hasNext()) {
+ final String columnName = it.next();
+ sqlStringBuilder.append(" ");
+ sqlStringBuilder.append(columnName);
+ sqlStringBuilder.append(" varchar NOT NULL");
+ if (it.hasNext()) {
+ sqlStringBuilder.append(",");
+ }
+ }
+ sqlStringBuilder.append(");");
+ }
+
+ private static void insertData(final StringBuilder sqlStringBuilder,
+ final String tempTableName,
+ final String[] columnNames,
+ final Collection<List<String>> sqlData) {
+ final Collection<String> sqlInserts = new HashSet<>(sqlData.size());
+ for (final Collection<String> row : sqlData) {
+ sqlInserts.add("('" + String.join("','", row) + "')");
+ }
+ sqlStringBuilder.append("INSERT INTO ");
+ sqlStringBuilder.append(tempTableName);
+ sqlStringBuilder.append(" (");
+ sqlStringBuilder.append(String.join(",", columnNames));
+ sqlStringBuilder.append(") VALUES ");
+ sqlStringBuilder.append(String.join(",", sqlInserts));
+ sqlStringBuilder.append(";");
+ }
+
+}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy
index bcb0807..4c67f7e 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy
@@ -230,8 +230,9 @@
def 'Identifying new module references where #scenario'() {
when: 'identifyNewModuleReferences is called'
def result = objectUnderTest.identifyNewModuleReferences(moduleReferences)
- then: 'the correct module reference collection is returned'
- assert result == expectedResult
+ then: 'the correct module references are returned'
+ assert result.size() == expectedResult.size()
+ assert result.containsAll(expectedResult)
where: 'the following data is used'
scenario | moduleReferences || expectedResult
'new module references exist' | toModuleReference([['some module 1' : 'some revision 1'], ['some module 2' : 'some revision 2']]) || toModuleReference([['some module 1' : 'some revision 1'], ['some module 2' : 'some revision 2']])
@@ -304,7 +305,7 @@
def moduleReferences = [].withDefault { [:] }
moduleReferenceAsMap.forEach(property ->
property.forEach((moduleName, revision) -> {
- moduleReferences.add(new ModuleReference('moduleName' : moduleName, 'revision' : revision))
+ moduleReferences.add(new ModuleReference(moduleName, revision))
}))
return moduleReferences
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsModuleReferenceRepositoryPerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsModuleReferenceRepositoryPerfTest.groovy
new file mode 100644
index 0000000..8da6c3a
--- /dev/null
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsModuleReferenceRepositoryPerfTest.groovy
@@ -0,0 +1,103 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2022 Nordix Foundation
+ * ================================================================================
+ * Licensed 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.performance
+
+import org.onap.cps.spi.CpsModulePersistenceService
+import org.onap.cps.spi.entities.SchemaSetEntity
+import org.onap.cps.spi.impl.CpsPersistenceSpecBase
+import org.onap.cps.spi.model.ModuleReference
+import org.onap.cps.spi.repository.DataspaceRepository
+import org.onap.cps.spi.repository.ModuleReferenceRepository
+import org.onap.cps.spi.repository.SchemaSetRepository
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.test.context.jdbc.Sql
+import org.springframework.util.StopWatch
+
+import java.util.concurrent.ThreadLocalRandom
+
+class CpsModuleReferenceRepositoryPerfTest extends CpsPersistenceSpecBase {
+
+ static final String PERF_TEST_DATA = '/data/perf-test.sql'
+
+ def NEW_RESOURCE_CONTENT = 'module stores {\n' +
+ ' yang-version 1.1;\n' +
+ ' namespace "org:onap:ccsdk:sample";\n' +
+ '\n' +
+ ' prefix book-store;\n' +
+ '\n' +
+ ' revision "2020-09-15" {\n' +
+ ' description\n' +
+ ' "Sample Model";\n' +
+ ' }' +
+ '}'
+
+ @Autowired
+ CpsModulePersistenceService objectUnderTest
+
+ @Autowired
+ DataspaceRepository dataspaceRepository
+
+ @Autowired
+ SchemaSetRepository schemaSetRepository
+
+ @Autowired
+ ModuleReferenceRepository moduleReferenceRepository
+
+ @Sql([CLEAR_DATA, PERF_TEST_DATA])
+ def 'Store new schema set with many modules'() {
+ when: 'a new schema set with 200 modules is stored'
+ def newYangResourcesNameToContentMap = [:]
+ (1..200).each {
+ def year = 2000 + it
+ def resourceName = "module${it}".toString()
+ def moduleName = "stores${it}"
+ def content = NEW_RESOURCE_CONTENT.replace('2020',String.valueOf(year)).replace('stores',moduleName)
+ newYangResourcesNameToContentMap.put(resourceName, content)
+ }
+ objectUnderTest.storeSchemaSet('PERF-DATASPACE', 'perfSchemaSet', newYangResourcesNameToContentMap)
+ then: 'the schema set is persisted correctly'
+ def dataspaceEntity = dataspaceRepository.getByName('PERF-DATASPACE')
+ SchemaSetEntity result = schemaSetRepository.getByDataspaceAndName(dataspaceEntity, 'perfSchemaSet')
+ result.yangResources.size() == 200
+ and: 'identification of new module resources is fast enough (1,000 executions less then 5,000 milliseconds)'
+ def stopWatch = new StopWatch()
+ 1000.times() {
+ def moduleReferencesToCheck = createModuleReferencesWithRandomMatchingExistingModuleReferences()
+ stopWatch.start()
+ def newModuleReferences = moduleReferenceRepository.identifyNewModuleReferences(moduleReferencesToCheck)
+ stopWatch.stop()
+ assert newModuleReferences.size() > 0 && newModuleReferences.size() < 300
+ }
+ assert stopWatch.getTotalTimeMillis() < 5000
+ }
+
+ def createModuleReferencesWithRandomMatchingExistingModuleReferences() {
+ def moduleReferences = []
+ (1..250).each {
+ def randomNumber = ThreadLocalRandom.current().nextInt(1, 300)
+ def year = 2000 + randomNumber
+ def moduleName = "stores${randomNumber}"
+ moduleReferences.add(new ModuleReference(moduleName, "${year}-09-15"))
+ }
+ return moduleReferences
+ }
+
+}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy
index 33e83f1..265c5fc 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsToDataNodePerfTest.groovy
@@ -47,26 +47,25 @@
static def ALLOWED_SETUP_TIME_MS = TimeUnit.SECONDS.toMillis(10)
static def ALLOWED_READ_TIME_AL_NODES_MS = 500
- def readStopWatch = new StopWatch()
+ def stopWatch = new StopWatch()
@Sql([CLEAR_DATA, PERF_TEST_DATA])
def 'Create a node with many descendants (please note, subsequent tests depend on this running first).'() {
given: 'a node with a large number of descendants is created'
- def setupStopWatch = new StopWatch()
- setupStopWatch.start()
+ stopWatch.start()
createLineage()
- setupStopWatch.stop()
- def setupDurationInMillis = setupStopWatch.getTotalTimeMillis()
+ stopWatch.stop()
+ def setupDurationInMillis = stopWatch.getTotalTimeMillis()
and: 'setup duration is under #ALLOWED_SETUP_TIME_MS milliseconds'
assert setupDurationInMillis < ALLOWED_SETUP_TIME_MS
}
def 'Get data node with many descendants by xpath #scenario'() {
when: 'get parent is executed with all descendants'
- readStopWatch.start()
+ stopWatch.start()
def result = objectUnderTest.getDataNode('PERF-DATASPACE', 'PERF-ANCHOR', xpath, INCLUDE_ALL_DESCENDANTS)
- readStopWatch.stop()
- def readDurationInMillis = readStopWatch.getTotalTimeMillis()
+ stopWatch.stop()
+ def readDurationInMillis = stopWatch.getTotalTimeMillis()
then: 'read duration is under 500 milliseconds'
assert readDurationInMillis < ALLOWED_READ_TIME_AL_NODES_MS
and: 'data node is returned with all the descendants populated'
@@ -79,10 +78,10 @@
def 'Query parent data node with many descendants by cps-path'() {
when: 'query is executed with all descendants'
- readStopWatch.start()
+ stopWatch.start()
def result = objectUnderTest.queryDataNodes('PERF-DATASPACE', 'PERF-ANCHOR', '//perf-parent-1' , INCLUDE_ALL_DESCENDANTS)
- readStopWatch.stop()
- def readDurationInMillis = readStopWatch.getTotalTimeMillis()
+ stopWatch.stop()
+ def readDurationInMillis = stopWatch.getTotalTimeMillis()
then: 'read duration is under 500 milliseconds'
assert readDurationInMillis < ALLOWED_READ_TIME_AL_NODES_MS
and: 'data node is returned with all the descendants populated'
@@ -91,10 +90,10 @@
def 'Query many descendants by cps-path with #scenario'() {
when: 'query is executed with all descendants'
- readStopWatch.start()
+ stopWatch.start()
def result = objectUnderTest.queryDataNodes('PERF-DATASPACE', 'PERF-ANCHOR', '//perf-test-grand-child-1', descendantsOption)
- readStopWatch.stop()
- def readDurationInMillis = readStopWatch.getTotalTimeMillis()
+ stopWatch.stop()
+ def readDurationInMillis = stopWatch.getTotalTimeMillis()
then: 'read duration is under 500 milliseconds'
assert readDurationInMillis < alowedDuration
and: 'data node is returned with all the descendants populated'
diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/ModuleReference.java b/cps-service/src/main/java/org/onap/cps/spi/model/ModuleReference.java
index 569f0a0..18d55d5 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/model/ModuleReference.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/model/ModuleReference.java
@@ -49,4 +49,5 @@
this.revision = revision;
this.namespace = "";
}
+
}