Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions api/utils/keycloak_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ def validate_token(
from authorization.models import User

user = User.objects.get(id=user_id)
user.save()
# Deliberately no user.save() here. It rewrote every
# column of an unchanged row on every authenticated
# request, taking a row lock for no benefit - and since
# concurrent requests for one user contend on that single
# row, it serialized them against each other.

# NOTE: Organizations are managed in DataSpace database, not Keycloak
# Organization memberships should be created/managed through DataSpace's
Expand Down Expand Up @@ -412,13 +416,35 @@ def sync_user_from_keycloak(
)

if user:
# Update existing user
user.keycloak_id = keycloak_id
user.username = username
user.email = email
user.first_name = user_info.get("given_name", "") or user.first_name
user.last_name = user_info.get("family_name", "") or user.last_name
user.is_active = True
# Update existing user, but only write when something actually
# changed. The unconditional save this replaces was the cause
# of the connection exhaustion: every login rewrote the same
# row, so concurrent logins for one user queued on a row lock
# (pg_stat_activity showed "Lock: tuple" and
# "Lock: transactionid" on UPDATE "ds_user"), each holding a
# database connection while it waited.
desired = {
"keycloak_id": keycloak_id,
"username": username,
"email": email,
"first_name": user_info.get("given_name", "") or user.first_name,
"last_name": user_info.get("family_name", "") or user.last_name,
"is_active": True,
"is_staff": "admin" in roles,
"is_superuser": "admin" in roles,
}
changed = [
field
for field, value in desired.items()
if getattr(user, field) != value
]
if changed:
for field in changed:
setattr(user, field, desired[field])
# update_fields keeps the UPDATE narrow instead of
# rewriting every column.
user.save(update_fields=changed)
return user
else:
# Create new user
user = User(
Expand Down
Loading