Thanks for the link but I'm not using asynchronous serialization.
I thought some more about the issue and I think I figured it out. My setup
looks like this:
1. Spring Session Redis
2. [Session Cache] (Not used because it is transient and stored with
writeObject/readObject and does not get serialized into Redis as we do not
use Java serialization)
3. PerSessionPageStore (Application-level cache held in memory)
4. RedisDataStore (Synchronous)
1. If i disable second-level cache or use the serializing second-level
cache provided by the DefaultPageManager, there are no issues
2. As soon as I enable the PerSessionPageStore we run into concurrency
I first thought that there were some thread-safety issues
with PerSessionPageStore but that is not the case because even a fully
synchronized version shows these problems.
The reason why disabling the 2nd-level cache or using a serializing variant
works, is because they do not operate on the same *instance* of the page.
Each thread gets their
own instance because the page is deserialized before being accessed.
PerSessionPageStore stores the page in memory without serializing it, thus
all threads share the same instance. This is also the case when using the
session cache or
the session-based stores, but the PageAccessSynchronizer bound to the
session takes care of ensuring that only single request can manipulate the
page at any given time.
So how does the synchronizer work? It keeps a Map in the
session and checks whether the page is locked on every request. In a
environment this works as expected as the session object lives in the
servlet container and is the same for each concurrent request. In my case,
is not provided by the servlet container, but fetched from Redis by Spring
Session on every request. So each concurrent thread has *their own version*
of the session and
the locks are *not shared between threads* because the session is only
saved back to Redis after the request has finished.
So the problematic flow looks like this
1. A request comes in, we fetch the session from Redis, the request
acquires the session-scoped lock and starts processing
2. Before the request is finished, another request comes in, fetches the
session from Redis, the lock map is empty because the state of request #1
has not been persisted to Redis
3. Now both requests can modify the page and we run into concurrency issues
PageAccessSynchronizer does not work with Spring Session or other solutions
that replace the servlet container session.
1. We could ensure that session locks are updated in Redis immediately but
that still leaves a couple of milliseconds for race conditions and adds a
lot of overhead
2. We could use an application-scoped PageAccessSynchronizer. This solves
the problem as long as sessions are sticky and all concurrent requests are
sent to the same server.
3. If we want to use non-sticky session we could use Redis locks for
implementing a global PageAccessSynchronizer
I would like to go with solution #2 for now. The problem is
that PageAccessSynchronizer is not an interface.
Would it be possible to extract an interface so I can easily implement
access synchronizers with different scopes?
On Wed, Feb 26, 2020 at 12:24 PM Sven Meier wrote:
> Hi Thomas,
> Im wondering whether you're running into
> I've been working on a solution to that problem, which is caused by pages
> being asynchronously serialized while another request is already coming in.
> Or maybe it is something different.
> Could you create a quickstart?
> Am 25. Februar 2020 22:12:46 MEZ schrieb Thomas Heigl >:
> >Hi again,
> >I investigated a bit and it does not seem to have anything to do with
> >PerSessionPageStore. I implemented a completely synchronized version of
> >and the problems still exist.
> >If I switch to the default second-level cache that stores serialized
> >in application scope, everything works as expected. Only the
> >pages in PerSessionPageStore seem to be affected by concurrent ajax
> >What is the difference between keeping pages in the session and keeping
> >same pages in the PerSessionPageStore? Is there some additional locking
> >done for pages in the session?
> >On Tue, Feb 25, 2020 at 8:25 PM Thomas Heigl
> >> Hi all,
> >> I'm currently experimenting with PerSessionPageStore as a
> >> cache. We are moving our page store from memory (i.e. session) to
> >Redis and
> >> keeping 1-2 pages per session in memory speeds up ajax requests quite
> >a bit
> >> because network roundtrips and (de)serialization can be skipped for
> >> pages.
> >> Our application is very ajax heavy (it is basically a single page
> >> application with