From cc92bb48a65f83d44d690c148cf02386a0983192 Mon Sep 17 00:00:00 2001 From: Mrinal Chanshetty Date: Fri, 6 Nov 2026 16:37:56 -0800 Subject: [PATCH] perf(ldap): resolve each member once per directory snapshot Users in many groups were looked up once per group; the load test made 2000 lookups for 494 distinct users at 100 groups. Results, including missing-entry errors, are reused within a snapshot only. --- litellm/proxy/common_utils/ldap_directory.py | 17 ++++++++---- .../proxy/common_utils/test_ldap_directory.py | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/common_utils/ldap_directory.py b/litellm/proxy/common_utils/ldap_directory.py index d37694786e8..000b3362f7b 100644 --- a/litellm/proxy/common_utils/ldap_directory.py +++ b/litellm/proxy/common_utils/ldap_directory.py @@ -219,7 +219,11 @@ def _resolve_member( def _read_group( - connection: LDAPConnection, settings: LDAPSyncSettings, entry: _Entry, stop: Event + connection: LDAPConnection, + settings: LDAPSyncSettings, + entry: _Entry, + stop: Event, + members: dict[str, str | DirectoryError], ) -> DirectoryGroup | DirectoryError: _check_cancelled(stop) team_id: Final = _one(entry, settings.group_id_attribute) @@ -228,9 +232,10 @@ def _read_group( return DirectoryError("LDAP group ID or name is missing or multivalued", entry.dn) if any(";range=" in key.lower() for key in entry.attributes): return DirectoryError("LDAP ranged membership responses are not supported", entry.dn) - identities: Final = tuple( - _resolve_member(connection, settings, value, stop) for value in _values(entry, settings.member_attribute) - ) + for value in _values(entry, settings.member_attribute): + if value not in members: + members[value] = _resolve_member(connection, settings, value, stop) + identities: Final = tuple(members[value] for value in _values(entry, settings.member_attribute)) error: Final = next((identity for identity in identities if isinstance(identity, DirectoryError)), None) if error is not None: return DirectoryError(error.reason, entry.dn) @@ -274,7 +279,9 @@ def read_directory( ) if len(group_ids) != len(frozenset(group_ids)): return DirectoryError("LDAP group IDs are not unique within the selected groups") - groups: Final = tuple(_read_group(client, settings, entry, cancellation) for entry in entries) + # Users often belong to many groups; resolve each member value once per snapshot. + members: Final[dict[str, str | DirectoryError]] = {} # mutable-ok: per-snapshot member lookup cache + groups: Final = tuple(_read_group(client, settings, entry, cancellation, members) for entry in entries) _check_cancelled(cancellation) return groups finally: diff --git a/tests/unit/proxy/common_utils/test_ldap_directory.py b/tests/unit/proxy/common_utils/test_ldap_directory.py index 9322e15b12b..d3ac73af8f8 100644 --- a/tests/unit/proxy/common_utils/test_ldap_directory.py +++ b/tests/unit/proxy/common_utils/test_ldap_directory.py @@ -132,6 +132,32 @@ def test_deleted_member_entry_skips_only_its_group(): assert connection.closed +def test_member_shared_by_groups_is_looked_up_once_per_snapshot(): + connection = Connection( + [group("project-a", member=[ALICE, BOB]), group("project-b", member=[ALICE])], + {ALICE: [user("alice@example.org")], BOB: [user("bob@example.org")]}, + ) + assert read_directory(SETTINGS, connection) == ( + DirectoryGroup("project-a", "project-a", ("alice@example.org", "bob@example.org")), + DirectoryGroup("project-b", "project-b", ("alice@example.org",)), + ) + assert [search[0] for search in connection.searches] == [ALICE, BOB] + + +def test_deleted_member_shared_by_groups_skips_each_group_after_one_lookup(): + connection = Connection( + [group("project-a", member=[BOB]), group("project-b", member=[ALICE, BOB]), group("project-c", member=[ALICE])], + {ALICE: [user("alice@example.org")]}, + raising={BOB: (32, LDAPNoSuchObjectResult(result=32, description="noSuchObject"))}, + ) + assert read_directory(SETTINGS, connection) == ( + DirectoryError("LDAP member entry does not exist", "cn=project-a,ou=groups,dc=example,dc=org"), + DirectoryError("LDAP member entry does not exist", "cn=project-b,ou=groups,dc=example,dc=org"), + DirectoryGroup("project-c", "project-c", ("alice@example.org",)), + ) + assert [search[0] for search in connection.searches] == [BOB, ALICE] + + def test_unreadable_member_entry_still_aborts_snapshot(): connection = Connection( [group("project-a", member=[ALICE, BOB]), group("project-b", member=[ALICE])],