1

Reading keys in Redis while they are being written produces a read-fail as if the key were not present in the cache. Is there any connection configuration in spring/jedis that allow clients to avoid the read fail returning the old data of a key while a new data it is being written?

I saw that to avoid this issue some people uses a local cache, but I am looking for a simpler solution. In my case reading the old value would not be a concern.

Artifacts:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

Spring configuration class:

@Configuration
@EntityScan(basePackages = { "com.entities.redis" })
@EnableRedisRepositories("com.repository.redis")
public class RedisConfig {
    
    @Value("${redis.server}")
    private String server;

    @Value("${redis.port}")
    private Integer port;
    
    @Value("${redis.pool-size}")
    private Integer poolSize;
    
    @Value("${redis.database-index}")
    private Integer databaseIndex;
    
    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(poolSize);
        return jedisPoolConfig;
    }
    
    @Bean
    public JedisClientConfiguration jedisClientConfiguration(JedisPoolConfig jedisPoolConfig) {
        return JedisClientConfiguration
                .builder()
                .usePooling()
                .poolConfig(jedisPoolConfig)
                .build();
    }
 
    @Bean
    public JedisConnectionFactory jedisConnectionFactory(JedisClientConfiguration jedisClientConfiguration) {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(server, port);
        config.setDatabase(databaseIndex);
        return new JedisConnectionFactory(config, jedisClientConfiguration);
    }
    
    @Bean
    public RedisTemplate<?, ?> redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
        RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
        template.setConnectionFactory(jedisConnectionFactory);
        return template;
    }

}

Repository class

@Repository
public interface DataBeanRepository extends CrudRepository<DataBean, String> {
}

Entity class

@RedisHash("DataBean")
public class DataBean {
    
    @Id
    private String data;
    
    public DataBean(String data) {
        this.data = data;
    }

    public String getData() {
        return data;
    }    
}

Test class

@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
public class RedisTest {
    private static Logger LOGGER = LoggerFactory.getLogger(RedisTest.class);

    @Autowired
    private DataBeanRepository repository;
    
    @Test
    public void testPutAndGet() throws InterruptedException {
        final Duration TIME_TO_RUN = Duration.of(1, ChronoUnit.MINUTES);
        final int threadInternalTimeMilli = 200;
        final List<String> dataStrings = List.of("TEST0001", "TEST0002");
        
        // Start the consumer thread
        getConsumerThread(dataStrings, threadInternalTimeMilli).start();
        
        // Producer thread
        getProducerThread(dataStrings, threadInternalTimeMilli).start();
        
        // Wait until finishing...
        Instant endTime = Instant.now().plusSeconds(TIME_TO_RUN.getSeconds());
        while (Instant.now().isBefore(endTime)) {
            Thread.sleep(100);
        }
    }
    
    private Thread getConsumerThread(List<String> serials, int threadInternalTimeMilli) {
        return new Thread(() -> {
            LOGGER.info("Starting consumer thread...");
            while (true) {
                List<String> foundStrings = StreamSupport.stream(repository.findAllById(serials).spliterator(), false).map(DataBean::getData).collect(Collectors.toList());
                List<String> notFoundStrings = serials.stream().filter(sn -> !foundStrings.contains(sn)).collect(Collectors.toList());
                if (CollectionUtils.nonEmpty(notFoundStrings)) {
                    LOGGER.error("Reading ERROR > Not found strings ({})", notFoundStrings.stream().collect(Collectors.joining(",")));
                } else {
                    LOGGER.info("Reading OK (All were found)");
                }
                // Thread sleep time
                try {
                    Thread.sleep(threadInternalTimeMilli);
                } catch (InterruptedException e) {
                    throw new IllegalStateException();
                }
            }
        });
    }
    
    private Thread getProducerThread(List<String> serials, int threadInternalTimeMilli) {
        return new Thread(() -> {
            LOGGER.info("Starting producer thread...");
            while (true) {
                serials.stream().forEach(data -> {
                    LOGGER.info("Saving data: '{}'", data);
                    repository.save(new DataBean(data));
                });
                // Thread sleep time
                try {
                    Thread.sleep(threadInternalTimeMilli);
                } catch (InterruptedException e) {
                    throw new IllegalStateException();
                }
            }
        });
    }

}

When I run the program I get:

2021-09-10 11:31:33.595  INFO 61439 --- [       Thread-2] : Starting consumer thread...
2021-09-10 11:31:33.596  INFO 61439 --- [       Thread-3] : Starting producer thread...
2021-09-10 11:31:33.596  INFO 61439 --- [       Thread-3] : Saving data: 'TEST0001'
2021-09-10 11:31:34.505  INFO 61439 --- [       Thread-3] : Saving data: 'TEST0002'
2021-09-10 11:31:34.506  INFO 61439 --- [       Thread-2] : Reading OK (All were found)
2021-09-10 11:31:35.295  INFO 61439 --- [       Thread-3] : Saving data: 'TEST0001'
2021-09-10 11:31:35.297  INFO 61439 --- [       Thread-2] : Reading OK (All were found)
2021-09-10 11:31:35.884  INFO 61439 --- [       Thread-3] : Saving data: 'TEST0002'
2021-09-10 11:31:36.088 ERROR 61439 --- [       Thread-2] : Reading ERROR > Not found strings (TEST0001)
2021-09-10 11:31:36.681  INFO 61439 --- [       Thread-3] : Saving data: 'TEST0001'
2021-09-10 11:31:36.877  INFO 61439 --- [       Thread-2] : Reading OK (All were found)
2021-09-10 11:31:37.274  INFO 61439 --- [       Thread-3] : Saving data: 'TEST0002'
2021-09-10 11:31:37.660 ERROR 61439 --- [       Thread-2] : Reading ERROR > Not found strings (TEST0002)
2021-09-10 11:31:38.061  INFO 61439 --- [       Thread-3] : Saving data: 'TEST0001'
2021-09-10 11:31:38.447  INFO 61439 --- [       Thread-2] : Reading OK (All were found)
2021-09-10 11:31:38.649  INFO 61439 --- [       Thread-3] : Saving data: 'TEST0002'
2021-09-10 11:31:39.233  INFO 61439 --- [       Thread-2] : Reading OK (All were found)
2021-09-10 11:31:39.433  INFO 61439 --- [       Thread-3] : Saving data: 'TEST0001'
2021-09-10 11:31:40.035  INFO 61439 --- [       Thread-3] : Saving data: 'TEST0002'
2021-09-10 11:31:40.037 ERROR 61439 --- [       Thread-2] : Reading ERROR > Not found strings (TEST0001)

Thank you, Regards

edtrcd
  • 21
  • 4
  • Simply saying, Redis is single threaded. The issue you're saying is non-existent in Redis. – sazzad Sep 10 '21 at 08:21
  • I added to the question the code I am using... Do you have any clues about why I am seeing this behavior in the output console logs? – edtrcd Sep 10 '21 at 14:42
  • Sorry, I don't. – sazzad Sep 10 '21 at 15:18
  • @edtrcd, Please take a look on this question [Spring data redis concurrency issue](https://stackoverflow.com/questions/62015742/spring-data-redis-concurrency-issue/69150405#69150405). See if my answer can help. – samabcde Sep 12 '21 at 10:10

0 Answers0