Cache Invalidation Doesn't Have To Be Hard

Поделиться
HTML-код
  • Опубликовано: 31 янв 2025

Комментарии • 101

  • @MilanJovanovicTech
    @MilanJovanovicTech  Год назад +1

    Want to master Clean Architecture? Go here: bit.ly/3PupkOJ
    Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt

  • @JonathanPeel
    @JonathanPeel Год назад +18

    There are two hard things in programming.
    Cache invalidation, naming things, and off-by-one errors.

  • @phw1009
    @phw1009 5 месяцев назад +2

    I had to get back all the way back in here, an year after you've uploaded.
    Every single videos are pure gold.
    Thanks again milan 👍👍

  • @ruslangaliev9641
    @ruslangaliev9641 Год назад +1

    In our project we implemented caching and invalidation as attributes on controller endpoints. Now we can add caching/invalidating in declarative way (It uses controller name and endpoint parameters as key value, when invalidate it removes everything related to specified controller). I'm wondering why other don't do the same because it's handy use and you cannot make a mistake when use caching in declarative way.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      It would be better if you can control which parameters can form the cache key, but it's a great idea

  • @pilotboba
    @pilotboba Год назад +2

    I think Caching is an infrastructure concern, not an application concern. So, having the handlers invalidate the cache seems wrong. Also, having handlers that respond to domain/internal events seems wrong too.
    I would consider the cache a form of persistence. So, I think the persistence infra code should deal with caching data.
    Now if we are talking about output caching, that is a presentation layer concern, and it should probably be the presentation layer that deals with invalidating the output cache.
    But, sometimes being pragmatic is better than being dogmatic.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      What do you mean by "having handlers that respond to domain/internal events seems wrong too"? What's going to handle the events then?
      Most of this can just as easily be moved to the Infrastructure layer.

    • @pilotboba
      @pilotboba Год назад

      @@MilanJovanovicTech probably just typed too fast. I mean having handlers for domain events integrating with caching.

  • @kodindoyannick5328
    @kodindoyannick5328 11 месяцев назад

    Great video. Thank you Milan.

  • @Fikusiklol
    @Fikusiklol Год назад +2

    Hey, Milan!
    Thanks for the caching related vidoes, great stuff. However, how would u go with invalidation if there were connections between resources?
    Example: one Order -> many OrderItems.
    You would need to invalidate 1) OrderItem by id 2) OrderItems filtering 3) Order that comes with his items.
    I suppose, you would you still go with the events approach?

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      One event, and clear keys by pattern matching. Requires storing all keys however.

  • @Sava-l3c
    @Sava-l3c Год назад +5

    Hello Milan. What happens when you have more processes and some of them are changing DB and some of them are reading from cash at the same time from more API instances? I believe that concurrency issues may happen there. What to do to prevent that and have valid results all the time?

    • @RahulSingh-il1xk
      @RahulSingh-il1xk Год назад

      How about we keep cache invalidation operation atomic. In redis we can use Lua scripts/watch commands while invalidating cache - during this process other redis resources would freeze. However, our app instances which are reading (not updating) cache, will face delayed response during the process.

    • @nayanc4353
      @nayanc4353 Год назад

      Bad design smells.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад +1

      There will always be the case for discrepancy between the source of truth and the cache. That's one thing you have to be okay with if you want to use caching.

  • @behnam_salehi
    @behnam_salehi Месяц назад

    Great video 🎉

  • @svorskemattias
    @svorskemattias Год назад +2

    I think caching lends itself better to the infrastructure layer, behing repository walls rather than just outside because a repository has explicit knowledge of when an entity is updated. Just a hunch. Havnt needed caching on that level so far. :)

    • @PelFox
      @PelFox Год назад +2

      This is generally how I do it as well. Making use of decorator pattern over a repository to make it a cached repository, any calls to update/delete/create will invalidate cache.

    • @jeremiahgavin9687
      @jeremiahgavin9687 Год назад

      @@PelFox Can you post an example snippet of this method's implementation?

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      I'm not using a repository here, I'm doomed then 🤷‍♂️

  • @evilroxxx
    @evilroxxx Год назад +1

    How would you handle caching and invalidating paginated data in a single and multi-tenant scenarios? How do you decide to choose application caching over output response caching?

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      Just cache the appropriate cache key "page-1-page-size-15" etc and reset that

  • @HAMEDMHMDI
    @HAMEDMHMDI Год назад +1

    but if we want to use redis as a layer between API and Database , what we should do?
    i mean persist data to disk and use redis as a write and read database(Cache) that after write data into redis, the data sent to an sql database.

  • @ala-eddinebejaoui2493
    @ala-eddinebejaoui2493 Год назад +1

    At 12:05, how do you invalidate a specific object from the cache without it's ID being used ? From what i read, the whole "products" caching is wiped in here, no id passed ?
    Maybe i haven't understood something ?
    Thanks !

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      Let's say the "products" key contains a list of all products or something similar .That was the idea

  • @VijayKumar-wy9ku
    @VijayKumar-wy9ku Год назад +1

    Nice content! Do we really need 3 Handlers for Cache Invalidation here? Can't we just publish the event with the cache key and use that key in the Handler to remove it from Cache?

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      Brilliant idea to be honest. My idea was more around generic events that can also be handled to do something else.

  • @aerys-cmd
    @aerys-cmd Год назад +1

    In your expensely project. You were caching the results of query which are implementing the ICacheableQuery. So what if I create a interface named ICacheInvalidatorCommand and write a function that returns the cache key/keys and invalidate those keys inside of a behaviour if the response is successful.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      That was a brave attempt... I didn't want to complicate it for this video

  • @Reverence12389
    @Reverence12389 22 дня назад

    Would love to see a more in depth cache invalidation video that covers different cache invalidation strategies, because this seems a bit simplistic and flawed. If products are changed often, then the caching would be constantly invalidated and might be considered pointless. I wonder if there's a better approach?

    • @MilanJovanovicTech
      @MilanJovanovicTech  22 дня назад

      A more specific cache key and creating some "buckets" for more granular invalidation

    • @Reverence12389
      @Reverence12389 22 дня назад

      @MilanJovanovicTech i imagine time based would also be acceptable if the business was okay with stale data for a certain period in order to have better performance. Alrho that probably only addresses the add/update scenario...since delete scenario could cause system errors if they try to open a deleted item from a cached list somewhere.

  • @nove1398
    @nove1398 Год назад +1

    *patiently waiting for naming things video*

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад +1

      Should I even make one about that? 🤔

    • @nove1398
      @nove1398 Год назад +1

      @@MilanJovanovicTech i dont think that one will get much attention but if you have spare time definitely haha maybe 5 minutes about tricks of naming, importance, general patterns, etc

  • @baranacikgoz
    @baranacikgoz Год назад +2

    Is it a good idea to override SaveChanges method of DbContext, and invalidate cache there?(key can be typeof(Entity).Name etc.) Or is your solution more reliable? What do you think?

    • @ravvvvar
      @ravvvvar Год назад +1

      I use this approach (overridding SaveChangensAsync or other UoW) and it works perfect for me. One place to deal with cache invalidation and forget about it. In DbContext there is access to ChangeTracker, so clearing "product-{id}" is no problem... I wondering why Milan didn't mension about that - is it something wrong with this solution, Milan? 🤔

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      It'll work, but gets overly complex as soon as your cache key is different from typeof(Entity).Name

  • @michaldivismusic
    @michaldivismusic Год назад +1

    One unrelated thing I've been wandering about is the cancellation token. When having steps like save saving to database and then pushing an event, wouldn't cancellation possibly lead to inconsistency? Specifically in case the request is cancelled after saving to database and before pushing the event.

    • @PelFox
      @PelFox Год назад +4

      Yes it could. I generally avoid using CancellationTokens in any Commands which does state changes.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      Completely valid point! I'll do better in future videos.

  • @gauravsingh-qt2zo
    @gauravsingh-qt2zo Год назад +1

    What is the advantage of making libraries instead of folders in a project such as for models, services, repository etc....

  • @GGGStalker
    @GGGStalker Год назад

    I love every example that you do. BUT! you don't share the code that you have written, is there a reason why?

  • @hamitkarahan2973
    @hamitkarahan2973 Год назад +1

    Very nice and efficient way Milan! Thanks for sharing😊 Recently I applied a solution for this specific problem. My solution was the approach of Pipeline Behavior which MediatR gives us out of the box. Something like `CacheInvalidationBehavior` for specific commands (Create, Update, Delete) was enough for me. Do you think which one is more usable in this case? Pipeline Behavior or Notification Handler approach 🤔

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      How do you tell which commands need to invalidate the cache, and how do you know which cache keys?

    • @hamitkarahan2973
      @hamitkarahan2973 Год назад +1

      @@MilanJovanovicTech Well, You can mark your commands with an interface, like `ICacheInvalidationRequest` and inside your behavior, you can check if the commands which hit the behavior implements that interface or not. If it's includes, you can invalidate your cache.
      About the cache key, you can put a method inside of `ICacheInvalidationRequest` like `string GetCacheKey()` and the commands which implements this interface must implement this method also. So you can set cache key in there and use it in your `CacheInvalidationBehavior` class.
      I guess it's harder to say than to do 😅

    • @ignaciocastro0713
      @ignaciocastro0713 7 месяцев назад

      @@hamitkarahan2973 this is interesting do you have an example?

  • @TheAzerue
    @TheAzerue Год назад

    One question
    1. Is there any specific reason of choosing record type for ProductDeleteEvent instead of class ?

  • @ibrahimhaouari6425
    @ibrahimhaouari6425 Год назад +1

    What about using interceptor and invalidate when products change

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      SaveChangesInterceptor? It'll grow pretty quickly when you add more entities into the mix

  • @evilroxxx
    @evilroxxx Год назад +1

    You didn’t really use the IPipelineBehavior for this. Instead you used Inotification which I guess also works. But do you have an example for using the pipeline behavior or this was it?

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      I don't have a pipeline behavior example. You could do something with an ICacheableComand interface + some way to tell the pipeline behavior which cache key to remove.

  • @MarcusKaseder
    @MarcusKaseder Год назад +1

    I don't think, it is a good idea to pass the CancellationToken to the methods after SaveChanges(cancellationToken).
    Since you're not using a Outbox pattern, all methods that are executed after SaveChanges can be canceled and will also lead into inconsistent state, aren't they?

  • @GoriRJ
    @GoriRJ 9 месяцев назад

    What is the theme your are using?

  • @DerClaudius
    @DerClaudius Год назад +1

    Alright so far... now have one dbserver but multiple API server.. how do you invalidate multiple local caches effectively?

  • @manliomarchica4911
    @manliomarchica4911 Год назад +1

    I've recently fix a bug of cache invalidation. the key was dinamically generated using the name of the method and the value of the parameters and in one method to add an item the invalidation of the cache was called with the wrong list of parameters... Do you have naming convention for the cache key ? and also in case mediator pattern is not used what could be a way to centralize the invalidationof the key ?

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      To centralize invalidation, place it in some service/class. For naming convention, I like to be as descriptive as possible and use kebab-case

  • @vipuldimri6571
    @vipuldimri6571 Год назад +1

    Hi ,
    In my project I am caching Labs data (Not change very frequent) with related relationship properties ,do you think its a good idea to cache related info also ?
    because everytime any lab changes its relationship property like (Working Days , Documents etc.) I need to reset the cache .
    Mostly these related property will have count less than 10.
    Example : caching labs with related properties.
    public async Task GetAll(bool trackChanges = false)
    {
    return await FindAll(trackChanges)
    .Include(x => x.WorkingDays)
    .Include(x => x.Documents)
    .Include(x => x.MediaList)
    .Include(x => x.ServiceablePincodes)
    .Include(x => x.Certificates)
    .ToListAsync();
    }

  • @hlazunov
    @hlazunov Год назад +1

    Or you can just simply update the cache instead of invalidating it. It means that you only read from the cache that is up to date always.

  • @zaghal
    @zaghal Год назад +1

    great

  • @jameshancock
    @jameshancock Год назад +1

    Our hardest problem to solve is cache of an object, and then someone else requests a list of the same object type. The cache is in redis and the data is in EF Core. On the surface it seems simple to look in the cache first and then get the rest.
    But the problem is row level (i.e. object level) security which then has to merge results but do so by applying security criteria.
    RavenDb largely solves this for us because it works like Redis itself when given enough memory, and thus our row level filtering queries intelligently use the cache whenever possible.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад

      RavenDB is incredibly good, and so unerrated

    • @jameshancock
      @jameshancock Год назад

      @@MilanJovanovicTech absolutely but for those not using it to cheat the issue, providing guidance on how to handling the segmented nature and ability to know if you got all of the rows relevant from cache or if you have to go back to the database is a big complex deal that almost every developer at scale ends up dealing with. (And security at row level hits us all and has to be done intelligently with caching involved. )

  • @mos90
    @mos90 Год назад +2

    Thanks. but your second solution has these cons as well 3:38

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад +4

      The point is this. Would you rather:
      - Have your cache invalidation code scattered across many files
      - Have it all in one place
      What's your choice?

    • @sandragredo4673
      @sandragredo4673 Год назад

      Sta ti studiras?

    • @mos90
      @mos90 Год назад +1

      @@MilanJovanovicTech Good point. its a better approach.
      but can we do something about this? I mean, the fact that we wrote that code in multiple places.

    • @kevinlloyd9507
      @kevinlloyd9507 Год назад

      @@MilanJovanovicTech Domain events?

  • @OeHomestead
    @OeHomestead Год назад +1

    Nice, but I'd go for a simpler solution where I didn't have to maintain 13-14 classes just to implement a simple CRUD for one product ;-)

  • @gingeral253
    @gingeral253 Год назад

    I’ll come back when I have more experience

  • @rustamhajiyev
    @rustamhajiyev Год назад +7

    If you can afford it - no cache is the best option 😂

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад +2

      And in most cases you can actually afford to

    • @PelFox
      @PelFox Год назад +4

      I agree that you shouldn't prematurely add caching, your code should work well enough without it. Often it's introduced early and developers think it's a must have. Your 10 users won't blow up the database, I can assure you.

    • @wolfVFXmc
      @wolfVFXmc Год назад

      @@PelFox but once more users use the application you will need it even if it’s just spikes

    • @PelFox
      @PelFox Год назад

      @@wolfVFXmc hopefully you are allowed to monitor your system. Very rarely it goes from 10 users to several thousands over a day, marketing takes time.

  • @mitar1283
    @mitar1283 Год назад

    Why not just call the class "ProductCashInvalidator"🤔

  • @gabrielrabhi3357
    @gabrielrabhi3357 Год назад

    I expected you'll speak about the real cache (wich is a really interesting topic) : cache, in computer science, is hardware L1, L2, L3 cache... so, be precise ! "Database cache" is what you are speaking about.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Год назад +1

      But this is an in-memory cache. And this is a software channel, not a hardware one.

    • @gabrielrabhi3357
      @gabrielrabhi3357 Год назад

      ​@@MilanJovanovicTech No. You cannot say : cache implicitly mean "software data cache" : it is simply false, for me.
      The fact is that your softwares are ALL TIME using hardware cache - without harware memory cache, your database cache may be 2-3 level of magnitude slower.
      In a point of view of an experienced developper that start programming in 1990 and have made video games, there is no question about the signification of "cache" in computer science : at any time I'm doing software, I'm massivelly using hardware memory cache. So, if you say "I will discuss about cache" - it does not mean "database cache". "Database cache" is a high level strategy rellying on hardware caching systems...