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

reschke 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 66b8bef296 OAK-10643: MongoDocumentStore: improve diagnostics for too 
large docs (#1306)
66b8bef296 is described below

commit 66b8bef296b132e821a26b2486cfa5339393395b
Author: Julian Reschke <[email protected]>
AuthorDate: Wed Feb 14 19:37:51 2024 +0100

    OAK-10643: MongoDocumentStore: improve diagnostics for too large docs 
(#1306)
    
    * OAK-10643: MongoDocumentStore: improve diagnostics for too large docs
    
    * OAK-10643: MongoDocumentStore: improve diagnostics for too large docs - 
simplify stats code
    
    * OAK-10643: MongoDocumentStore: improve diagnostics for too large docs - 
remove unneeded change
---
 .../plugins/document/mongo/MongoDocumentStore.java | 23 ++++++-
 .../oak/plugins/document/util/Utils.java           | 72 ++++++++++++++++++++++
 2 files changed, 93 insertions(+), 2 deletions(-)

diff --git 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
index 604829b38d..3303f0fb75 100644
--- 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
+++ 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/mongo/MongoDocumentStore.java
@@ -1056,6 +1056,7 @@ public class MongoDocumentStore implements DocumentStore {
         }
         final Stopwatch watch = startWatch();
         boolean newEntry = false;
+
         try {
             // get modCount of cached document
             Long modCount = null;
@@ -1123,6 +1124,7 @@ public class MongoDocumentStore implements DocumentStore {
             if (checkConditions && oldNode == null) {
                 return null;
             }
+
             T oldDoc = convertFromDBObject(collection, oldNode);
             if (oldDoc != null) {
                 if (collection == Collection.NODES) {
@@ -1146,8 +1148,8 @@ public class MongoDocumentStore implements DocumentStore {
             return oldDoc;
         } catch (MongoWriteException e) {
             WriteError werr = e.getError();
-            LOG.error("Failed to update the document with Id={} with 
MongoWriteException message = '{}'.",
-                    updateOp.getId(), werr.getMessage());
+            LOG.error("Failed to update the document with Id={} with 
MongoWriteException message = '{}'. Document statistics: {}.",
+                    updateOp.getId(), werr.getMessage(), 
produceDiagnostics(collection, updateOp.getId()), e);
             throw handleException(e, collection, updateOp.getId());
         } catch (MongoCommandException e) {
             LOG.error("Failed to update the document with Id={} with 
MongoCommandException message ='{}'. ",
@@ -1164,6 +1166,23 @@ public class MongoDocumentStore implements DocumentStore 
{
         }
     }
 
+    private <T extends Document> String produceDiagnostics(Collection<T> col, 
String id) {
+        StringBuilder t = new StringBuilder();
+
+        try {
+            T doc = find(col, id);
+            if (doc != null) {
+                t.append("_id: " + doc.getId() + ", _modCount: " + 
doc.getModCount() + ", memory: " + doc.getMemory());
+                t.append("; Contents: ");
+                t.append(Utils.mapEntryDiagnostics(doc.entrySet()));
+            }
+        } catch (Throwable thisIsBestEffort) {
+            t.append(thisIsBestEffort.getMessage());
+        }
+
+        return t.toString();
+    }
+
     /**
      * Try to apply all the {@link UpdateOp}s with at least MongoDB requests as
      * possible. The return value is the list of the old documents (before
diff --git 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java
 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java
index 793e3a9433..0d4999c761 100644
--- 
a/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java
+++ 
b/oak-store-document/src/main/java/org/apache/jackrabbit/oak/plugins/document/util/Utils.java
@@ -26,12 +26,14 @@ import java.sql.Timestamp;
 import java.time.Instant;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
@@ -206,6 +208,76 @@ public class Utils {
         return (int) size;
     }
 
+    private static class PropertyStats {
+        public int count;
+        public int size;
+    }
+
+    public static String mapEntryDiagnostics(@NotNull Set<Entry<String, 
Object>> entries) {
+        StringBuilder t = new StringBuilder();
+        Map<String, PropertyStats> stats = new TreeMap<>();
+
+        for (Map.Entry<String, Object> member : entries) {
+            String key = member.getKey();
+
+            PropertyStats stat = stats.get(key);
+            if (stat == null) {
+                stat = new PropertyStats();
+            }
+
+            Object o = member.getValue();
+            if (o instanceof String) {
+                stat.size += StringUtils.estimateMemoryUsage((String) o);
+                stat.count += 1;
+            } else if (o instanceof Long) {
+                stat.size += 16;
+                stat.count += 1;
+            } else if (o instanceof Boolean) {
+                stat.size += 8;
+                stat.count += 1;
+            } else if (o instanceof Integer) {
+                stat.size += 8;
+                stat.count += 1;
+            } else if (o instanceof Map) {
+                @SuppressWarnings("unchecked")
+                Map<Object, Object> x = (Map<Object, Object>)o;
+                stat.size += 8 + Utils.estimateMemoryUsage(x);
+                stat.count += x.size();
+            } else if (o == null) {
+                // zero
+            } else {
+                throw new IllegalArgumentException("Can't estimate memory 
usage of " + o);
+            }
+
+            stats.put(key, stat);
+        }
+
+        List<Map.Entry<String, PropertyStats>> sorted = new 
ArrayList<Entry<String, PropertyStats>>(stats.entrySet());
+
+        // sort by estimated entry size, highest first
+        Collections.sort(sorted, new Comparator<Map.Entry<String, 
PropertyStats>>() {
+            @Override
+            public int compare(Entry<String, PropertyStats> o1, Entry<String, 
PropertyStats> o2) {
+                return o2.getValue().size - o1.getValue().size;
+            }
+        });
+
+        String sep = "";
+        for (Map.Entry<String, PropertyStats> member : sorted) {
+            String name = member.getKey();
+            PropertyStats stat = member.getValue();
+            t.append("'" + sep + name + "': ");
+            sep = ", ";
+            if (stat.count <= 1) {
+                t.append(stat.size + " bytes");
+            } else {
+                t.append(stat.size + " bytes in " + stat.count + " entries (" 
+ stat.size / stat.count + " avg)");
+            }
+        }
+
+        return t.toString();
+    }
+
     public static String escapePropertyName(String propertyName) {
         int len = propertyName.length();
         if (len == 0) {

Reply via email to