Bucketed Leaderboards and Storage Locks

Hello guys.

I have encountered an issue with the storage system. What I am trying to achieve is consistent bucketed leaderboards. Players should be assigned a bucket when they first play a game. The bucket data for each user is saved as an object in storage with keys CollectionKey: "leaderboards-users" Key: "leaderboard_id:bucket-data" for each person. The leaderboards also have bucket objects in the storage. Each leaderboard has a bucket holder, which holds the names of buckets assigned to leaderboards, and for each bucket, there is an object in storage that holds all users assigned to the bucket.

The problem is that when multiple people play games, when the user count increases, the chance of a race condition rises significantly. I have tried using the optimistic concurrency that the storage provides, but I haven’t been able to make much progress since there are a lot of edge cases (roll-backs and so on). Aside from that, I’m assuming retrying numerous times is not intended, plus it makes the code hard to read and follow.

Are there any simple solutions for this problem? I could use a mutex lock, but that seems to be heavily discouraged, and I’m not sure what would happen to the lock when running multiple Nakama instances in a cluster.

Also, I have thought of introducing a queue that responds to these bucket assignment requests, but that solves the last concurrency issue and introduces another.

Hi @ErfanNikouie,

Your approach is correct, the only thing I’d add is the use of StorageSearch to look for suitable bucket (one that has not been filled yet), this will allow to lookup a suitable object efficiently. Make sure you also register a filter so that the index only keeps non-empty buckets in it.

If there’s multiple bucket assignments happening concurrently, the optimistic Storage Engine write model will still cause only the 1st successful write to “succeed”, causing the others to fail (assuming you’re using the “version” field). In this case, retrying some limited amount of times is ok and expected, I’d add some built-in retries on write version mismatch error, but you’d need to make sure your retry logic rereads from the StorageIndex to get an updated suitable bucket, then retry the storage write with the new bucket.

Hope this helps.