Skip to content

Caching Strategies

shubhamphepale edited this page Dec 6, 2021 · 3 revisions

Caching

Caching refers to the process of storing frequently used data so that those data can be served much faster for any future requests. So we take the most frequently used data and copy it into temporary storage so that it can be accessed much faster in future calls from the client. Caching significantly improves the performance of an application, reducing the complexity to generate content.

Caching Strategies

  • Cache Aside
  • Write-Through Cache
  • Read-Through Cache
  • Write-Back

Cache Aside

In this caching strategy, the cache is logically placed at the side and the application directly communicates with the cache and the database to know if the requested information is present or not.

The followings are the characteristics of the pattern.

  • When an application needs data, first it looks in the cache.
  • If the data available in the cache, then application will use the data from the cache, otherwise data is retrieved from the database and the cache entry will be updated.

Dependency

we have to add a dependency to the cache starter

 implementation 'org.springframework.boot:spring-boot-starter-cache'

Cache Aside Annotations

  • @EnableCaching

    We can enable the caching feature simply by adding the @EnableCaching annotation to any of the configuration classes:

     @EnableCaching
     public class CachingConfig {
         @Bean
         public CacheManager cacheManager() {
              return new ConcurrentMapCacheManager("addresses");
         }
     }
    
  • @Cacheable

    we can provide a cache name by using the value or cacheNames attribute.

      @Cacheable(value = "cacheAside",key = "#id")
      public Optional<CacheAsideEntity> findById(Long id) {
           return cacheAsideEntityRepository.findById(id);
      }
    
  • @CachePut

    It is used when we want to update the cache without interfering the method execution. It means the method will always execute, and its result will be placed into the cache. It supports the attributes of @Cacheable annotation.

      @CachePut(value = "cacheAside",key = "#cacheAside.id")
      public CacheAsideEntity save(CacheAsideDTO sampleEntityDTO) {
          CacheAsideEntity cacheAsideEntity = cacheAsideEntityMapper.dtoToEntity(sampleEntityDTO);
          return cacheAsideEntityRepository.save(sampleEntity);
      }
    
  • @CacheEvict

    We can use the @CacheEvict annotation to indicate the removal of one or more/all values so that fresh values can be loaded into the cache again:

      @CacheEvict(value = "sampleEntity",allEntries = false,key = "#id")
      public boolean deleteById(Long id) {
            sampleEntityRepository.deleteById(id);
        return true;
      }
    

Code Walkthrough Video:

CacheAside.mp4

Write-Through Cache

Write-through caching is a caching strategy in which the cache and database are updated almost simultaneously. When we want to update the information in the cache, we first update the cache itself, and then propagate the same update to the underlying database.

write-through

Redisson includes functionality for write-through caching in Redis by using the RMap interface.

   @Bean
   public RMapCache<Long, SampleEntity> userRMapCache() {
        final RMapCache<Long, SampleEntity> userRMapCache = redissonClient.getMapCache(CACHE_NAME,MapOptions.<Long, SampleEntity>defaults()
            .writer(getMapWriter()).loader(getMapLoader())
            .writeMode(MapOptions.WriteMode.WRITE_THROUGH));
        return userRMapCache;
   }

When the Map entry is being updated method won't return until Redisson update it in an external storage using MapWriter object. Code example:

    private MapWriter<Long, SampleEntity> getMapWriter() {
        return new MapWriter<Long, SampleEntity>() {

        @Override
        public void write(final Map<Long, SampleEntity> map) {
            logger.info("*********************** write");
            map.forEach( (k, v) -> {
            	
            	WriteThroughCacheEntityDTO sampleEntityDTO=new WriteThroughCacheEntityDTO();
            	sampleEntityDTO.setId(v.getId());
            	sampleEntityDTO.setAge(v.getAge());
            	sampleEntityDTO.setName(v.getName());
            	sampleEntityDTO.setPassword(v.getPassword());
            	sampleEntityDTO.setPhone(v.getPhone());
            	
                userJPAAdapter.save(sampleEntityDTO);
            });
        }

        @Override
        public void delete(Collection<Long> keys) {
            logger.info("*********************** delete");
            keys.stream().forEach(e -> {
                userJPAAdapter.deleteById(e);
                
            });
          
        }
    };
}   

Write-Through Cache Code Walkthrough using Video:

writethrough.mp4

Read-Through Cache

Read-through cache sits in-line with the database. When there is a cache miss, it loads missing data from database, populates the cache and returns it to the application. Read-through caches work best for read-heavy workloads when the same data is requested many times.

read-through

If requested entry doesn't exist in the Redisson Map object when it will be loaded using provided MapLoader object. Code example:

     private MapLoader<Long, ReadThroughEntity> getMapLoader(){
	return new MapLoader<Long, ReadThroughEntity>() {

		@Override
		public ReadThroughEntity load(Long key) {
			logger.info("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ReadThroughEntity load(Long key)");
			
			return sampleEntity2JpaAdapter.findById(key).get();
		}

		@Override
		public Iterable<Long> loadAllKeys() {
			logger.info("############################## Itrerable<Long> loadAllKeys()");
			List<Long> list = new ArrayList<Long>() {

				/**
				 * 
				 */
				private static final long serialVersionUID = 1L;};
			List<ReadThroughEntity> list1 = sampleEntity2JpaAdapter.findAll();
			for(ReadThroughEntity sample : list1) {
				list.add(sample.getId());
			}
			return list;
		}
	};
}

Read-Through Cache Code Walkthrough using Video:

video1226335207.mp4