Avoid reading session from the database multiple times during a big pipe request

Created on 12 January 2024, 6 months ago
Updated 28 March 2024, 3 months ago

Problem/Motivation

Found in 📌 Add authenticated user umami performance tests Fixed .

Bigpipe does rendering after the initial response is sent. Sending the initial response requires the session to be closed. This means that big pipe has to read the session again, this results in running the session read query twice in one request.

While this only reduces one database query, it does so when the dynamic render cache is fully warmed, so it's quite a high percentage of the non-cache database queries on those requests (15 down to 14). Once you take into account that most of the remaining 14 queries are cache tags (can also be moved to redis/memcache) and state (open issue to move to a cache collector), the percentage reduction is even higher.

Steps to reproduce

Proposed resolution

Add the session data to a property on the session handler, this allows the data to be got from there if the session is read twice.

Stacked on 📌 Add authenticated user umami performance tests Fixed for performance test coverage.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

📌 Task
Status

Fixed

Version

10.3

Component
BigPipe 

Last updated about 13 hours ago

Created by

🇬🇧United Kingdom catch

Live updates comments and jobs are added and updated live.
  • Performance

    It affects performance. It is often combined with the Needs profiling tag.

Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @catch
  • Status changed to Needs work 6 months ago
  • 🇬🇧United Kingdom catch

    Two test failures, one is StandardPerformanceTest doing less database queries, the other is a json api test that needs investigation.

  • Status changed to Needs review 5 months ago
  • 🇬🇧United Kingdom catch

    Tests should be green. Have to rebuild the container in the JSON API test, but the fact we only have to do that in a single test in core suggests this won't be disruptive in general.

  • Status changed to RTBC 5 months ago
  • 🇺🇸United States smustgrave

    Change of using a variable instead seems good to me.

    Seems small enough that I can mark now vs waiting a week.

  • Status changed to Needs review 5 months ago
  • 🇬🇧United Kingdom longwave UK

    By adding a static cache are we increasing the changes of a race condition here, if a user has multiple page requests in flight and both of them want to write to the session?

  • 🇬🇧United Kingdom catch

    I don't think it increases it or at least not compared to a non-bigpipe response.

    The session is read right at the beginning of the request and that content is used until it's closed, so for a non-bigpipe response, it's only read once anyway, and anything that happens before the write would lead to a similar race condition. The static cache has no effect in this situation.

    With big pipe, the issue is that we're reading the session, closing the session, then reading it again to close it again later, but the relative amount of the time between the session initially getting read and eventually getting written to isn't longer with bigpipe than it is with the 'normal' response. Another way to approach this would be to somehow make it so that the session doesn't get closed twice on bigpipe responses, that would mean no need for the static cache then, but I couldn't figure out any way to do that yet. Might look again though in case it's doable.

  • Merge request !6441Resolve #3414287 "Big pipe" → (Open) created by catch
  • Pipeline finished with Failed
    5 months ago
    Total: 265s
    #87428
  • Pipeline finished with Failed
    5 months ago
    #87451
  • Pipeline finished with Success
    5 months ago
    Total: 579s
    #87458
  • 🇬🇧United Kingdom catch

    Another way to approach this would be to somehow make it so that the session doesn't get closed twice on bigpipe responses, that would mean no need for the static cache then, but I couldn't figure out any way to do that yet.

    Thought of a way that seems OK, and completely gets rid of the need for the static cache.

    Originally I was looking at decorating Drupal\Core\StackMiddleWare\Session but that would have led to a lot of duplicated code.

    Instead, I added a new ResponseKeepSessionOpenInterface, have BigPipeResponse implement that, then have the session middleware check for it - when it's there, we skip closing the session after sending the response, which allows big pipe to take care of closing the session itself later, but without having to first reload it. That way we don't have to special-case big_pipe anywhere

  • Pipeline finished with Success
    5 months ago
    Total: 605s
    #87476
  • Pipeline finished with Success
    5 months ago
    Total: 525s
    #87483
  • 🇬🇧United Kingdom catch
  • Status changed to RTBC 5 months ago
  • 🇺🇸United States smustgrave

    Change makes sense and thanks to your performanceTest can see that 1 less call is made, so appears to be working.

  • 🇬🇧United Kingdom longwave UK

    Symfony's SessionInterface::save() says

         * This method is generally not required for real sessions as
         * the session will be automatically saved at the end of
         * code execution.
    

    so do we have to explicitly save the session ourselves at all?

  • Pipeline finished with Canceled
    5 months ago
    Total: 549s
    #91671
  • 🇬🇧United Kingdom catch

    Pushed a commit to try that, but it doesn't look very encouraging - broke logins: https://git.drupalcode.org/project/drupal/-/pipelines/91673

    Maybe worth exploring more in a follow-up - maybe we would just need to explicitly save the session when it's new or something like that but could be tricky.

  • Pipeline finished with Success
    5 months ago
    Total: 484s
    #91678
  • 🇬🇧United Kingdom longwave UK

    Should we tag the new interface as @internal for now to try and discourage use, and then if we land 📌 Check if we can rely on Symfony to save the session for us Active then we can remove it again more easily?

  • 🇬🇧United Kingdom catch

    That makes sense. Added @internal. It only exists to avoid special-casing BigPipeResponse in /core.

  • Pipeline finished with Canceled
    4 months ago
    Total: 94s
    #93032
  • Pipeline finished with Success
    4 months ago
    Total: 493s
    #93033
  • Pipeline finished with Success
    4 months ago
    Total: 484s
    #101409
  • 🇬🇧United Kingdom alexpott 🇪🇺🌍

    Committed and pushed 213906e286 to 11.x and 522404706e to 10.3.x. Thanks!

    • alexpott committed 52240470 on 10.3.x
      Issue #3414287 by catch, longwave: Avoid reading session from the...
  • Status changed to Fixed 4 months ago
    • alexpott committed 213906e2 on 11.x
      Issue #3414287 by catch, longwave: Avoid reading session from the...
  • 🇧🇪Belgium Wim Leers Ghent 🇧🇪🇪🇺

    Another DB query bites the dust — nice one!

    (CR seems to not be valuable here, because nothing else should need this, right?)

    I'm curious to see if this will break https://www.drupal.org/project/big_pipe_sessionless 😇 Happy to update that. Will know after tomorrow's scheduled CI pipeline 👍

  • 🇬🇧United Kingdom catch

    I did originally think about a CR, but now we've made the interface internal I think we should explicitly not have one, and try to remove it again if we can rely on session auto saving.

  • Automatically closed - issue fixed for 2 weeks with no activity.

  • 🇬🇧United Kingdom catch

    Tagging for release highlights, not enough by itself but combined with 📌 Optimize user logins by avoiding duplicate entity queries Needs work I think we can add something.

Production build 0.69.0 2024