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

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


The following commit(s) were added to refs/heads/1.22 by this push:
     new de0eaad451 OAK-10643: MongoDocumentStore: improve diagnostics for too 
large docs (backport to 1.22) (#2755)
de0eaad451 is described below

commit de0eaad451d948d98bb4456385bd3f5800737d8e
Author: Julian Reschke <[email protected]>
AuthorDate: Fri Feb 20 13:41:29 2026 +0100

    OAK-10643: MongoDocumentStore: improve diagnostics for too large docs 
(backport to 1.22) (#2755)
---
 .../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 4b42d3f53b..fe2ca3d104 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
@@ -925,6 +925,7 @@ public class MongoDocumentStore implements DocumentStore {
         }
         final Stopwatch watch = startWatch();
         boolean newEntry = false;
+
         try {
             // get modCount of cached document
             Long modCount = null;
@@ -992,6 +993,7 @@ public class MongoDocumentStore implements DocumentStore {
             if (checkConditions && oldNode == null) {
                 return null;
             }
+
             T oldDoc = convertFromDBObject(collection, oldNode);
             if (oldDoc != null) {
                 if (collection == Collection.NODES) {
@@ -1015,8 +1017,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 ='{}'. ",
@@ -1044,6 +1046,23 @@ public class MongoDocumentStore implements DocumentStore 
{
         return doc;
     }
 
+    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 57a0b04a9b..8c2ce28e68 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
@@ -23,12 +23,14 @@ import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.sql.Timestamp;
 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;
 
@@ -201,6 +203,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