The backend Multiplayer API has a series of request rate limits which must be adhered to when working with our services.
This article outlines some best practices and common issues/solutions when working with the multiplayer services package.
What are the Rate Limits
You can find the rate limits applied for each individual service at:
- Lobby: https://docs.unity.com/ugs/en-us/manual/lobby/manual/rate-limits
- Relay: https://docs.unity.com/ugs/en-us/manual/relay/manual/rate-limits
- Matchmaker: https://docs.unity.com/ugs/en-us/manual/matchmaker/manual/matchmaker-rate-limits
Using the Multiplayer services package
When using the multiplayer services package, many of the API interactions are handled within SDK, this means that you shouldn't need to worry much about the Rate Limits as long as you wait for an execution to finish before starting another process for each client.
This can be done by simply adding a lock or flag check to prevent subsequent SDK calls whilst one is still ongoing.
Through Sessions, many of the standalone services (Lobby, Relay, Matchmaker) are handled internally, this should help reduce the impact of Rate Limited errors on your clients.
Common Rate Limited Error causes
To help further understand why your project is experiencing rate limited errors, here are a few examples of some common causes and some suggested workarounds
Calling Update Session to push minor changes too often
Updating property information too quickly, or sending predictable data as property can add unnecessary requests, these should be grouped or ignored as necessary.
Join/Leave session requests too soon
Players attempting to spam entry/leave requests for sessions, either due to non-reactive UI or through spam will result in quick requests to the API, these should be throttled and handled on a project-by-project basis
Aggressive polling of List/Get session
Trying to keep session data up to date, or allowing players to refresh game lists too frequently will often result in the Rate Limit being exceeded, which in turn will return an error and depending on your error handling flow for your project, this may result in empty game lists being visible to players, or being unable to get up to date session information
Creating sessions too frequently
Similar to aggressive polling and join/leave session request spamming, this will often happen due to a non-reactive UI to user input or without proper queueing of requests. It is possible for players to be in multiple sessions at one time, so the SDK will not inherently prevent a player creating multiple sessions.
Retry Logic without backoff
When a request fails to execute, a simple retry request will likely hit a rate limit, especially if it fails at the gateway level as this will likely return an error with 1s and cause subsequent Rate Limit errors. If your project uses retry logic
Best Practices & Recommendations
Here are a list of suggestions to improve the stability of your project and improve user experience:
-
Implement Request Locking: Use a flag or state manager to prevent the SDK from being called while a previous request is still
awaitingcompletion. - Queue API Calls: Use a "SessionManager" to queue requests, ensuring they are processed sequentially rather than in parallel.
-
Avoid Update Loops: Never call SDK methods directly inside
Update()orFixedUpdate()loops. -
Batch Updates: Combine multiple property changes into a single
UpdateSessioncall rather than sending them individually. -
Event-Driven Architecture: Rely on events (e.g.,
OnSessionChanged,OnValueChanged) rather than polling to update game state. - Cache Data: Avoid redundant reads; if the data hasn't changed locally, do not re-fetch it from the service.
- Utilize UX to delay inputs: Adding loading screens and input blockers can improve user experience, app responsiveness and increase time between requests
- Limit parallel requests per client: Create/Join/Update requests should be staggered to avoid bursts at startup
- Initialize Unity Services and sign in once: reuse the session across scenes and let the SDK refresh tokens to maintain access
Examples
Here are a few examples of applying the recommendations into your project
Request Locking (preventing spam)
private bool _isBusy = false;
public async void CreateSessionSafeAsync()
{
// 1. Check if an operation is already in progress
if (_isBusy)
{
Debug.LogWarning("Request ignored: Operation already in progress.");
return;
}
_isBusy = true;
try
{
// 2. Perform the SDK call
var options = new SessionOptions { Name = "MySession", MaxPlayers = 4 };
var session = await MultiplayerService.Instance.CreateSessionAsync(options);
Debug.Log($"Session {session.Id} created successfully.");
}
catch (System.Exception e)
{
Debug.LogError($"Failed to create session: {e.Message}");
}
finally
{
// 3. Always release the lock, even if the request failed
_isBusy = false;
}
}
Batching Updates
public async Task UpdateSessionPropertiesBatchAsync(IHostSession hostSession)
{
// BAD PRACTICE: Calling update twice consumes 2 API requests
// await UpdateMap(hostSession, "WinterMap");
// await UpdateGameMode(hostSession, "CaptureTheFlag");
// BEST PRACTICE: Set multiple properties before saving to combine changes into a single request
hostSession.SetProperties(new Dictionary<string, SessionProperty>()
{
{ "Map", new SessionProperty("WinterMap", VisibilityPropertyOptions.Public) },
{ "GameMode", new SessionProperty("CaptureTheFlag", VisibilityPropertyOptions.Public) }
});
await hostSession.SavePropertiesAsync();
}
Simple Retry with Exponential Backoff
public async Task<ISession> JoinSessionWithBackoffAsync(string sessionId)
{
int maxRetries = 3;
int delayMilliseconds = 1000;
for (int i = 0; i < maxRetries; i++)
{
try
{
return await MultiplayerService.Instance.JoinSessionByIdAsync(sessionId);
}
catch (Unity.Services.Core.RequestFailedException e)
{
if (e.ErrorCode == 429) // Rate Limited
{
// Wait before retrying (Exponential backoff: 1s, 2s, 4s)
await Task.Delay(delayMilliseconds * (int)Mathf.Pow(2, i));
continue;
}
throw; // Rethrow other exceptions
}
}
return null;
}