I need to implement some kind of “matchmaking” process. In our game, a player can attack other-player (to be selected by the server according to predefined rules) the other player does not have to be online, but the rules are pretty much similar to any matchmaking rules. (rank, level, currency, etc.)
I tried to find a way to use your matchmaking system but it requires the players to be online.
and also in my case, all the players are always, passively, waiting to be matched, so I am not sure about the performance and how it will work in a distributed system.
Another option I researched for is to simply create my own matchmaking collection and whenever a user requesting for a match, to execute a query against this collection, (which will basically hold all the users who are available to be attacked) but I couldn’t find a way to apply custom filters using the runtime storageList method.
currently, it simply leaves me with the option of creating a table by myself and executing a custom query using the go SQL package by myself. which means managing performance, retries, errors and a lot of low-level logic I really tried to avoid by using your solution.
I would love to know if you have any suggestion, this is a pretty common thing in social casual games, so maybe you tackled this issue in a different way already.
Or maybe (from your experience) there is a way to implement a solution similar to your matchmaking, using bleve that will work for me using your distributed system.
(means that whenever the server is starting all the users will be manually added to the index, and of course whenever a new user is signed up)
This is a great question. We often refer to it as passive or async matchmaking because you’re searching for players without them online. We’ve not yet developed a great builtin feature for it because its very game specific. We do have a few scalable patterns we use when we work with game teams on their projects that involve this feature.
- Take advantage of the random distribution of user IDs (UUIDv4 types)
- Use custom SQL indexes against the storage engine collection that contains the properties to search.
- Use Bleve in-memory to manage your own search index with the Go runtime.
The approach to use depends on how complex the criteria is you want to matchmake players on. I can go into more detail if you can share some examples of the kind of fields you want to match on.
I am trying to develop a flexible enough system to support different rules for different match types.
But a representative example will be:
- if the player is a Facebook friend.
- if the currency is between x to y.
- player region
- if the player has a linked Facebook account.
The idea is to implement some kind of waterfall and change the values of these properties to find a match.
My concern about using bleve in-memory (which is, of course, the preferred option) is to run-out-of memory. I saw that at least in the open-source version the matchmaker is also in memory, but I guess that you have a distributed solution in the enterprise version?
Anyway, I would love to hear your thoughts. Thanks.
My concern about using bleve in-memory (which is, of course, the preferred option) is to run-out-of memory.
@oshribin The Bleve index is unlikely to be overwhelmed from a memory perspective because you can constrain the important data properties you want to match players on. In your example it will be 4 properties which could be stored in the index.
but I guess that you have a distributed solution in the enterprise version?
Yes we have a distributed runtime that replicates some core datasets across the cluster and converges them. Each Bleve index uses this dataset to build its indexes in-memory on the nodes.
To solve your example I’d approach it as follows:
- Setup the Bleve index at server startup. Define the schema for the fields to be indexed and their types.
- Stream the records from the database in batches. It’s important not to flood the server at startup with a long lived database query so make sure you move this work onto a goroutine.
- It’s unlikely you need to fetch all records out of the database to achieve a useful dataset for passive matchmaker logic. You’ll need to decide on what size of dataset would be useful to bootstrap the matchmaker.
- Use active player behaviour to populate the dataset with additional records “on demand”.
- Read from the matchmaker when you want to have players matched against opponents with an RPC function.
Does this look like it fits your requirements? Have you taken another approach?
So, my first intention was to implement something very similar to your suggestion. But because of:
memory concerns, after your answer it is less concerning, though I don’t know, with hopefully millions of users it will be very hard to scale. unless I had the ability to shard it across the nodes.
Consistency, again, the suggested solution means having this dataset replicated on each node. without having control of the nodes, consistency is impossible.
Use active player behaviour to populate the dataset with additional records “on demand”.
If I want this record to be available for all the player I will have to distribute across the nodes somehow.
- Persistency, some of the data need to be persistent, to manage cooldown, for example.
So, tradeoffs had to be made :). I decided to implement it using the storage system, with manual inverted indexing and custom queries.
Anyway, I really appreciate your help. Thanks a lot.
@oshribin Sounds good. If you want me to peek at the queries and make any performance suggestions share them here