From 7027111eb4f4bfcc281ff0f948b1354a576a9ee8 Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Fri, 3 Jul 2026 14:02:43 +0300 Subject: [PATCH 1/2] Avoid full attribute walk for types without subordinate types Subtype-inclusive attribute lookups (getAllAttributes, hasAttribute) iterate every attribute of the entry calling isSuperTypeOf() for each, because LDAP semantics require matching subordinate attribute types. These lookups run for every entry read: compare assertions, search filter evaluation, password policy attributes. But for the vast majority of attribute types nothing in the schema extends them, and the walk degenerates to an exact map lookup. Cache, per schema instance, the set of attribute types that have at least one subordinate type (rebuilt on schema change, published through volatile references), and take an exact-lookup fast path in the three subtype walkers when the requested type has no subordinates. Falls back to the full walk when no server schema is available. Interleaved A/B under the COMPARE benchmark scenario: +1..+18% throughput (median +7.6%) with a visibly better p99.9; 10,769 tests pass including the full SearchFilterTests matrix, and subtype matching (compare on "name" matching a cn value) verified against a live server. --- .../java/org/opends/server/types/Entry.java | 94 ++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java b/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java index c8087de18f..af8d4d1f3f 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java @@ -13,7 +13,7 @@ * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2016 ForgeRock AS. - * Portions Copyright 2023-2024 3A Systems, LLC. + * Portions Copyright 2023-2026 3A Systems, LLC. */ package org.opends.server.types; @@ -34,6 +34,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; @@ -663,9 +664,68 @@ public List getAllAttributes(AttributeType attributeType, return Collections.emptyList(); } + /** + * The set of attribute types having at least one subordinate type in the + * schema, cached per schema instance. Subtype-inclusive attribute lookups + * run for every entry read (compare assertions, search filters, password + * policy attributes, ...): when the requested type has no subordinates, + * walking every attribute of the entry degenerates to an exact map lookup, + * so cache which types actually need the walk. The set is published before + * the schema reference, so a reader observing the current schema also + * observes the set computed for it. + */ + private static volatile Set typesWithSubordinates = Collections.emptySet(); + private static volatile Schema typesWithSubordinatesSchema; + + private static boolean mayHaveSubordinateTypes(AttributeType attrType) + { + Schema schema; + try + { + schema = DirectoryServer.getInstance().getServerContext().getSchema(); + } + catch (Exception e) + { + // No server schema available (offline tools, early startup): + // conservatively assume subtypes may exist. + return true; + } + if (schema == null) + { + return true; + } + if (schema != typesWithSubordinatesSchema) + { + Set withSubordinates = new HashSet<>(); + for (AttributeType type : schema.getAttributeTypes()) + { + for (AttributeType superior = type.getSuperiorType(); superior != null; + superior = superior.getSuperiorType()) + { + withSubordinates.add(superior); + } + } + typesWithSubordinates = withSubordinates; + typesWithSubordinatesSchema = schema; + } + return typesWithSubordinates.contains(attrType); + } + private void addAttributeTypeOrSubTypeValue(Collection results, AttributeType attrType, Map> attrsMap) { + if (!mayHaveSubordinateTypes(attrType)) + { + // No type in the schema extends attrType: isSuperTypeOf() can only + // match the type itself, so use an exact lookup instead of walking + // every attribute of the entry. + List attributes = attrsMap.get(attrType); + if (attributes != null) + { + results.addAll(attributes); + } + return; + } for (Map.Entry> mapEntry : attrsMap.entrySet()) { if (attrType.isSuperTypeOf(mapEntry.getKey())) @@ -678,6 +738,21 @@ private void addAttributeTypeOrSubTypeValue(Collection results, Attri private void addAttributeTypeOrSubTypeValue(Collection results, AttributeDescription attrDesc, Map> attrsMap) { + if (!mayHaveSubordinateTypes(attrDesc.getAttributeType())) + { + List attributes = attrsMap.get(attrDesc.getAttributeType()); + if (attributes != null) + { + for (Attribute attribute : attributes) + { + if (attrDesc.isSuperTypeOf(attribute.getAttributeDescription())) + { + results.add(attribute); + } + } + } + return; + } for (Map.Entry> mapEntry : attrsMap.entrySet()) { if (!attrDesc.getAttributeType().isSuperTypeOf(mapEntry.getKey())) @@ -697,6 +772,23 @@ private void addAttributeTypeOrSubTypeValue(Collection results, Attri private boolean hasAttributeOrSubType(AttributeDescription attrDesc, Map> attrsMap) { + if (!mayHaveSubordinateTypes(attrDesc.getAttributeType())) + { + List attributes = attrsMap.get(attrDesc.getAttributeType()); + if (attributes != null) + { + for (Attribute attribute : attributes) + { + // It's possible that there could be an attribute without any values, + // which we should treat as not having the requested attribute. + if (!attribute.isEmpty() && attrDesc.isSuperTypeOf(attribute.getAttributeDescription())) + { + return true; + } + } + } + return false; + } for (Map.Entry> mapEntry : attrsMap.entrySet()) { if (!attrDesc.getAttributeType().isSuperTypeOf(mapEntry.getKey())) From 52df3388a63f1584dbe67fbfe46814d8a446febd Mon Sep 17 00:00:00 2001 From: Valera V Harseko Date: Sat, 4 Jul 2026 11:13:28 +0300 Subject: [PATCH 2/2] Harden the subordinate-types cache: atomic holder and place-holder guard Hold the schema and the set computed for it in one immutable object swapped atomically, so a reader can never pair a set with the wrong schema even when two threads rebuild the cache concurrently (previously the invariant relied on the field write/read order); this also halves the volatile reads on the hot path. Skip the exact-lookup fast path for place-holder attribute types: isSuperTypeOf() matches them against schema-defined types by name, which a hash lookup keyed on the numeric OID cannot honor. --- .../java/org/opends/server/types/Entry.java | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java b/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java index af8d4d1f3f..edf19f7e4e 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java @@ -665,20 +665,38 @@ public List getAllAttributes(AttributeType attributeType, } /** - * The set of attribute types having at least one subordinate type in the + * The set of attribute types having at least one subordinate type in a * schema, cached per schema instance. Subtype-inclusive attribute lookups * run for every entry read (compare assertions, search filters, password * policy attributes, ...): when the requested type has no subordinates, * walking every attribute of the entry degenerates to an exact map lookup, - * so cache which types actually need the walk. The set is published before - * the schema reference, so a reader observing the current schema also - * observes the set computed for it. + * so cache which types actually need the walk. The schema and the set + * computed for it are held in one immutable holder swapped atomically, so + * a reader can never pair a set with the wrong schema. */ - private static volatile Set typesWithSubordinates = Collections.emptySet(); - private static volatile Schema typesWithSubordinatesSchema; + private static final class TypesWithSubordinates + { + private final Schema schema; + private final Set types; + + private TypesWithSubordinates(Schema schema, Set types) + { + this.schema = schema; + this.types = types; + } + } + + private static volatile TypesWithSubordinates typesWithSubordinates = + new TypesWithSubordinates(null, Collections. emptySet()); private static boolean mayHaveSubordinateTypes(AttributeType attrType) { + if (attrType.isPlaceHolder()) + { + // isSuperTypeOf() matches place-holder types against schema-defined + // types by name, which an exact map lookup cannot honor: keep the walk. + return true; + } Schema schema; try { @@ -694,7 +712,8 @@ private static boolean mayHaveSubordinateTypes(AttributeType attrType) { return true; } - if (schema != typesWithSubordinatesSchema) + TypesWithSubordinates cached = typesWithSubordinates; + if (cached.schema != schema) { Set withSubordinates = new HashSet<>(); for (AttributeType type : schema.getAttributeTypes()) @@ -705,10 +724,10 @@ private static boolean mayHaveSubordinateTypes(AttributeType attrType) withSubordinates.add(superior); } } - typesWithSubordinates = withSubordinates; - typesWithSubordinatesSchema = schema; + cached = new TypesWithSubordinates(schema, withSubordinates); + typesWithSubordinates = cached; } - return typesWithSubordinates.contains(attrType); + return cached.types.contains(attrType); } private void addAttributeTypeOrSubTypeValue(Collection results, AttributeType attrType,