mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
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.
This commit is contained in:
parent
3ace4da55f
commit
cc92bb48a6
2 changed files with 38 additions and 5 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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])],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue