Avoid full attribute walk for types without subordinate types#679
Open
vharseko wants to merge 2 commits into
Open
Avoid full attribute walk for types without subordinate types#679vharseko wants to merge 2 commits into
vharseko wants to merge 2 commits into
Conversation
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.
maximthomas
approved these changes
Jul 3, 2026
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Second result of the COMPARE hot-path profiling (after #674). CPU profiling
shows ~4% of server CPU in
Entry.hasAttributeOrSubType/AttributeType.isSuperTypeOf: subtype-inclusive attribute lookups(
getAllAttributes,hasAttribute) iterate every attribute of theentry, calling
isSuperTypeOf()for each, because LDAP semantics requirematching subordinate attribute types (a compare on
namemust matchcn,sn, …). These lookups run for every entry read — compare assertions,search filter evaluation, password policy attribute reads on bind — yet for
the vast majority of attribute types (
uid,l,userPassword, …)nothing in the schema extends them, and the walk degenerates to an exact
map lookup.
Change
Entrycaches, per schema instance, the set of attribute types thathave at least one subordinate type (one pass over
schema.getAttributeTypes()walking the superior chains; rebuilt whenthe schema reference changes; the schema and its set are held in one
immutable holder swapped atomically — see the review follow-up below).
addAttributeTypeOrSubTypeValue×2,hasAttributeOrSubType) take an exact-map-lookup fast path when therequested type has no subordinates —
isSuperTypeOf()can only matchthe type itself in that case, so behaviour is identical.
code conservatively falls back to the full walk.
removed.
Benchmark
Same harness as #674: packaged server,
jebackend, 5,000 users, JDK 11,8-core host, 200 persistent connections bound as regular users issuing
COMPAREs on random users,
caffeinate, warm-up run after every restart,interleaved order old→new→new→old→old→new:
Median +7.6%, consistently better tail latency; 0 errors in all runs. BIND
and search filter evaluation take the same fast path (password attribute
reads and filter matching go through the same walkers).
Review follow-up (52df338)
The publication order (set before schema, read in the reverse order)
made the pairing safe for a single rebuilder, but two threads
rebuilding concurrently for different schema instances could interleave
the four writes and skew the pair — and the invariant lived entirely in
the ordering of two assignments. They are now one immutable holder
swapped atomically, which also halves the volatile reads on the hot
path.
defined in the schema):
isSuperTypeOf()matches place-holders againstschema-defined types by name, which an exact hash lookup keyed on
the numeric OID cannot honor. Without the guard, a lookup using a
place-holder type could miss a same-named attribute held by an
Entrydecoded under an older schema that still defined the type. (For
schema-defined types the equivalence holds as before:
AttributeType.equals()/hashCode()compare the numeric OID only.)Testing
mvn -P precommit -pl opendj-server-legacy verifyforTestEntry,EntrySchemaCheckingTestCase,SearchFilterTests(10,468 — the fullfilter matching matrix),
SearchOperationTestCase,CompareOperationTestCase,PasswordPolicyTestCase:10,769 tests, 0 failures.
uid(fast path) → COMPARE_TRUE; compare on
namewith acnvalue(subtype walk) → COMPARE_TRUE; wrong value → COMPARE_FALSE.
Files
opendj-server-legacy/src/main/java/org/opends/server/types/Entry.java