fix(studio): bump realtime max_concurrent_users soft limit (#50438)

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Bump realtime max_concurrent_users soft limit to 300k

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Increased the maximum supported concurrent clients from 50,000 to
300,000 when plan entitlements allow it.

* **Bug Fixes**
* Improved validation for concurrent-client limits, including clearer
handling of entitlement-based and unlimited limits.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
Eduardo GurgelandJoshen Lim authored and GitHub committed 2026-09-17 03:12:28 +00:00
1 parent babebc959c
commit c15b0836d8
2 files changed
+84 -14

No files matched your search

@@ -68,19 +68,28 @@ const REALTIME_CONFIG = {
suspend: false,
} as const satisfies RealtimeConfigurationData
const REALTIME_ENTITLEMENTS: Entitlement[] = (
[
['realtime.max_concurrent_users', 50_000],
['realtime.max_events_per_second', 50_000],
['realtime.max_presence_events_per_second', 5_000],
['realtime.max_payload_size_in_kb', 3_000],
] satisfies [FeatureKey, number][]
).map(([key, value]) => ({
config: { enabled: true, unit: '', unlimited: false, value },
feature: { key, type: 'numeric' },
hasAccess: true,
type: 'numeric',
}))
const buildRealtimeEntitlements = (
overrides: Partial<Record<FeatureKey, number | 'unlimited'>> = {}
): Entitlement[] =>
(
[
['realtime.max_concurrent_users', 50_000],
['realtime.max_events_per_second', 50_000],
['realtime.max_presence_events_per_second', 5_000],
['realtime.max_payload_size_in_kb', 3_000],
] satisfies [FeatureKey, number][]
).map(([key, value]) => {
const override = overrides[key]
const unlimited = override === 'unlimited'
return {
config: { enabled: true, unit: '', unlimited, value: unlimited ? 0 : (override ?? value) },
feature: { key, type: 'numeric' },
hasAccess: true,
type: 'numeric',
}
})
const REALTIME_ENTITLEMENTS: Entitlement[] = buildRealtimeEntitlements()
describe('RealtimeSettings', () => {
beforeEach(() => {
@@ -199,4 +208,65 @@ describe('RealtimeSettings', () => {
expect(requests).toHaveLength(0)
}
})
test.each([
{ value: '300000', accepted: true },
{ value: '300001', accepted: false },
])(
'$accepted for max concurrent clients of $value when the entitlement is unlimited',
async ({ value, accepted }) => {
addAPIMock({
method: 'get',
path: '/platform/organizations/:slug/entitlements',
response: {
entitlements: buildRealtimeEntitlements({ 'realtime.max_concurrent_users': 'unlimited' }),
},
})
const requests: unknown[] = []
addAPIMock({
method: 'patch',
path: '/platform/projects/:ref/config/realtime',
response: async ({ request }) => {
requests.push(await request.json())
return new HttpResponse(null, { status: 204 })
},
})
customRender(<RealtimeSettings />)
const maxConcurrentUsersInput = await screen.findByLabelText('Max concurrent clients')
await userEvent.clear(maxConcurrentUsersInput)
await userEvent.type(maxConcurrentUsersInput, value)
fireEvent.click(screen.getByRole('button', { name: 'Save changes' }))
if (accepted) {
const dialog = await screen.findByRole('dialog')
fireEvent.click(within(dialog).getByRole('button', { name: 'Save changes' }))
await waitFor(() => expect(requests).toHaveLength(1))
expect(requests[0]).toMatchObject({ max_concurrent_users: Number(value) })
} else {
expect(
await screen.findByText('Cannot exceed 300,000 concurrent clients')
).toBeInTheDocument()
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
expect(requests).toHaveLength(0)
}
}
)
test('rejects max concurrent clients above the entitlement when it is below the soft limit', async () => {
customRender(<RealtimeSettings />)
const maxConcurrentUsersInput = await screen.findByLabelText('Max concurrent clients')
await userEvent.clear(maxConcurrentUsersInput)
await userEvent.type(maxConcurrentUsersInput, '50001')
fireEvent.click(screen.getByRole('button', { name: 'Save changes' }))
expect(await screen.findByText('Cannot exceed 50,000 concurrent clients')).toBeInTheDocument()
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
})
})
@@ -44,7 +44,7 @@ import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
const formId = 'realtime-configuration-form'
const REALTIME_SOFT_LIMITS = {
max_concurrent_users: 50_000,
max_concurrent_users: 300_000,
max_events_per_second: 50_000,
max_presence_events_per_second: 5_000,
max_payload_size_in_kb: 3_000,