From fb30063d41079b597ad61f5c4ab698d3c73c7b53 Mon Sep 17 00:00:00 2001 From: egor <58992960+egor-romanov@users.noreply.github.com> Date: Mon, 15 Jan 2024 14:24:47 +0400 Subject: [PATCH] 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> --- .../components/RealtimeLimitsEstimater.tsx | 203 +++++++++++++++ apps/docs/data/realtime/throughput.json | 232 ++++++++++++++++++ .../guides/realtime/postgres-changes.mdx | 41 +--- 3 files changed, 440 insertions(+), 36 deletions(-) create mode 100644 apps/docs/components/RealtimeLimitsEstimater.tsx create mode 100644 apps/docs/data/realtime/throughput.json diff --git a/apps/docs/components/RealtimeLimitsEstimater.tsx b/apps/docs/components/RealtimeLimitsEstimater.tsx new file mode 100644 index 00000000000..d8e153c2d3b --- /dev/null +++ b/apps/docs/components/RealtimeLimitsEstimater.tsx @@ -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 ( +
+

Set your expected parameters

+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + {limits && ( +
+

Current maximum possible throughput

+ + + + + + + + + + + + + + + + + + +
Total DB changes /secMax messages per client /secMax total messages /secLatency p95
{limits.maxDBChanges}{limits.maxMessagesPerClient} + {Intl.NumberFormat().format(limits.totalMessagesPerSecond)} + {limits.p95Latency}ms
+
+ )} + + + +
+

View raw throughput table

+
+
+ +
+ {throughputTable + .map((l) => l.computeAddOn) + .filter((v, i, a) => a.indexOf(v) === i) + .map((computeAddOn) => ( +
+

+ {computeAddOn === 'micro' + ? 'Micro' + : computeAddOn === 'small' + ? 'Small to medium' + : 'Large to 16XL'} +

+ + + + + + + + + + + + + + {throughputTable + .filter((l) => l.computeAddOn === computeAddOn) + .map((l) => ( + + + + + + + + + + ))} + +
FiltersRLSConnected clientsTotal DB changes /secMax messages per client /secMax total messages /secLatency p95
{l.filters ? '✅' : '🚫'}{l.rls ? '✅' : '🚫'} + {Intl.NumberFormat().format(l.concurrency)} + {l.maxDBChanges}{l.maxMessagesPerClient} + {Intl.NumberFormat().format(l.totalMessagesPerSecond)} + {l.p95Latency}ms
+
+ ))} +
+
+
+
+ ) +} diff --git a/apps/docs/data/realtime/throughput.json b/apps/docs/data/realtime/throughput.json new file mode 100644 index 00000000000..2afc1e6d862 --- /dev/null +++ b/apps/docs/data/realtime/throughput.json @@ -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 + } +] diff --git a/apps/docs/pages/guides/realtime/postgres-changes.mdx b/apps/docs/pages/guides/realtime/postgres-changes.mdx index 3dedcf71c25..b4305c293c8 100644 --- a/apps/docs/pages/guides/realtime/postgres-changes.mdx +++ b/apps/docs/pages/guides/realtime/postgres-changes.mdx @@ -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 | + Don't forget to run your own benchmarks to make sure that the performance is acceptable for your use case.