mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
docs: add form to estimate realtime throughput (#20260)
* docs: add form to estimate realtime throughput * fix: compute select resetting rls and filters * fix: typos * docs: info on compute impact on realtime streaming * docs: add 200k realtime test results * docs: fix realtime single thread processing desc Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> * docs: fix copy for realtime throughput estimation Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> * fix: review comments and add collapsed table view * fix: change concurrency to connected clients * docs: add rt 100k test results --------- Co-authored-by: Charis <26616127+charislam@users.noreply.github.com>
This commit is contained in:
1 parent
36a0d62404
commit
fb30063d41
3 files changed
+440
-36
No files matched your search
@@ -0,0 +1,203 @@
|
||||
import { useState } from 'react'
|
||||
import { Select, Collapsible, Button, IconChevronDown } from 'ui'
|
||||
import throughputTable from '~/data/realtime/throughput.json'
|
||||
|
||||
export default function RealtimeLimitsEstimater({}) {
|
||||
const findTableValue = ({ computeAddOn, filters, rls, concurrency }) => {
|
||||
return throughputTable.find(
|
||||
(l) =>
|
||||
l.computeAddOn === computeAddOn &&
|
||||
l.filters === filters &&
|
||||
l.rls === rls &&
|
||||
l.concurrency === concurrency
|
||||
)
|
||||
}
|
||||
|
||||
const [computeAddOn, setComputeAddOn] = useState('micro')
|
||||
const [filters, setFilters] = useState(false)
|
||||
const [rls, setRLS] = useState(false)
|
||||
const [concurrency, setConcurrency] = useState(500)
|
||||
|
||||
const [limits, setLimits] = useState(findTableValue({ computeAddOn, filters, rls, concurrency }))
|
||||
|
||||
const [expandPreview, setExpandPreview] = useState(false)
|
||||
|
||||
const handleComputeAddOnSelection = (e) => {
|
||||
const val = e.target.value
|
||||
setComputeAddOn(val)
|
||||
setConcurrency(500)
|
||||
setLimits(findTableValue({ computeAddOn: val, filters, rls, concurrency: 500 }))
|
||||
}
|
||||
|
||||
const handleFiltersSelection = (e) => {
|
||||
const val = e.target.value.toLowerCase() === 'true'
|
||||
setFilters(val)
|
||||
setConcurrency(500)
|
||||
setLimits(findTableValue({ computeAddOn, filters: val, rls, concurrency: 500 }))
|
||||
}
|
||||
|
||||
const handleRLSSelection = (e) => {
|
||||
const val = e.target.value.toLowerCase() === 'true'
|
||||
setRLS(val)
|
||||
setConcurrency(500)
|
||||
setLimits(findTableValue({ computeAddOn, filters, rls: val, concurrency: 500 }))
|
||||
}
|
||||
|
||||
const handleConcurrencySelection = (e) => {
|
||||
const val = parseInt(e.target.value)
|
||||
setConcurrency(val)
|
||||
setLimits(findTableValue({ computeAddOn, filters, rls, concurrency: val }))
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4>Set your expected parameters</h4>
|
||||
<div className="grid mb-8 gap-y-8 gap-x-8 grid-cols-2 xl:grid-cols-4">
|
||||
<div>
|
||||
<label htmlFor="computeAddOn">Compute:</label>
|
||||
<Select id="computeAddOn" className="font-mono" onChange={handleComputeAddOnSelection}>
|
||||
<Select.Option value="micro">Micro</Select.Option>
|
||||
<Select.Option value="small">Small to medium</Select.Option>
|
||||
<Select.Option value="large">Large to 16XL</Select.Option>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="filters">Filters:</label>
|
||||
<Select
|
||||
id="filters"
|
||||
className="font-mono"
|
||||
disabled={true}
|
||||
onChange={handleFiltersSelection}
|
||||
>
|
||||
<Select.Option value="false">No</Select.Option>
|
||||
<Select.Option value="true">Yes</Select.Option>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="rls">RLS:</label>
|
||||
<Select id="rls" className="font-mono" onChange={handleRLSSelection}>
|
||||
<Select.Option value="false">No</Select.Option>
|
||||
<Select.Option value="true">Yes</Select.Option>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="concurrency">Connected clients:</label>
|
||||
<Select id="concurrency" className="font-mono" onChange={handleConcurrencySelection}>
|
||||
{throughputTable
|
||||
.filter(
|
||||
(l) => l.computeAddOn === computeAddOn && l.filters === filters && l.rls === rls
|
||||
)
|
||||
.map((l) => (
|
||||
<Select.Option
|
||||
value={l.concurrency.toString()}
|
||||
selected={l.concurrency === concurrency}
|
||||
>
|
||||
{Intl.NumberFormat().format(l.concurrency)}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{limits && (
|
||||
<div className="mt-8">
|
||||
<h4>Current maximum possible throughput</h4>
|
||||
|
||||
<table className="table-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2">Total DB changes /sec</th>
|
||||
<th className="px-4 py-2">Max messages per client /sec</th>
|
||||
<th className="px-4 py-2">Max total messages /sec</th>
|
||||
<th className="px-4 py-2">Latency p95</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border px-4 py-2">{limits.maxDBChanges}</td>
|
||||
<td className="border px-4 py-2">{limits.maxMessagesPerClient}</td>
|
||||
<td className="border px-4 py-2">
|
||||
{Intl.NumberFormat().format(limits.totalMessagesPerSecond)}
|
||||
</td>
|
||||
<td className="border px-4 py-2">{limits.p95Latency}ms</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Collapsible open={expandPreview} onOpenChange={setExpandPreview}>
|
||||
<Collapsible.Trigger asChild>
|
||||
<div className="py-1 flex items-center">
|
||||
<p className="text-sm">View raw throughput table</p>
|
||||
<Button
|
||||
type="text"
|
||||
icon={
|
||||
<IconChevronDown
|
||||
size={18}
|
||||
strokeWidth={2}
|
||||
className={expandPreview && 'rotate-180'}
|
||||
/>
|
||||
}
|
||||
className="px-1"
|
||||
onClick={() => setExpandPreview(!expandPreview)}
|
||||
/>
|
||||
</div>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
<div>
|
||||
{throughputTable
|
||||
.map((l) => l.computeAddOn)
|
||||
.filter((v, i, a) => a.indexOf(v) === i)
|
||||
.map((computeAddOn) => (
|
||||
<div>
|
||||
<h4>
|
||||
{computeAddOn === 'micro'
|
||||
? 'Micro'
|
||||
: computeAddOn === 'small'
|
||||
? 'Small to medium'
|
||||
: 'Large to 16XL'}
|
||||
</h4>
|
||||
<table className="table-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2">Filters</th>
|
||||
<th className="px-4 py-2">RLS</th>
|
||||
<th className="px-4 py-2">Connected clients</th>
|
||||
<th className="px-4 py-2">Total DB changes /sec</th>
|
||||
<th className="px-4 py-2">Max messages per client /sec</th>
|
||||
<th className="px-4 py-2">Max total messages /sec</th>
|
||||
<th className="px-4 py-2">Latency p95</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{throughputTable
|
||||
.filter((l) => l.computeAddOn === computeAddOn)
|
||||
.map((l) => (
|
||||
<tr>
|
||||
<td className="border px-4 py-2">{l.filters ? '✅' : '🚫'}</td>
|
||||
<td className="border px-4 py-2">{l.rls ? '✅' : '🚫'}</td>
|
||||
<td className="border px-4 py-2">
|
||||
{Intl.NumberFormat().format(l.concurrency)}
|
||||
</td>
|
||||
<td className="border px-4 py-2">{l.maxDBChanges}</td>
|
||||
<td className="border px-4 py-2">{l.maxMessagesPerClient}</td>
|
||||
<td className="border px-4 py-2">
|
||||
{Intl.NumberFormat().format(l.totalMessagesPerSecond)}
|
||||
</td>
|
||||
<td className="border px-4 py-2">{l.p95Latency}ms</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Collapsible.Content>
|
||||
</Collapsible>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
[
|
||||
{
|
||||
"computeAddOn": "micro",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 500,
|
||||
"maxDBChanges": 64,
|
||||
"maxMessagesPerClient": 64,
|
||||
"totalMessagesPerSecond": 32000,
|
||||
"p95Latency": 238
|
||||
},
|
||||
{
|
||||
"computeAddOn": "micro",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 5000,
|
||||
"maxDBChanges": 10,
|
||||
"maxMessagesPerClient": 10,
|
||||
"totalMessagesPerSecond": 50000,
|
||||
"p95Latency": 807
|
||||
},
|
||||
{
|
||||
"computeAddOn": "micro",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 10000,
|
||||
"maxDBChanges": 5,
|
||||
"maxMessagesPerClient": 5,
|
||||
"totalMessagesPerSecond": 50000,
|
||||
"p95Latency": 1310
|
||||
},
|
||||
{
|
||||
"computeAddOn": "micro",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 30000,
|
||||
"maxDBChanges": 1,
|
||||
"maxMessagesPerClient": 1,
|
||||
"totalMessagesPerSecond": 30000,
|
||||
"p95Latency": 941
|
||||
},
|
||||
{
|
||||
"computeAddOn": "micro",
|
||||
"filters": false,
|
||||
"rls": true,
|
||||
"concurrency": 500,
|
||||
"maxDBChanges": 30,
|
||||
"maxMessagesPerClient": 6,
|
||||
"totalMessagesPerSecond": 3000,
|
||||
"p95Latency": 228
|
||||
},
|
||||
{
|
||||
"computeAddOn": "micro",
|
||||
"filters": false,
|
||||
"rls": true,
|
||||
"concurrency": 1500,
|
||||
"maxDBChanges": 10,
|
||||
"maxMessagesPerClient": 2,
|
||||
"totalMessagesPerSecond": 3000,
|
||||
"p95Latency": 356
|
||||
},
|
||||
{
|
||||
"computeAddOn": "micro",
|
||||
"filters": false,
|
||||
"rls": true,
|
||||
"concurrency": 3000,
|
||||
"maxDBChanges": 5,
|
||||
"maxMessagesPerClient": 1,
|
||||
"totalMessagesPerSecond": 3000,
|
||||
"p95Latency": 616
|
||||
},
|
||||
{
|
||||
"computeAddOn": "small",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 500,
|
||||
"maxDBChanges": 64,
|
||||
"maxMessagesPerClient": 64,
|
||||
"totalMessagesPerSecond": 32000,
|
||||
"p95Latency": 184
|
||||
},
|
||||
{
|
||||
"computeAddOn": "small",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 5000,
|
||||
"maxDBChanges": 10,
|
||||
"maxMessagesPerClient": 10,
|
||||
"totalMessagesPerSecond": 50000,
|
||||
"p95Latency": 782
|
||||
},
|
||||
{
|
||||
"computeAddOn": "small",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 10000,
|
||||
"maxDBChanges": 5,
|
||||
"maxMessagesPerClient": 5,
|
||||
"totalMessagesPerSecond": 50000,
|
||||
"p95Latency": 1349
|
||||
},
|
||||
{
|
||||
"computeAddOn": "small",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 35000,
|
||||
"maxDBChanges": 1,
|
||||
"maxMessagesPerClient": 1,
|
||||
"totalMessagesPerSecond": 35000,
|
||||
"p95Latency": 1287
|
||||
},
|
||||
{
|
||||
"computeAddOn": "small",
|
||||
"filters": false,
|
||||
"rls": true,
|
||||
"concurrency": 500,
|
||||
"maxDBChanges": 30,
|
||||
"maxMessagesPerClient": 6,
|
||||
"totalMessagesPerSecond": 3000,
|
||||
"p95Latency": 282
|
||||
},
|
||||
{
|
||||
"computeAddOn": "small",
|
||||
"filters": false,
|
||||
"rls": true,
|
||||
"concurrency": 1500,
|
||||
"maxDBChanges": 10,
|
||||
"maxMessagesPerClient": 2,
|
||||
"totalMessagesPerSecond": 3000,
|
||||
"p95Latency": 387
|
||||
},
|
||||
{
|
||||
"computeAddOn": "small",
|
||||
"filters": false,
|
||||
"rls": true,
|
||||
"concurrency": 3000,
|
||||
"maxDBChanges": 5,
|
||||
"maxMessagesPerClient": 1,
|
||||
"totalMessagesPerSecond": 3000,
|
||||
"p95Latency": 920
|
||||
},
|
||||
{
|
||||
"computeAddOn": "large",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 500,
|
||||
"maxDBChanges": 64,
|
||||
"maxMessagesPerClient": 64,
|
||||
"totalMessagesPerSecond": 32000,
|
||||
"p95Latency": 184
|
||||
},
|
||||
{
|
||||
"computeAddOn": "large",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 5000,
|
||||
"maxDBChanges": 10,
|
||||
"maxMessagesPerClient": 10,
|
||||
"totalMessagesPerSecond": 50000,
|
||||
"p95Latency": 672
|
||||
},
|
||||
{
|
||||
"computeAddOn": "large",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 10000,
|
||||
"maxDBChanges": 5,
|
||||
"maxMessagesPerClient": 5,
|
||||
"totalMessagesPerSecond": 50000,
|
||||
"p95Latency": 1253
|
||||
},
|
||||
{
|
||||
"computeAddOn": "large",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 35000,
|
||||
"maxDBChanges": 1,
|
||||
"maxMessagesPerClient": 1,
|
||||
"totalMessagesPerSecond": 35000,
|
||||
"p95Latency": 1257
|
||||
},
|
||||
{
|
||||
"computeAddOn": "large",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 100000,
|
||||
"maxDBChanges": "0.1 (6/min)",
|
||||
"maxMessagesPerClient": "0.1 (6/min)",
|
||||
"totalMessagesPerSecond": 40000,
|
||||
"p95Latency": 4951
|
||||
},
|
||||
{
|
||||
"computeAddOn": "large",
|
||||
"filters": false,
|
||||
"rls": false,
|
||||
"concurrency": 200000,
|
||||
"maxDBChanges": "0.05 (3/min)",
|
||||
"maxMessagesPerClient": "0.05 (3/min)",
|
||||
"totalMessagesPerSecond": 40000,
|
||||
"p95Latency": 4581
|
||||
},
|
||||
{
|
||||
"computeAddOn": "large",
|
||||
"filters": false,
|
||||
"rls": true,
|
||||
"concurrency": 500,
|
||||
"maxDBChanges": 40,
|
||||
"maxMessagesPerClient": 8,
|
||||
"totalMessagesPerSecond": 4000,
|
||||
"p95Latency": 618
|
||||
},
|
||||
{
|
||||
"computeAddOn": "large",
|
||||
"filters": false,
|
||||
"rls": true,
|
||||
"concurrency": 2000,
|
||||
"maxDBChanges": 10,
|
||||
"maxMessagesPerClient": 2,
|
||||
"totalMessagesPerSecond": 4000,
|
||||
"p95Latency": 606
|
||||
},
|
||||
{
|
||||
"computeAddOn": "large",
|
||||
"filters": false,
|
||||
"rls": true,
|
||||
"concurrency": 4000,
|
||||
"maxDBChanges": 5,
|
||||
"maxMessagesPerClient": 1,
|
||||
"totalMessagesPerSecond": 4000,
|
||||
"p95Latency": 918
|
||||
}
|
||||
]
|
||||
@@ -1,5 +1,6 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
import RealtimeLimitsEstimater from '~/components/RealtimeLimitsEstimater'
|
||||
|
||||
export const meta = {
|
||||
title: 'Postgres Changes',
|
||||
@@ -1361,45 +1362,13 @@ Realtime systems usually require forethought because of their scaling dynamics.
|
||||
|
||||
There can be a database bottleneck which limits message throughput. If your database cannot authorize the changes rapidly enough, the changes will be delayed until you receive a timeout.
|
||||
|
||||
Database changes are processed on a single thread to maintain the change order. That means compute upgrades don't have a large effect on the performance of Postgres change subscriptions. You can estimate the expected maximum throughput for your database below.
|
||||
|
||||
If you are using Postgres Changes at scale, you should consider using separate "public" table without RLS and filters. Alternatively, you can use Realtime server-side only and then re-stream the changes to your clients using a Realtime Broadcast.
|
||||
|
||||
From our observations, we recommend the following limits depending on your database size:
|
||||
Enter your database settings to estimate the maximum throughput for your instance:
|
||||
|
||||
### Micro
|
||||
|
||||
| Filters | RLS Usage | Concurrent Clients | DB Changes per second (total) | Records per second per client | Messages per second (total) | Latency p95 (ms) |
|
||||
| ------- | --------- | ------------------ | ----------------------------- | ----------------------------- | --------------------------- | ---------------- |
|
||||
| 🚫 | 🚫 | 500 | 64 | 64 | 32,000 | 238 |
|
||||
| 🚫 | 🚫 | 5,000 | 10 | 10 | 50,000 | 807 |
|
||||
| 🚫 | 🚫 | 10,000 | 5 | 5 | 50,000 | 1310 |
|
||||
| 🚫 | 🚫 | 30,000 | 1 | 1 | 30,000 | 941 |
|
||||
| 🚫 | ✅ | 500 | 30 | 6 | 3,000 | 228 |
|
||||
| 🚫 | ✅ | 1,500 | 10 | 2 | 3,000 | 356 |
|
||||
| 🚫 | ✅ | 3,000 | 5 | 1 | 3,000 | 616 |
|
||||
|
||||
### Small to medium
|
||||
|
||||
| Filters | RLS Usage | Concurrent Clients | DB Changes per second (total) | Records per second per client | Messages per second (total) | Latency p95 (ms) |
|
||||
| ------- | --------- | ------------------ | ----------------------------- | ----------------------------- | --------------------------- | ---------------- |
|
||||
| 🚫 | 🚫 | 500 | 64 | 64 | 32,000 | 184 |
|
||||
| 🚫 | 🚫 | 5,000 | 10 | 10 | 50,000 | 782 |
|
||||
| 🚫 | 🚫 | 10,000 | 5 | 5 | 50,000 | 1349 |
|
||||
| 🚫 | 🚫 | 35,000 | 1 | 1 | 35,000 | 1287 |
|
||||
| 🚫 | ✅ | 500 | 30 | 6 | 3,000 | 282 |
|
||||
| 🚫 | ✅ | 1,500 | 10 | 2 | 3,000 | 387 |
|
||||
| 🚫 | ✅ | 3,000 | 5 | 1 | 3,000 | 920 |
|
||||
|
||||
### Large to 16XL
|
||||
|
||||
| Filters | RLS Usage | Concurrent Clients | DB Changes per second (total) | Records per second per client | Messages per second (total) | Latency p95 (ms) |
|
||||
| ------- | --------- | ------------------ | ----------------------------- | ----------------------------- | --------------------------- | ---------------- |
|
||||
| 🚫 | 🚫 | 500 | 64 | 64 | 32,000 | 184 |
|
||||
| 🚫 | 🚫 | 5,000 | 10 | 10 | 50,000 | 672 |
|
||||
| 🚫 | 🚫 | 10,000 | 5 | 5 | 50,000 | 1253 |
|
||||
| 🚫 | 🚫 | 35,000 | 1 | 1 | 35,000 | 1257 |
|
||||
| 🚫 | ✅ | 500 | 40 | 8 | 4,000 | 618 |
|
||||
| 🚫 | ✅ | 2,000 | 10 | 2 | 4,000 | 606 |
|
||||
| 🚫 | ✅ | 4,000 | 5 | 1 | 4,000 | 918 |
|
||||
<RealtimeLimitsEstimater />
|
||||
|
||||
Don't forget to run your own benchmarks to make sure that the performance is acceptable for your use case.
|
||||
|
||||
|
||||
Reference in new issue
Block a user