1

Let's say I'm building a cards game (I'm using an analogy as I can't disclose the original project details). Consider the following structure:

Dealer1
    91 // Card 9 of spades (Second digit represents card type, check the legend below)
        Rejectors:
            John
    A2 // Ace of hearts
        Rejectors:
            Rowan
    J3
    K2
        Rejectors:
            David
    33
Dealer2
    43
    52
        Rejectors:
            David
    13
Dealer3
    44
    83
    93


//  1: spades ♠, 2: hearts ♥, 3 diamonds 4: clubs ♣.

The dealer deals a card (It gets added to his list), Only one player at a time gets one chance to pull the card, look at it and either keep it or reject it (put it back into the deck).

If the card is rejected, we mark the name of the player (so next time he can't pull it).

If the card is kept, it is removed from the list above.

If the card is being looked at by any player, it is not counted into any other player list (We can remove it temporarily from the list).

I want to be able at any moment to show a player how many cards left for him to pull.

For instance, if John asked how many cards left for Dealer 1 and Dealer 2, the answer would be 7 cards (A2, J3, K2, 33, 43, 52, 13), remember card 91 was rejected previously by John.

Now if Hanna asks how many cards for the same dealers the answer would be 8 (she hasn't rejected any card before, therefore her name is not in any dealer list)

Rules:

  • Cards are unique across all dealers (no two dealers can deal the same card)
  • Cards are infinite (traditionally there are only 52 cards, but in this example, there are randomly generated imaginary cards)
  • Card type is irrelevant, I put it to make it easier (maybe not), think of it as a random ID.

What is the best approach to save this in Redis? Sets? Hashes?

The first try, I just saved a serialized object as follows (not necessarily the same list above):

// Key 1
"Dealer1": [
    {
        "Card": "A1",
        "Rejecters": [
            "John"
        ]
    },
    {
        "Card": "K2",
        "Rejecters": [
            "David"
        ]
    }
]

// Key 2
"Dealer2": [
    {
        "Card": "31",
        "Rejecters": [
            "Adam"
        ]
    },
    {
        "Card": "Q2",
        "Rejecters": [
            "David"
        ]
    }
]

The key is the dealer id, the value is a serialized array of objects, each object represents a card and its "rejectors" (not the best name maybe) list.

I know this is not efficient, as every time I need to query the count of available cards for a player, I have to loop on the dealers' keys requested, fetch its whole list, count (client-side) how many objects in the list where the player name is not present.

PS: I'm using ServiceStack.Redis C# client (but I can handle raw Redis commands / Data types, and I can translate them into C# code).

Nour
  • 5,252
  • 3
  • 41
  • 66
  • Did you check https://redisjson.com? – Guy Korland Jul 15 '20 at 06:49
  • I'm not looking for a persistence solution. I'm looking for the best performant approach to represent this solution in Redis. I'm already saving the value of the key as JSON which is as I mentioned is not efficient (Serializing and Deserializing a big object is hit) – Nour Jul 15 '20 at 07:23
  • 1
    redisjson just adds a new datatype on top of Redis and allows you to query/project/update parts of it, without the need to retrieve it all and deserialize it all – Guy Korland Jul 15 '20 at 09:57
  • Knowing Redis commands, it must use the same commands (fetch the object to the client and do queries), there is no way to do such queries on Server side. Also. the URL seems to be gone. – Nour Jul 15 '20 at 10:38
  • 1
    RedisJSON is a module extension to Redis, therefore it can run things in the server side and optimize the way it stores the data for it – Guy Korland Jul 15 '20 at 13:51
  • Interesting, definitely worth checking, but I guess it is not enabled in our server. Will see if we can ask to enable this. Thanks – Nour Jul 16 '20 at 10:37

1 Answers1

1

Create a redis set for each card dealer.
For example,

"dealer_1_set" : ["91,"A1"]

"dealer_2_set" : ["A2,"A3"]

"dealer_3_set" : ["B9,"B10","36"]

For each card rejected by John, maintainn a redis set for him.

"cards_rejected_by_john_set" : ["91","A3","36"]

Now to calculate the available cards from dealer 1 and dealer 2 for John

use redis command

SINTER dealer_1_set dealer_2_set ... (add more dealer sets if you want)

You get new set, say temp_set ["91","A1","A2,"A3"]

Then use redis command

SDIFF temp_set cards_rejected_by_john_set

Then you get ["A1", "A2"] , which is the result.

The above operations should be pretty fast. But to make the above actions atomic, you'll need to write a Lua script.

AwesomeHunter
  • 690
  • 6
  • 10
  • Believe it or not, you beat me to writing the answer here. This is exactly what I did. I will mark this as answer as a thank you. One thing though, as far as I understand, Redis already manages the atomicity, what exactly should I watch for? – Nour Jul 17 '20 at 15:30
  • Redis ensures atomicity for single commands. You have two commands, SINTER and SDIFF, with the second one relies on the result produced by the first one. In this case, to ensure atomicity, you need to have a lua script to combine these two commands together. Unless you don't care about the atomicity here, or the concurrency of this application is fairly modest. E.g., you don't mind someone adds a new card to dealer 1, after the SINTER command, but before the SDIFF command, which makes the result slightly inaccurate – AwesomeHunter Jul 17 '20 at 17:10
  • The process doing SINTER could be different from the one doing SDIFF. I want the number of available cards to be accurate to the last `adding a card` operation and that's all. – Nour Jul 17 '20 at 17:13