abp icon indicating copy to clipboard operation
abp copied to clipboard

Distributed Entity Cache

Open hikalkan opened this issue 3 years ago • 1 comments

@realLiangshiwei can you please add unit tests.

Usage

Entity objects are directly cached

  1. Register the entity cache: context.Services.AddEntityCache<Product, Guid>().
  2. Inject & use IEntityCache<Product, Guid> service.

With this usage, the entity class should be serializable/deserializable to/from JSON.

Entities are mapped to a cache item

  1. Define a cache item class, like ProductCacheItem.
  2. Configure object mapper for Product -> ProductCacheItem mapping.
  3. Register the entity cache: context.Services.AddEntityCache<Product, ProductCacheItem, Guid>().
  4. Inject & use IEntityCache<ProductCacheItem, Guid> service.

With contextualized object mapper

This is only needed for reusables modules build with the best practices guide. Application developers don't need it.

  1. Define a cache item class, like ProductCacheItem.
  2. Configure object mapper for Product -> ProductCacheItem mapping.
  3. Register the entity cache: context.Services.AddEntityCache<TMyModule, Product, ProductCacheItem, Guid>().*
  4. Inject & use IEntityCache<ProductCacheItem, Guid> service.

*TMyModule is Type of the module class we define the object mapper context.

hikalkan avatar Sep 17 '22 22:09 hikalkan

The entity cache may have the danger of consistent problems.

https://github.com/abpframework/abp/blob/91fcbd8b3e7d222b83e0c18b245a3337f610c35e/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheBase.cs#L44-L52

What if another thread re-creates the cache before the current UOW is complete? The dirty cache item could exist for a long time (even eternal) since the default cache duration time is 20min and sliding.

I consider that the cache invalidator should also implement the IDistributedEventHandler:

  • The IDistributedEventHandler removes the target cache item.
  • The ILocalEventHandler removes the target cache item on the current UOW completed.

I believe it could cover all these scenarios:

  1. Local distributed event bus.
  2. Real distributed event bus.
  3. Real distributed event bus with event boxes.

The cache duration time should be shorter if we don't use event boxes since the uow.OnCompleted method may not execute due to the app crashing, making a consistent problem. And I believe keeping the cache duration short is better for safety, even if we use event boxes

gdlcf88 avatar Sep 21 '22 08:09 gdlcf88

Entity key property is protected set, developer need to use JsonInclude if using Entity objects are directly cached.

For example:

public class Product: Entity<Guid>
{
    [JsonInclude]
    public override Guid Id { get; protected set; }

    public string Name { get; set; }

    public decimal Price { get; set; }
}

realLiangshiwei avatar Sep 27 '22 08:09 realLiangshiwei

We don't need the [JsonInclude] after https://github.com/abpframework/abp/pull/13357

maliming avatar Sep 27 '22 09:09 maliming

@realLiangshiwei why we've added these lines:

services
    .TryAddTransient<EntityCacheWithObjectMapper<TEntity, TEntityCacheItem, TKey>>();

It is not needed to resolve services with EntityCacheWithObjectMapper<TEntity, TEntityCacheItem, TKey>, we should resolve with IEntityCache.

hikalkan avatar Oct 04 '22 05:10 hikalkan

@gdlcf88

  • See the "Additional notes" I've added to the PR summary, for the shorter cache duration. It is 2 minutes now, and not sliding.
  • For the consistency problem, I double remove the cache item. See this commit: https://github.com/abpframework/abp/pull/14055/commits/f96277192bd49fe02c319438eec7587984f126b0

I don't want to use distributed event bus. It has big cost, and also requires additional steps to publish distributed events.

hikalkan avatar Oct 04 '22 06:10 hikalkan

@realLiangshiwei I am merging this. Please see my comments (https://github.com/abpframework/abp/pull/14055#issuecomment-1266419811) and remove these and send a PR if we don't have a good reason.

hikalkan avatar Oct 04 '22 06:10 hikalkan

I found an article summarizing the cache consistency solutions: https://danielw.cn/cache-consistency-with-database.

The double-deletion solution does improve the problem but still has non-consistency dangers: image

Another solution rockscache looks like a good way, but it depends on the Lua script feature of Redis, which is not acceptable with the ABP framework.

Actually, cache consistency is hard to implement by the ABP framework itself.

I think the current design is enough. When the accident occurs, stale data can only exist for up to 2min (by default). What the developers should do with the entity caches is face the non-consistency and don't directly use them in a writing operation.

gdlcf88 avatar Oct 04 '22 06:10 gdlcf88

Entity Cache is designed for read-only. We shouldn't make changes on the same entity when we use the entity cache. If we need to change the entity, we should always read it from database to ensure transactional consistency. I will write note about that in the documentation.

hikalkan avatar Oct 04 '22 06:10 hikalkan

And a remote service query that returns entity data should also avoid using the entity cache if we use the data for a writing operation.

gdlcf88 avatar Oct 04 '22 06:10 gdlcf88

https://github.com/abpframework/abp/pull/14055#issuecomment-1266439997

it is necessary because we want to remove the entity cache when the entity is changed.

EntityCacheWithObjectMapper and EntityCacheWithObjectMapperContext is the Event Handler, we need to expose themselves to the dependency injection container

image

realLiangshiwei avatar Oct 06 '22 06:10 realLiangshiwei

Thanks @realLiangshiwei for your explanation. Then it seems fine.

hikalkan avatar Oct 06 '22 07:10 hikalkan