From 9e871800f7bb6de76a2baf10d0395933fd0bb5ab Mon Sep 17 00:00:00 2001 From: danielhanrahan Date: Thu, 17 Aug 2023 14:58:30 +0100 Subject: Normalize JSON attributes during update 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 Change-Id: Ia764a353bf96c05758827845e1358745247ee237 --- .../spi/impl/CpsDataPersistenceServiceImpl.java | 35 ++++++++++++++++------ docs/release-notes.rst | 3 +- .../performance/cps/UpdatePerfTest.groovy | 10 +++---- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java index 50e671d247..19547bbccf 100644 --- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java @@ -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 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 currentLeaves) { + final Map sortedLeaves = new TreeMap<>(String::compareTo); + sortedLeaves.putAll(currentLeaves); + return jsonObjectMapper.asJsonString(sortedLeaves); + } + + private String getOrderedLeavesAsJson(final String currentLeavesAsString) { + if (currentLeavesAsString == null) { + return "{}"; + } + final Map sortedLeaves = jsonObjectMapper.convertJsonString(currentLeavesAsString, + TreeMap.class); + return jsonObjectMapper.asJsonString(sortedLeaves); } private static Map extractListElementFragmentEntitiesByXPath( diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 7fabfc3bb3..b7f4c4fbee 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -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 `_ Improve performance of CPS update operations. Version: 3.4.1 diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy index 35f65551f8..b3030b1c6b 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy @@ -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 -- cgit 1.2.3-korg