Skip to content

fix(sessions): skip unnecessary FOR UPDATE lock on app/user state rows#4656

Closed
giulio-leone wants to merge 2 commits intogoogle:mainfrom
giulio-leone:fix/skip-unnecessary-for-update-lock
Closed

fix(sessions): skip unnecessary FOR UPDATE lock on app/user state rows#4656
giulio-leone wants to merge 2 commits intogoogle:mainfrom
giulio-leone:fix/skip-unnecessary-for-update-lock

Conversation

@giulio-leone
Copy link

Summary

Fixes #4655

DatabaseSessionService.append_event() unconditionally acquires SELECT ... FOR UPDATE on both app_states and user_states tables, even when the event carries no state delta for those scopes.

Problem

Since app_states is keyed by app_name alone, all concurrent append_event calls within the same app serialize on this single row lock, even when they only carry session-scoped state (the vast majority of events). The user_states lock similarly serializes all calls for the same (app_name, user_id) pair.

This was partially addressed by #764, which fixed the unnecessary UPDATE (merge) side. However, the SELECT ... FOR UPDATE (row-level lock acquisition) still happened unconditionally.

Fix

Pre-analyze the event's state_delta before entering the transaction to determine which scopes actually need write locks:

# Only acquire FOR UPDATE when the event actually modifies that scope
has_app_delta = bool(state_deltas.get("app"))
has_user_delta = bool(state_deltas.get("user"))

storage_app_state = await _select_required_state(
    ...,
    use_row_level_locking=use_row_level_locking and has_app_delta,
)
storage_user_state = await _select_required_state(
    ...,
    use_row_level_locking=use_row_level_locking and has_user_delta,
)

The state rows are still fetched (needed for the stale-timestamp reload path), but without the FOR UPDATE clause when there's nothing to write.

This also eliminates a redundant call to extract_state_delta() — the deltas are now computed once and reused.

Impact

  • Events with no app:/user: state deltas no longer block on the shared row lock
  • Concurrent append_event throughput improves significantly for apps with moderate concurrency
  • No behavioral change — the same state merges happen when deltas are present

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a performance bottleneck in the session service by optimizing database locking behavior. It ensures that row-level write locks are only acquired when necessary, preventing serialization of concurrent operations that do not modify shared application or user state. This change enhances the scalability and efficiency of event processing without altering existing functional behavior.

Highlights

  • Conditional Row-Level Locking: Implemented logic to pre-analyze event state deltas and apply SELECT ... FOR UPDATE locks on app_states and user_states tables only when app or user state changes are actually present in the event. This avoids unnecessary serialization of concurrent append_event calls.
  • Performance Improvement: Significantly improved throughput for concurrent append_event operations, especially for events carrying only session-scoped state, by reducing contention on shared row locks.
  • Code Refactoring: Eliminated a redundant call to _session_util.extract_state_delta() by performing the extraction once at the beginning of the append_event method and reusing the result.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/google/adk/sessions/database_session_service.py
    • Introduced variables has_app_delta, has_user_delta, and state_deltas to store pre-analyzed state delta information.
    • Modified the _select_required_state calls for StorageAppState and StorageUserState to conditionally apply use_row_level_locking based on the presence of app and user state deltas, respectively.
    • Refactored the extraction of state_deltas to occur once at the beginning of the append_event method, removing a duplicate call later in the function.
Activity
  • No human activity (comments, reviews, etc.) has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@google-cla
Copy link

google-cla bot commented Feb 28, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@adk-bot adk-bot added the services [Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc label Feb 28, 2026
@adk-bot
Copy link
Collaborator

adk-bot commented Feb 28, 2026

Response from ADK Triaging Agent

Hello @giulio-leone, thank you for your contribution!

It looks like the Contributor License Agreement (CLA) check has failed. Before we can merge this pull request, you'll need to sign the CLA. You can find more information at https://cla.developers.google.com/.

Thank you!

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses a performance bottleneck by avoiding unnecessary SELECT ... FOR UPDATE locks on the app_states and user_states tables. The approach of pre-analyzing the event's state delta to conditionally acquire locks is sound and should significantly improve concurrency for append_event calls. The refactoring to compute the state deltas only once is also a good efficiency gain. I have one minor suggestion to improve the conciseness of the new logic.

Comment on lines 557 to 565
has_app_delta = False
has_user_delta = False
state_deltas = None
if event.actions and event.actions.state_delta:
state_deltas = _session_util.extract_state_delta(
event.actions.state_delta
)
has_app_delta = bool(state_deltas.get("app"))
has_user_delta = bool(state_deltas.get("user"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for determining if there are app or user state deltas can be made more concise. By using a conditional expression to initialize state_deltas and then deriving the boolean flags directly, you can reduce the number of lines and improve readability without changing the logic.

        state_deltas = (
            _session_util.extract_state_delta(event.actions.state_delta)
            if event.actions and event.actions.state_delta
            else None
        )
        has_app_delta = bool(state_deltas and state_deltas.get("app"))
        has_user_delta = bool(state_deltas and state_deltas.get("user"))

@giulio-leone giulio-leone force-pushed the fix/skip-unnecessary-for-update-lock branch from 167a92f to d198ae5 Compare February 28, 2026 14:38
DatabaseSessionService.append_event() unconditionally acquires SELECT ...
FOR UPDATE on both app_states and user_states tables, even when the event
carries no state delta for those scopes.

Since app_states is keyed by app_name alone, all concurrent append_event
calls within the same app serialize on this single row lock, even when
they only carry session-scoped state (the vast majority of events).

Fix: pre-analyze the event's state_delta before acquiring locks and only
use FOR UPDATE when the corresponding scope actually has changes. This
also avoids a redundant call to extract_state_delta later in the method.

Fixes google#4655
Addresses review feedback: use a single conditional expression instead
of separate variable initializations and if block.
@giulio-leone giulio-leone force-pushed the fix/skip-unnecessary-for-update-lock branch from d198ae5 to 2abcbdf Compare February 28, 2026 14:39
@giulio-leone
Copy link
Author

Closing — CLA not yet signed. Will resubmit when ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

services [Component] This issue is related to runtime services, e.g. sessions, memory, artifacts, etc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DatabaseSessionService.append_event: unnecessary FOR UPDATE lock on app_states/user_states when no state delta exists

2 participants