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:
Mrinal Chanshetty 2026-11-06 16:37:56 -08:00
parent 3ace4da55f
commit cc92bb48a6
2 changed files with 38 additions and 5 deletions

View file

@ -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:

View file

@ -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])],