This is an automated email from the ASF dual-hosted git repository. reschke pushed a commit to branch OAK-10643-backport in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
commit 7bd6752e4ed427372836b1c9ad09514246c1bd3f Author: Julian Reschke <[email protected]> AuthorDate: Fri Feb 20 10:28:45 2026 +0100 OAK-10643: MongoDocumentStore: improve diagnostics for too large docs (backport to 1.22) --- .../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) {
