redis / lettuce

Advanced Java Redis client for thread-safe sync, async, and reactive usage. Supports Cluster, Sentinel, Pipelining, and codecs.
https://lettuce.io
MIT License
5.36k stars 959 forks source link

Cursor based operations not working properly with ReadFrom = ANY #2926

Open EMDavl opened 2 months ago

EMDavl commented 2 months ago

Bug Report

Current Behavior

Hello!

We have encountered the following problem - when using zscan with Redis Cluster and the ReadFrom parameter set to ANY, consecutive requests may be sent to different nodes. Because of this, the response does not comply with the guarantees provided by Redis regarding SCAN family operations. The response may contain duplicate values, and some values may be missing altogether.

Small example - https://github.com/EMDavl/cursor-bug

Expected behavior/code

SCAN family requests return correct values with the ReadFrom parameter set to ANY when the library is used with Redis Cluster. Requests are sent to the same node.

Environment

Possible Solution

Send requests related to scan based operations to the same node untill iteration ends

EMDavl commented 1 week ago

@tishun Hi! Is there any updates regarding this topic?

tishun commented 1 week ago

Will try to take a look this week, apologies for the delay

tishun commented 6 days ago

Hey @EMDavl ,

    /**
     * Setting to read from any node.
     *
     * @since 5.2
     */
    public static final ReadFrom ANY = new ReadFromImpl.ReadFromAnyNode();

Am I correct to assume you want to both use ReadFrom.Any and in the same time (only for the SCAN family of commands) read from the same node? Can you elaborate more on the use case? I assume ReadFrom.LOWEST_LATENCY is - for some reason - not going to work for you?

tishun commented 6 days ago

Hello again,

I've taken the liberty of modifying your code and it now reads all the values:

        Set<String> set = new HashSet<>();
        ScoredValueScanCursor<String> cursor = null;
        do  {
            if (cursor == null) {
                cursor = sync.zscan(zsetName, ScanCursor.of("0"), ScanArgs.Builder.limit(1000));
            } else {
                cursor = sync.zscan(zsetName, ScanCursor.of(cursor.getCursor()), ScanArgs.Builder.limit(1000));
            }

            for (ScoredValue<String> value : cursor.getValues()) {
                set.add(value.getValue());
            }

        } while (!cursor.isFinished());

Does that help?