You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by nf...@apache.org on 2024/08/02 07:39:28 UTC

(jackrabbit-oak) branch trunk updated: OAK-10989 - Performance improvements to JSON parsing #1612

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

nfsantos pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/trunk by this push:
     new c4b5a6b2fd OAK-10989 - Performance improvements to JSON parsing #1612
c4b5a6b2fd is described below

commit c4b5a6b2fdabff92b3154b0d553274262285d237
Author: Nuno Santos <ns...@adobe.com>
AuthorDate: Fri Aug 2 09:39:23 2024 +0200

    OAK-10989 - Performance improvements to JSON parsing #1612
    
    - when reading a property, check for the most common types first (String).
    - make instances reusable by creating a new MemoryNodeBuilder for every call to deserialize
    - Remove usages of Guava collections.
    - Move the methods in the inner class DeserializationSupport to top level methods in JsonDeserializer, as these inner class was not accessed anywhere outside JsonDeserializer and did not contain any state.
---
 .../document/flatfile/NodeStateEntryReader.java    |   6 +-
 .../jackrabbit/oak/json/JsonDeserializer.java      | 178 +++++++++------------
 2 files changed, 82 insertions(+), 102 deletions(-)

diff --git a/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/index/indexer/document/flatfile/NodeStateEntryReader.java b/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/index/indexer/document/flatfile/NodeStateEntryReader.java
index 87852d0fc3..96732d4e96 100644
--- a/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/index/indexer/document/flatfile/NodeStateEntryReader.java
+++ b/oak-run-commons/src/main/java/org/apache/jackrabbit/oak/index/indexer/document/flatfile/NodeStateEntryReader.java
@@ -29,10 +29,11 @@ import org.apache.jackrabbit.oak.spi.state.NodeState;
 import static org.apache.jackrabbit.oak.commons.StringUtils.estimateMemoryUsage;
 
 public class NodeStateEntryReader {
-    private final BlobDeserializer blobDeserializer;
+    private final JsonDeserializer des;
 
     public NodeStateEntryReader(BlobStore blobStore) {
-        this.blobDeserializer = new BlobIdSerializer(blobStore);
+        BlobDeserializer blobDeserializer = new BlobIdSerializer(blobStore);
+        this.des = new JsonDeserializer(blobDeserializer);
     }
 
     public NodeStateEntry read(String line) {
@@ -43,7 +44,6 @@ public class NodeStateEntryReader {
     }
 
     protected NodeState parseState(String part) {
-        JsonDeserializer des = new JsonDeserializer(blobDeserializer);
         return des.deserialize(part);
     }
 }
diff --git a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java
index 49f7d4137a..f10abb1835 100644
--- a/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java
+++ b/oak-store-spi/src/main/java/org/apache/jackrabbit/oak/json/JsonDeserializer.java
@@ -19,12 +19,12 @@
 
 package org.apache.jackrabbit.oak.json;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
 import javax.jcr.PropertyType;
 
-import org.apache.jackrabbit.guava.common.collect.Lists;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.commons.LongUtils;
@@ -39,8 +39,6 @@ import org.apache.jackrabbit.oak.plugins.value.Conversions;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
-import static org.apache.jackrabbit.guava.common.collect.ImmutableSet.of;
-import static java.util.Collections.emptyList;
 import static org.apache.jackrabbit.JcrConstants.JCR_MIXINTYPES;
 import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
 import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED;
@@ -48,26 +46,45 @@ import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE
 import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty;
 
 public class JsonDeserializer {
-    public static final String OAK_CHILD_ORDER = ":childOrder";
-    private final BlobDeserializer blobHandler;
-    private final NodeBuilder builder;
-    private final DeserializationSupport deserializationSupport;
+    /**
+     * Provides support for inferring types for some common property name and types
+     */
+    private static final Set<String> NAME_PROPS = Set.of(JCR_PRIMARYTYPE, JCR_MIXINTYPES);
+    private static final String ORDERABLE_TYPE = NT_UNSTRUCTURED;
+    private static final String OAK_CHILD_ORDER = ":childOrder";
 
-    private JsonDeserializer(BlobDeserializer blobHandler, NodeBuilder builder, DeserializationSupport support) {
-        this.blobHandler = blobHandler;
-        this.builder = builder;
-        this.deserializationSupport = support;
+    private static Type<?> inferPropertyType(String propertyName, String jsonString) {
+        if (NAME_PROPS.contains(propertyName) && hasSingleColon(jsonString)) {
+            return Type.NAME;
+        }
+        return Type.UNDEFINED;
     }
 
-    public JsonDeserializer(BlobDeserializer blobHandler) {
-        this(blobHandler, EMPTY_NODE.builder(), DeserializationSupport.INSTANCE);
+    private static boolean hasOrderableChildren(NodeBuilder builder) {
+        PropertyState primaryType = builder.getProperty(JCR_PRIMARYTYPE);
+        return primaryType != null && ORDERABLE_TYPE.equals(primaryType.getValue(Type.NAME));
+    }
+
+    private static boolean hasSingleColon(String jsonString) {
+        // In case the primaryType was encoded then it would be like
+        // "nam:oak:Unstructured". So check if there is only one occurrence
+        // of ':'.
+        int colonCount = 0;
+        for (int i = 0; i < jsonString.length() && colonCount < 2; i++) {
+            if (jsonString.charAt(i) == ':') {
+                colonCount += 1;
+            }
+        }
+        return colonCount == 1;
     }
 
-    public JsonDeserializer(BlobDeserializer blobHandler, NodeBuilder builder) {
-        this(blobHandler, builder, DeserializationSupport.INSTANCE);
+    private final BlobDeserializer blobHandler;
+
+    public JsonDeserializer(BlobDeserializer blobHandler) {
+        this.blobHandler = blobHandler;
     }
 
-    public NodeState deserialize(String json){
+    public NodeState deserialize(String json) {
         JsopReader reader = new JsopTokenizer(json);
         reader.read('{');
         NodeState state = deserialize(reader);
@@ -75,13 +92,14 @@ public class JsonDeserializer {
         return state;
     }
 
-    public NodeState deserialize(JsopReader reader){
+    public NodeState deserialize(JsopReader reader) {
+        NodeBuilder builder = EMPTY_NODE.builder();
         readNode(reader, builder);
         return builder.getNodeState();
     }
 
     private void readNode(JsopReader reader, NodeBuilder builder) {
-        List<String> childNames = Lists.newArrayList();
+        List<String> childNames = new ArrayList<>();
         if (!reader.matches('}')) {
             do {
                 String key = reader.readString();
@@ -98,8 +116,7 @@ public class JsonDeserializer {
             reader.read('}');
         }
 
-        if (deserializationSupport.hasOrderableChildren(builder)
-                && !builder.hasProperty(OAK_CHILD_ORDER)){
+        if (hasOrderableChildren(builder) && !builder.hasProperty(OAK_CHILD_ORDER)) {
             builder.setProperty(OAK_CHILD_ORDER, childNames, Type.NAMES);
         }
 
@@ -112,48 +129,46 @@ public class JsonDeserializer {
      * @return new property state
      */
     private PropertyState readProperty(String name, JsopReader reader) {
-        if (reader.matches(JsopReader.NUMBER)) {
-            String number = reader.getToken();
-            Long maybeLong = LongUtils.tryParse(number);
-            if (maybeLong == null) {
-                return new DoublePropertyState(name, Double.parseDouble(number));
-            } else {
-                return new LongPropertyState(name, maybeLong);
-            }
-        } else if (reader.matches(JsopReader.TRUE)) {
-            return BooleanPropertyState.booleanProperty(name, true);
-        } else if (reader.matches(JsopReader.FALSE)) {
-            return BooleanPropertyState.booleanProperty(name, false);
-        } else if (reader.matches(JsopReader.STRING)) {
+        // Test for the most common types first
+        if (reader.matches(JsopReader.STRING)) {
             String jsonString = reader.getToken();
-            Type inferredType = deserializationSupport.inferPropertyType(name, jsonString);
+            Type<?> inferredType = inferPropertyType(name, jsonString);
             if (jsonString.startsWith(TypeCodes.EMPTY_ARRAY)) {
-                int type = PropertyType.valueFromName(
-                        jsonString.substring(TypeCodes.EMPTY_ARRAY.length()));
-                return PropertyStates.createProperty(
-                        name, emptyList(), Type.fromTag(type, true));
+                int type = PropertyType.valueFromName(jsonString.substring(TypeCodes.EMPTY_ARRAY.length()));
+                return PropertyStates.createProperty(name, List.of(), Type.fromTag(type, true));
             }
             int split = TypeCodes.split(jsonString);
             if (split != -1) {
                 int type = TypeCodes.decodeType(split, jsonString);
                 String value = TypeCodes.decodeName(split, jsonString);
                 if (type == PropertyType.BINARY) {
-                    return  BinaryPropertyState.binaryProperty(
-                            name, blobHandler.deserialize(value));
+                    return BinaryPropertyState.binaryProperty(name, blobHandler.deserialize(value));
                 } else {
                     //It can happen that a value like oak:Unstructured is also interpreted
                     //as type code. So if oakType is not undefined then use raw value
                     //Also default to STRING in case of UNDEFINED
-                    if (type == PropertyType.UNDEFINED){
-                        Type oakType = inferredType != Type.UNDEFINED ? inferredType : Type.STRING;
+                    if (type == PropertyType.UNDEFINED) {
+                        Type<?> oakType = inferredType != Type.UNDEFINED ? inferredType : Type.STRING;
                         return createProperty(name, jsonString, oakType);
                     }
                     return createProperty(name, value, type);
                 }
             } else {
-                Type oakType = inferredType != Type.UNDEFINED ? inferredType : Type.STRING;
+                Type<?> oakType = inferredType != Type.UNDEFINED ? inferredType : Type.STRING;
                 return createProperty(name, jsonString, oakType);
             }
+        } else if (reader.matches(JsopReader.NUMBER)) {
+            String number = reader.getToken();
+            Long maybeLong = LongUtils.tryParse(number);
+            if (maybeLong == null) {
+                return new DoublePropertyState(name, Double.parseDouble(number));
+            } else {
+                return new LongPropertyState(name, maybeLong);
+            }
+        } else if (reader.matches(JsopReader.TRUE)) {
+            return BooleanPropertyState.booleanProperty(name, true);
+        } else if (reader.matches(JsopReader.FALSE)) {
+            return BooleanPropertyState.booleanProperty(name, false);
         } else {
             throw new IllegalArgumentException("Unexpected token: " + reader.getToken());
         }
@@ -167,27 +182,12 @@ public class JsonDeserializer {
      */
     private PropertyState readArrayProperty(String name, JsopReader reader) {
         int type = PropertyType.STRING;
-        List<Object> values = Lists.newArrayList();
+        List<Object> values = new ArrayList<>();
         while (!reader.matches(']')) {
-            if (reader.matches(JsopReader.NUMBER)) {
-                String number = reader.getToken();
-                Long maybeLong = LongUtils.tryParse(number);
-                if (maybeLong == null) {
-                    type = PropertyType.DOUBLE;
-                    values.add(Double.parseDouble(number));
-                } else {
-                    type = PropertyType.LONG;
-                    values.add(maybeLong);
-                }
-            } else if (reader.matches(JsopReader.TRUE)) {
-                type = PropertyType.BOOLEAN;
-                values.add(true);
-            } else if (reader.matches(JsopReader.FALSE)) {
-                type = PropertyType.BOOLEAN;
-                values.add(false);
-            } else if (reader.matches(JsopReader.STRING)) {
+            // Test for the most common types first
+            if (reader.matches(JsopReader.STRING)) {
                 String jsonString = reader.getToken();
-                Type inferredType = deserializationSupport.inferPropertyType(name, jsonString);
+                Type<?> inferredType = inferPropertyType(name, jsonString);
                 int split = TypeCodes.split(jsonString);
                 if (split != -1) {
                     type = TypeCodes.decodeType(split, jsonString);
@@ -199,7 +199,7 @@ public class JsonDeserializer {
                     } else if (type == PropertyType.DECIMAL) {
                         values.add(Conversions.convert(value).toDecimal());
                     } else {
-                        if (type == PropertyType.UNDEFINED){
+                        if (type == PropertyType.UNDEFINED) {
                             //If determine type is undefined then check if inferred type is defined
                             //else default to STRING
                             type = inferredType != Type.UNDEFINED ? inferredType.tag() : PropertyType.STRING;
@@ -212,6 +212,22 @@ public class JsonDeserializer {
                     type = inferredType != Type.UNDEFINED ? inferredType.tag() : PropertyType.STRING;
                     values.add(jsonString);
                 }
+            } else if (reader.matches(JsopReader.NUMBER)) {
+                String number = reader.getToken();
+                Long maybeLong = LongUtils.tryParse(number);
+                if (maybeLong == null) {
+                    type = PropertyType.DOUBLE;
+                    values.add(Double.parseDouble(number));
+                } else {
+                    type = PropertyType.LONG;
+                    values.add(maybeLong);
+                }
+            } else if (reader.matches(JsopReader.TRUE)) {
+                type = PropertyType.BOOLEAN;
+                values.add(Boolean.TRUE);
+            } else if (reader.matches(JsopReader.FALSE)) {
+                type = PropertyType.BOOLEAN;
+                values.add(Boolean.FALSE);
             } else {
                 throw new IllegalArgumentException("Unexpected token: " + reader.getToken());
             }
@@ -219,40 +235,4 @@ public class JsonDeserializer {
         }
         return createProperty(name, values, Type.fromTag(type, true));
     }
-
-
-    /**
-     * Provides support for inferring types for some common property name and types
-     */
-    private static class DeserializationSupport {
-        static final Set<String> NAME_PROPS = of(JCR_PRIMARYTYPE, JCR_MIXINTYPES);
-        static final Set<String> ORDERABLE_TYPES = of(NT_UNSTRUCTURED);
-        static final DeserializationSupport INSTANCE = new DeserializationSupport();
-
-        Type inferPropertyType(String propertyName, String jsonString) {
-            if (NAME_PROPS.contains(propertyName) && hasSingleColon(jsonString)) {
-                return Type.NAME;
-            }
-            return Type.UNDEFINED;
-        }
-
-        boolean hasOrderableChildren(NodeBuilder builder) {
-            PropertyState primaryType = builder.getProperty(JCR_PRIMARYTYPE);
-            return primaryType != null && ORDERABLE_TYPES.contains(primaryType.getValue(Type.NAME));
-        }
-
-        private boolean hasSingleColon(String jsonString) {
-            // In case the primaryType was encoded then it would be like
-            // "nam:oak:Unstructured". So check if there is only one occurrence
-            // of ':'.
-            int colonCount = 0;
-            for (int i = 0; i < jsonString.length() && colonCount < 2; i++) {
-                if (jsonString.charAt(i) == ':') {
-                    colonCount += 1;
-                }
-            }
-            return colonCount == 1;
-        }
-    }
-
 }