Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caching aggregates to avoid repeated loads #157

Open
alexeyzimarev opened this issue Nov 4, 2022 · 9 comments
Open

Caching aggregates to avoid repeated loads #157

alexeyzimarev opened this issue Nov 4, 2022 · 9 comments
Labels
linear Sync the issue to Linear perfornamce Ways to improve performance

Comments

@alexeyzimarev
Copy link
Contributor

alexeyzimarev commented Nov 4, 2022

Proposal:

  • Place the loaded aggregate instance in a cache
  • When getting a command for that instance, get it from the cache instead of loading it from the stream
  • Check the stream revision
  • If it's higher, read the stream tail

It can be done in a composable version of the AggregateStore.

EVE-35

@alexeyzimarev alexeyzimarev added the perfornamce Ways to improve performance label Nov 4, 2022
@bartelink
Copy link
Contributor

Stating the obvious.... This requires the state (aka aggregate instance) to be thread-safe to cover for when two callers take it from the cache for an overlapping period. I'd say that having the state require this is a good pattern in general, but it's definitely another concept that needs to be covered in the high level docs. (i.e. referring to using System.Collections.Immutable etc to implement persistent data structures). (As an aside, while the cost of folding things in terms of perf or GC impact is rarely a concern IRL, having the folding function accept a sequence of events such that a batch of updates can be applied to the immutable/persistent structure in an optimal manner is good to be able to accommodate. In other words, if you have lots of events that make small changes to the state, forcing that to use a persistent data structure for every event being applied might be suboptimal compared to providing batches of 100 events, and having the fold function copy the state to a mutable structure, do a bulk updated, and then convert it back to a persistent data structure after that's done)

When designing the API... Sometimes it's useful to be able to specify that you want to skip reading newer events and assume that, if the cache has a value, that it's worth doing a roundtrip assuming that the cache is in-date (in Equinox, I call that AllowStale). Of course you need to have a good story for what happens when the cache entry was stale (in Equinox the default behavior caters to this by re-running the command if the Sync based on the cached value shows a conflict)

@alexeyzimarev
Copy link
Contributor Author

You're right, I haven't considered concurrent calls for the same object. The expected scenario, however, was a bit different and doesn't involve concurrency. In many cases, a user opens a single page and uses some task-based UI to execute several operations in a relatively short time (for a computer), sequentially.

Regarding the implementation, I planned to use the ASP.NET Core caching extensions and avoid thinking about concurrency. When the cached state is loaded, I'd expect it to be a deserialised instance (I might be wrong), so concurrent calls won't get the same instance.

@bartelink
Copy link
Contributor

I planned to use the ASP.NET Core caching extensions and avoid thinking about concurrency.

If you look in the Equinox impl, it uses System.MemoryCache - the advantage is that it stashes ready-to-consume objects without the state needing to be serializable (any snapshotting involves a call to an explicit snapshotting function that takes that state and renders an explicitly versionable/serializable form of it). But yes, if the cache is hydrating it as an independent fresh instance, that does indeed render the concurrency issues solved ;)

@alexeyzimarev
Copy link
Contributor Author

I planned to use IMemoryCache (who in their sane mind created an interface per cache kind???), and I don't know if it involves serialisation. Their Redis implementation does, of course, so I'd expect it to behave similarly. Need to check anyway, not willing to make wrong assumptions.

@alexeyzimarev alexeyzimarev added the linear Sync the issue to Linear label Mar 3, 2023
@alexeyzimarev alexeyzimarev changed the title Caching aggregates to avoid repeated loads [EVE-35] Caching aggregates to avoid repeated loads Mar 3, 2023
@ugumba
Copy link

ugumba commented May 11, 2023

I'm in need of this - any thoughts on ETA? :-) (Sorry for asking...)

@bartelink
Copy link
Contributor

bartelink commented May 11, 2023

and I don't know if it involves serialisation.

It does not rely on serialization - it holds onto the actual object in your single process(only)
the impl internally relies on GC hooks etc to keep the total size of held objects within a defined capacity
(it's used in Equinox and works well - note the thing that's held is the folded state, together with the version of the stream at which it was produced)

@alexeyzimarev
Copy link
Contributor Author

As I am currently working on subscriptions (split of physical subscription and consumer pipeline), it is now up for grabs. It's relatively straightforward, and I think the good first step would be to add it at the lowest level (read of stream in the event reader). Composition would work there nicely.

@ugumba
Copy link

ugumba commented May 12, 2023

#218 , #219 and #220 together add implicit support for IMemoryCache (if injected).
I've only been able to run the basic tests, but the changes are also very basic... :-)

Tests in my own application indicate that more than 10 times as many (basic) commands can be processed in 1 second than without caching. With longer intervals, the difference should be exponentially better.

I've ignored composability, but I'll look into it next - use of IMemoryCache must at least be optional.

@alexeyzimarev alexeyzimarev changed the title [EVE-35] Caching aggregates to avoid repeated loads Caching aggregates to avoid repeated loads Jun 17, 2024
Copy link

linear bot commented Aug 16, 2024

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
linear Sync the issue to Linear perfornamce Ways to improve performance
Projects
Status: Triage
Development

No branches or pull requests

3 participants