Normalize JSON attributes during update 99/136899/7
authordanielhanrahan <daniel.hanrahan@est.tech>
Thu, 17 Aug 2023 13:58:30 +0000 (14:58 +0100)
committerdanielhanrahan <daniel.hanrahan@est.tech>
Thu, 4 Jan 2024 18:31:34 +0000 (18:31 +0000)
This change fixes a few issues related to JSON encoding of
FragmentEntity attributes (data leaves). This significantly
improves update performance in cases of partial updates.

- Normalize JSON and order leaves by name when comparing
  data leaves during update operations.
- Update performance test timings.

Issue-ID: CPS-2018
Signed-off-by: danielhanrahan <daniel.hanrahan@est.tech>
Change-Id: Ia764a353bf96c05758827845e1358745247ee237

cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
docs/release-notes.rst
integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy

index 50e671d..19547bb 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
@@ -38,6 +38,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -526,7 +527,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
 
     private void updateFragmentEntityAndDescendantsWithDataNode(final FragmentEntity existingFragmentEntity,
                                                                 final DataNode newDataNode) {
-        existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(newDataNode.getLeaves()));
+        copyAttributesFromNewDataNode(existingFragmentEntity, newDataNode);
 
         final Map<String, FragmentEntity> existingChildrenByXpath = existingFragmentEntity.getChildFragments().stream()
                 .collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity));
@@ -668,7 +669,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             return convertToFragmentWithAllDescendants(parentEntity.getAnchor(), newListElement);
         }
         if (newListElement.getChildDataNodes().isEmpty()) {
-            copyAttributesFromNewListElement(existingListElementEntity, newListElement);
+            copyAttributesFromNewDataNode(existingListElementEntity, newListElement);
             existingListElementEntity.getChildFragments().clear();
         } else {
             updateFragmentEntityAndDescendantsWithDataNode(existingListElementEntity, newListElement);
@@ -681,12 +682,28 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         return !existingListElementsByXpath.containsKey(replacementDataNode.getXpath());
     }
 
-    private void copyAttributesFromNewListElement(final FragmentEntity existingListElementEntity,
-                                                  final DataNode newListElement) {
-        final FragmentEntity replacementFragmentEntity =
-                FragmentEntity.builder().attributes(jsonObjectMapper.asJsonString(
-                        newListElement.getLeaves())).build();
-        existingListElementEntity.setAttributes(replacementFragmentEntity.getAttributes());
+    private void copyAttributesFromNewDataNode(final FragmentEntity existingFragmentEntity,
+                                               final DataNode newDataNode) {
+        final String oldOrderedLeavesAsJson = getOrderedLeavesAsJson(existingFragmentEntity.getAttributes());
+        final String newOrderedLeavesAsJson = getOrderedLeavesAsJson(newDataNode.getLeaves());
+        if (!oldOrderedLeavesAsJson.equals(newOrderedLeavesAsJson)) {
+            existingFragmentEntity.setAttributes(jsonObjectMapper.asJsonString(newDataNode.getLeaves()));
+        }
+    }
+
+    private String getOrderedLeavesAsJson(final Map<String, Serializable> currentLeaves) {
+        final Map<String, Serializable> sortedLeaves = new TreeMap<>(String::compareTo);
+        sortedLeaves.putAll(currentLeaves);
+        return jsonObjectMapper.asJsonString(sortedLeaves);
+    }
+
+    private String getOrderedLeavesAsJson(final String currentLeavesAsString) {
+        if (currentLeavesAsString == null) {
+            return "{}";
+        }
+        final Map<String, Serializable> sortedLeaves = jsonObjectMapper.convertJsonString(currentLeavesAsString,
+                TreeMap.class);
+        return jsonObjectMapper.asJsonString(sortedLeaves);
     }
 
     private static Map<String, FragmentEntity> extractListElementFragmentEntitiesByXPath(
index 7fabfc3..b7f4c4f 100644 (file)
@@ -1,6 +1,6 @@
 .. This work is licensed under a Creative Commons Attribution 4.0 International License.
 .. http://creativecommons.org/licenses/by/4.0
-.. Copyright (C) 2021-2023 Nordix Foundation
+.. Copyright (C) 2021-2024 Nordix Foundation
 
 .. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING
 .. _release_notes:
@@ -43,6 +43,7 @@ Bug Fixes
 
 Features
 --------
+    - `CPS-2018 <https://jira.onap.org/browse/CPS-2018>`_ Improve performance of CPS update operations.
 
 
 Version: 3.4.1
index 35f6555..b3030b1 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
+ *  Copyright (C) 2023-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
@@ -80,8 +80,8 @@ class UpdatePerfTest extends CpsPerfTestBase {
         where:
             scenario                           | totalNodes | startId | changeLeaves || timeLimit | memoryLimit
             'Replace 0 nodes with 100'         | 100        | 1       | false        ||         7 | 250
-            'Replace 100 using same data'      | 100        | 1       | false        ||         5 | 250
-            'Replace 100 with new leaf values' | 100        | 1       | true         ||         5 | 250
+            'Replace 100 using same data'      | 100        | 1       | false        ||         3 | 250
+            'Replace 100 with new leaf values' | 100        | 1       | true         ||         3 | 250
             'Replace 100 with 100 new nodes'   | 100        | 101     | false        ||        12 | 300
             'Replace 50 existing and 50 new'   | 100        | 151     | true         ||         8 | 250
             'Replace 100 nodes with 0'         | 0          | 1       | false        ||         5 | 250
@@ -106,8 +106,8 @@ class UpdatePerfTest extends CpsPerfTestBase {
         where:
             scenario                                   | totalNodes | startId | changeLeaves || timeLimit | memoryLimit
             'Replace list of 0 with 100'               | 100        | 1       | false        ||         7 | 250
-            'Replace list of 100 using same data'      | 100        | 1       | false        ||         5 | 250
-            'Replace list of 100 with new leaf values' | 100        | 1       | true         ||         5 | 250
+            'Replace list of 100 using same data'      | 100        | 1       | false        ||         3 | 250
+            'Replace list of 100 with new leaf values' | 100        | 1       | true         ||         3 | 250
             'Replace list with 100 new nodes'          | 100        | 101     | false        ||        12 | 300
             'Replace list with 50 existing and 50 new' | 100        | 151     | true         ||         8 | 250
             'Replace list of 100 nodes with 1'         | 1          | 1       | false        ||         5 | 250