Cache invalidation with nested objects

Hi everyone

In these days of relative calm, I’d like to talk to you about a topic that I’ve been carrying around since I’ve been a developer without ever finding the perfect solution: cache invalidation with nested objects.

This is the scenario:

Product.cfc:

property name="id" type="String"
property name="categories" type="Category[]"

Category.cfc:

property name="id" type="String";

When I read a “Category”:

function getCategory( id ) {

     // check that the object is not already in cache
     // if yes, return it

     if ( cacheIdExists("category_" & id) ) {

         return cacheGet( "category_" & id )

     } else {

        // Otherwise I create it

         var categoryBean = new Category();
         categoryBean.setId( id );
        
         cachePut( "category_#id#", categoryBean );

         return categoryBean;

     }
}

When I read “Product”:

function getProduct( id ) {

      // check that the object is not already in cache
     // if yes, return it
     if ( cacheIdExists( "product_" & id) ) {

         return cacheGet( "product_" & id )

     } else {

        // Otherwise I create it

         var productBean = new Product()
         productBean.setId( id );

         var categories = [];

         for ( var categoryId in list_categories_from_db ) {
    
             //ok, I don't do exactly that but it's to explain that
             // inside the "Product" object there are the categories in cache

             categories.add(
                 getCategory( categoryId )
             )
    
         }

         productBean.setCategories( categories );

         cachePut( "product_#id#", productBean );

         return productBean;

     }
}

As you can see, I cached the entire “Product” object, including its “Categoy” children.
But now I have the problem that when I edit a category, I should invalidate the cache of all linked “Product” objects.

It’s quite simple when there’s a parent and a few children, but it gets really complex if there are many, very nested children, children, children…

The solutions that I have found and that I apply depending on the complexity are these:

  1. clear the parent cache by hand (in our example all the “Products” that have the category changed. It works, but it’s complicated with many parents/children).
  2. don’t put the nested properties like “Categories” in the “Product” bean but use a service to get all the categories of a product. It’s not as solid as I would like.
  3. Use an event system that removes as in point 1. It is more complex but it also works with complex objects.
  4. Don’t cache all the “Product” object, but create it every time. It’s a good solution because it generally decreases the load on the db a lot anyway, but I would prefer that my code doesn’t do useless things like always creating the same object when it hasn’t been modified yet…

What I would like to achieve is to have my own “ToolSomethingCache” that deletes all “parents” when deleting a child. Before starting to write code, I would like to understand if there is any literature on the matter, or if products like CacheBox do this job for me :sweat_smile::grin:

I hope I made myself clear.

I wish a wonderful New Year to all the guys who keep this community alive, and the developers who make Lucee a fantastic product!

:santa::christmas_tree:

1 Like

Wow - Awesome question.
I am, sincerely, hoping for a really good discussion here.
I know very little about caching.
In fact when looking for bugs - it’s the first thing I remove from the equation!

In a current project I have removed it entirely, because it was coded by different people over the time of the application’s lifetime and was implemented differently in different portions of the application.

Lastly
I LOVE the image!

1 Like

I’ve heard this quote before: “There are only two hard things in Computer Science: cache invalidation and naming things.” I don’t think anyone has necessary figured this problem out in a great way.

Rails has a concept called Russian Doll Caching, in which you can specify object dependencies in such a way that the cache for dependent objects will automatically be cleared. It’s used for caching of rendered view templates, but the concept might be portable.