realtime: improve documentation (#34393)

Improve realtime documentation
---------

Co-authored-by: Copple <10214025+kiwicopple@users.noreply.github.com>
Co-authored-by: Charis <26616127+charislam@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Filipe Cabaço
2025-03-26 17:30:16 +00:00
committed by GitHub
parent 1f6a41e925
commit b2d3ce4e9b
8 changed files with 334 additions and 635 deletions

View File

@@ -1592,10 +1592,6 @@ export const realtime: NavMenuConstant = {
name: 'Listening to Postgres Changes with Flutter',
url: '/guides/realtime/realtime-listening-flutter',
},
{
name: 'Migrate to Broadcast Changes',
url: '/guides/realtime/migrate-from-postgres-changes',
},
],
},
{

View File

@@ -6,163 +6,11 @@ subtitle: 'Send and receive messages to connected clients.'
hideToc: true
---
Supabase provides a globally distributed cluster of [Realtime](https://github.com/supabase/realtime) servers that enable the following functionality:
Supabase provides a globally distributed [Realtime](https://github.com/supabase/realtime) service with the following features:
- [Broadcast](/docs/guides/realtime/broadcast): Send ephemeral messages from client to clients with low latency.
- [Presence](/docs/guides/realtime/presence): Track and synchronize shared state between clients.
- [Postgres Changes](/docs/guides/realtime/postgres-changes): Listen to Postgres database changes and send them to authorized clients.
### Realtime API
By default Realtime is disabled on your database. Let's turn on Realtime for a `todos` table.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Database](https://supabase.com/dashboard/project/_/database/tables) page in the Dashboard.
2. Click on **Publications** in the sidebar.
3. Control which database events are sent by toggling **Insert**, **Update**, and **Delete**.
4. Control which tables broadcast changes by selecting **Source** and toggling each table.
<video width="99%" muted playsInline controls={true}>
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-realtime.mp4"
type="video/mp4"
/>
</video>
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
alter
publication supabase_realtime add table todos;
```
</TabPanel>
</Tabs>
From the client, we can listen to any new data that is inserted into the `todos` table:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
```js
// Initialize the JS client
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
// Create a function to handle inserts
const handleInserts = (payload) => {
console.log('Change received!', payload)
}
// Listen to inserts
supabase
.channel('todos')
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'todos' }, handleInserts)
.subscribe()
```
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
import 'package:supabase_flutter/supabase_flutter.dart';
void main() async {
// Initialize the Flutter client
Supabase.initialize(
url: 'https://<project>.supabase.co',
anonKey: '<your-anon-key>',
realtimeClientOptions: const RealtimeClientOptions(
eventsPerSecond: 2,
),
);
runApp(const MyApp());
}
final supabase = Supabase.instance.client;
void handleInserts(payload) {
print('Change received! $payload');
}
// Listen to inserts
supabase
.channel('todos')
.onPostgresChanges(
event: PostgresChangeEvent.insert,
schema: 'public',
table: 'todos',
callback: handleInserts)
.subscribe();
```
</TabPanel>
<TabPanel id="swift" label="Swift">
```swift
// Initialize the Swift client
import Supabase
let supabase = SupabaseClient(supabaseURL: SUPABASE_URL, supabaseKey: SUPABASE_ANON_KEY)
// Create channel
let channel = await supabase.realtime.channel("todos")
// Create the observations before subscribing
let insertions = await channel.postgresChanges(
InsertAction.self,
schema: "public",
table: "todos"
)
await channel.subscribe()
for await insertion in insertions {
let todo = try insertion.decoded(as: Todo.self)
print("Todo inserted", todo)
}
```
</TabPanel>
<TabPanel id="python" label="Python">
```py
# Initialize the Python client
from supabase import create_client, Client
supabase: Client = create_client(SUPABASE_URL, SUPABASE_ANON_KEY)
# Create a function to handle inserts
def handle_inserts(payload):
print("Change received", payload)
# Listen to inserts
await supabase.channel("todos").on_postgres_changes(
"INSERT", schema="public", table="todos", callback=handle_inserts
).subscribe()
```
</TabPanel>
</Tabs>
Use [subscribe()](/docs/reference/javascript/subscribe) to listen to database changes.
The Realtime API works through PostgreSQL's replication functionality. Postgres sends database changes to a [publication](/docs/guides/database/replication#publications)
called `supabase_realtime`, and by managing this publication you can control which data is broadcast.
- [Broadcast](/docs/guides/realtime/broadcast): Send low-latency messages using the client libraries, REST, or your Database.
- [Presence](/docs/guides/realtime/presence): Track and synchronize shared state between users.
- [Postgres Changes](/docs/guides/realtime/postgres-changes): Listen to Database changes and send them to authorized users.
## Examples

View File

@@ -49,6 +49,14 @@ Realtime knows the region your database is in, and connects to it from the close
Every Realtime region has at least two nodes so if one node goes offline the other node should reconnect and start streaming changes again.
## Broadcast from Postgres
Realtime Broadcast sends messages when changes happen in your database. Behind the scenes, Realtime creates a publication on the `realtime.messages` table. It then reads the Write-Ahead Log (WAL) file for this table, and sends a message whenever an insert happens. Messages are sent as JSON packages over WebSockets.
The `realtime.messages` table is partitioned by day. This allows old messages to be deleted performantly, by dropping old partitions. Partitions are retained for 3 days before being deleted.
Broadcast uses [Realtime Authorization](/docs/guides/realtime/authorization) by default to protect your data.
## Streaming the Write-Ahead Log
A Postgres logical replication slot is acquired when connecting to your database.

View File

@@ -1,22 +1,23 @@
---
title: 'Benchmarks'
subtitle: 'Benchmark results for Supabase Realtime.'
description: 'Check out reference numbers for Supabase Realtime performance.'
subtitle: 'Scalability Benchmarks for Supabase Realtime.'
description: 'Scalability Benchmarks for Supabase Realtime.'
---
This guide explores what can be expected from Realtime's Postgres Changes, broadcast and presence performance. A set of load tests demonstrates its scaling capabilities.
This guide explores the scalability of Realtime's features: Broadcast, Presence, and Postgres Changes.
## Methodology
The benchmarks were conducted using k6, an open-source load testing tool, against a Realtime Cluster deployed on AWS. The cluster configurations used 2-6 nodes, tested in both single-region and multi-region setups, all connected to a single Supabase project. The load generators (k6 servers) were also deployed on AWS to minimize network latency impact on the results. Tests were executed with the full load from the start without warm-up runs.
- The benchmarks are conducted using k6, an open-source load testing tool, against a Realtime Cluster deployed on AWS.
- The cluster configurations use 2-6 nodes, tested in both single-region and multi-region setups, all connected to a single Supabase project.
- The load generators (k6 servers) are deployed on AWS to minimize network latency impact on the results.
- Tests are executed with a full load from the start without warm-up runs.
The metrics collected include message throughput, latency percentiles, CPU and memory utilization, and connection success rates. It's worth noting that performance in production environments may vary based on factors such as network conditions, hardware specifications, and specific usage patterns.
The metrics collected include: message throughput, latency percentiles, CPU and memory utilization, and connection success rates. Note that performance in production environments may vary based on factors such as network conditions, hardware specifications, and specific usage patterns.
## Workloads
The proposed workloads are designed to demonstrate Supabase Realtime's throughput and scalability capabilities. These benchmarks focus on core functionality and common usage patterns.
The benchmarking results include the following workloads:
The proposed workloads are designed to demonstrate Supabase Realtime's throughput and scalability. These benchmarks focus on core functionality and common usage patterns. The benchmarking results include the following workloads:
1. **Broadcast Performance**
2. **Payload Size Impact on Broadcast**
@@ -26,9 +27,9 @@ The benchmarking results include the following workloads:
## Results
### Realtime broadcast performance
### Broadcast: Using WebSockets
This workload evaluates the system's capacity to handle multiple concurrent WebSocket connections and message broadcasting. Each virtual user (VU) in the test:
This workload evaluates the system's capacity to handle multiple concurrent WebSocket connections and sending Broadcast messages via the WebSocket. Each virtual user (VU) in the test:
- Establishes and maintains a WebSocket connection
- Joins two distinct channels:
@@ -38,20 +39,45 @@ This workload evaluates the system's capacity to handle multiple concurrent WebS
![Broadcast Performance](/docs/img/guides/realtime/broadcast-performance.png)
| Metric | Value |
| ------------------- | ------------------------- |
| Concurrent Users | 32_000 |
| Total Channel Joins | 64_000 |
| Message Throughput | 224_000 msgs/sec |
| Median Latency | 6 ms |
| Latency (p95) | 28 ms |
| Latency (p99) | 213 ms |
| Data Received | 47.2 MB/s (29.6 GB total) |
| Data Sent | 15.2 MB/s (9.6 GB total) |
| New Connection Rate | 320 conn/sec |
| Channel Join Rate | 640 joins/sec |
| Metric | Value |
| ------------------- | ----------------------- |
| Concurrent Users | 32_000 |
| Total Channel Joins | 64_000 |
| Message Throughput | 224_000 msgs/sec |
| Median Latency | 6 ms |
| Latency (p95) | 28 ms |
| Latency (p99) | 213 ms |
| Data Received | 6.4 MB/s (7.9 GB total) |
| Data Sent | 23 KB/s (28 MB total) |
| New Connection Rate | 320 conn/sec |
| Channel Join Rate | 640 joins/sec |
### Payload size impact
### Broadcast: Using the database
This workload evaluates the system's capacity to send Broadcast messages from the database using the `realtime.broadcast_changes` function. Each virtual user (VU) in the test:
- Establishes and maintains a WebSocket connection
- Joins a distinct channel:
- A single channel (100 users per channel) for group communication
- Database has a trigger set to run `realtime.broadcast_changes` on every insert
- Database triggers 10_000 inserts per second
![Broadcast from Database Performance](/docs/img/guides/realtime/broadcast-from-database-performance.png)
| Metric | Value |
| ------------------- | ---------------------- |
| Concurrent Users | 80_000 |
| Total Channel Joins | 160_000 |
| Message Throughput | 10_000 msgs/sec |
| Median Latency | 46 ms |
| Latency (p95) | 132 ms |
| Latency (p99) | 159 ms |
| Data Received | 1.7 MB/s (42 GB total) |
| Data Sent | 0.4 MB/s (4 GB total) |
| New Connection Rate | 2000 conn/sec |
| Channel Join Rate | 4000 joins/sec |
### Broadcast: Impact of payload size
This workload tests the system's performance with different message payload sizes to understand how data volume affects throughput and latency. Each virtual user (VU) follows the same connection pattern as the broadcast test, but with varying message sizes:
@@ -86,15 +112,14 @@ This workload tests the system's performance with different message payload size
> Note: The final column shows results with reduced load (2,000 users) for the 50KB payload test, demonstrating how the system performs with larger payloads under different concurrency levels.
### Large-Scale broadcasting
### Broadcast: Scalability scenarios
This workload demonstrates Realtime's capability to handle high-scale scenarios with a large number of concurrent users and broadcast channels. The test simulates a scenario where each user participates in group communications with periodic message broadcasts:
This workload demonstrates Realtime's capability to handle high-scale scenarios with a large number of concurrent users and broadcast channels. The test simulates a scenario where each user participates in group communications with periodic message broadcasts. Each virtual user (VU):
- Each virtual user (VU):
- Establishes and maintains a WebSocket connection (30-120 minutes)
- Joins 2 broadcast channels
- Sends 1 message per minute to each joined channel
- Each message is broadcast to 100 other users
- Establishes and maintains a WebSocket connection (30-120 minutes)
- Joins 2 broadcast channels
- Sends 1 message per minute to each joined channel
- Each message is broadcast to 100 other users
![Large Broadcast Performance](/docs/img/guides/realtime/broadcast-large.png)
@@ -112,13 +137,12 @@ This workload demonstrates Realtime's capability to handle high-scale scenarios
### Realtime Auth
This workload demonstrates Realtime's capability to handle large amounts of new connections per second and channel joins per second with Authentication Row Level Security (RLS) enabled for these channels. The test simulates a scenario where large volumes of users connect to realtime and participate in auth protected communications:
This workload demonstrates Realtime's capability to handle large amounts of new connections per second and channel joins per second with Authentication Row Level Security (RLS) enabled for these channels. The test simulates a scenario where large volumes of users connect to realtime and participate in auth protected communications. Each virtual user (VU):
- Each virtual user (VU):
- Establishes and maintains a WebSocket connection (2.5 minutes)
- Joins 2 broadcast channels
- Sends 1 message per minute to each joined channel
- Each message is broadcast to 100 other users
- Establishes and maintains a WebSocket connection (2.5 minutes)
- Joins 2 broadcast channels
- Sends 1 message per minute to each joined channel
- Each message is broadcast to 100 other users
![Broadcast Auth Performance](/docs/img/guides/realtime/broadcast-auth.png)
@@ -134,7 +158,7 @@ This workload demonstrates Realtime's capability to handle large amounts of new
| Latency (p95) | 49 ms |
| Latency (p99) | 96 ms |
### Realtime's Postgres Changes
### Postgres Changes
Realtime systems usually require forethought because of their scaling dynamics. For the `Postgres Changes` feature, every change event must be checked to see if the subscribed user has access. For instance, if you have 100 users subscribed to a table where you make a single insert, it will then trigger 100 "reads": one for each user.

View File

@@ -1,14 +1,14 @@
---
title: 'Broadcast'
subtitle: 'Send and receive messages using Realtime Broadcast'
description: 'Send and receive messages using Realtime Broadcast'
subtitle: 'Send low-latency messages using the client libs, REST, or your Database.'
description: 'Send low-latency messages using the client libs, REST, or your Database.'
---
Let's explore how to implement Realtime Broadcast to send messages between clients using either WebSockets, REST API or triggers from your database.
You can use Realtime Broadcast to send low-latency messages between users. Messages can be sent using the client libraries, REST APIs, or directly from your database.
## Usage
## Subscribe to messages
You can use the Supabase client libraries to send and receive Broadcast messages.
You can use the Supabase client libraries to receive Broadcast messages.
### Initialize the client
@@ -86,9 +86,9 @@ Go to your Supabase project's [API Settings](https://supabase.com/dashboard/proj
</TabPanel>
</Tabs>
### Listening to broadcast messages
### Receiving Broadcast messages
You can provide a callback for the `broadcast` channel to receive message. This example will receive any `broadcast` messages in `room-1`:
You can provide a callback for the `broadcast` channel to receive message. This example will receive any `broadcast` messages that are sent to `test-channel`:
<Tabs
scrollable
@@ -102,7 +102,7 @@ You can provide a callback for the `broadcast` channel to receive message. This
{/* prettier-ignore */}
```js
// Join a room/topic. Can be anything except for 'realtime'.
const channelA = supabase.channel('room-1')
const myChannel = supabase.channel('test-channel')
// Simple function to log any messages we receive
function messageReceived(payload) {
@@ -110,10 +110,10 @@ You can provide a callback for the `broadcast` channel to receive message. This
}
// Subscribe to the Channel
channelA
myChannel
.on(
'broadcast',
{ event: 'test' },
{ event: 'shout' }, // Listen for "shout". Can be "*" to listen to all events
(payload) => messageReceived(payload)
)
.subscribe()
@@ -122,8 +122,9 @@ You can provide a callback for the `broadcast` channel to receive message. This
</TabPanel>
<TabPanel id="dart" label="Dart">
{/* prettier-ignore */}
```dart
final channelA = supabase.channel('room-1');
final myChannel = supabase.channel('test-channel');
// Simple function to log any messages we receive
void messageReceived(payload) {
@@ -131,9 +132,11 @@ You can provide a callback for the `broadcast` channel to receive message. This
}
// Subscribe to the Channel
channelA
myChannel
.onBroadcast(
event: 'test', callback: (payload) => messageReceived(payload))
event: 'shout', // Listen for "shout". Can be "*" to listen to all events
callback: (payload) => messageReceived(payload)
)
.subscribe();
```
@@ -141,12 +144,12 @@ You can provide a callback for the `broadcast` channel to receive message. This
<TabPanel id="swift" label="Swift">
```swift
let channelA = await supabase.channel("room-1")
let myChannel = await supabase.channel("test-channel")
// Listen for broadcast messages
let broadcastStream = await channelA.broadcast(event: "test")
let broadcastStream = await myChannel.broadcast(event: "shout") // Listen for "shout". Can be "*" to listen to all events
await channelA.subscribe()
await myChannel.subscribe()
for await event in broadcastStream {
print(event)
@@ -156,42 +159,45 @@ You can provide a callback for the `broadcast` channel to receive message. This
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
{/* prettier-ignore */}
```kotlin
val channelA = supabase.channel("room-1")
val myChannel = supabase.channel("test-channel")
//Listen for broadcast messages
val broadcastFlow: Flow<JsonObject> = channelA.broadcastFlow<JsonObject>("test")
.onEach {
println(it)
}
.launchIn(yourCoroutineScope) //you can also use .collect { } here
/ Listen for broadcast messages
val broadcastFlow: Flow<JsonObject> = myChannel
.broadcastFlow<JsonObject>("shout") // Listen for "shout". Can be "*" to listen to all events
.onEach { println(it) }
.launchIn(yourCoroutineScope) // you can also use .collect { } here
channelA.subscribe()
myChannel.subscribe()
```
</TabPanel>
<TabPanel id="python" label="Python">
{/* prettier-ignore */}
```python
# Join a room/topic. Can be anything except for 'realtime'.
channel_a = supabase.channel('room-1')
my_channel = supabase.channel('test-channel')
# Simple function to log any messages we receive
def message_received(payload):
print(f"Broadcast received: {payload}")
# Subscribe to the Channel
channel_a
.on_broadcast(
'test',
message_received
)
my_channel
.on_broadcast('shout', message_received) # Listen for "shout". Can be "*" to listen to all events
.subscribe()
```
</TabPanel>
</Tabs>
### Sending broadcast messages
## Send messages
### Broadcast using the client libraries
You can use the Supabase client libraries to send Broadcast messages.
<Tabs
scrollable
@@ -202,76 +208,62 @@ You can provide a callback for the `broadcast` channel to receive message. This
>
<TabPanel id="js" label="JavaScript">
We can send Broadcast messages using `channelB.send()`. Let's set up another client to send messages.
{/* prettier-ignore */}
```js
// Join a room/topic. Can be anything except for 'realtime'.
const channelB = supabase.channel('room-1')
const myChannel = supabase.channel('test-channel')
channelB.subscribe((status) => {
// Wait for successful connection
/**
* Sending a message before subscribing will use HTTP
*/
myChannel
.send({
type: 'broadcast',
event: 'shout',
payload: { message: 'Hi' },
})
.then((resp) => console.log(resp))
/**
* Sending a message after subscribing will use Websockets
*/
myChannel.subscribe((status) => {
if (status !== 'SUBSCRIBED') {
return null
}
// Send a message once the client is subscribed
channelB.send({
myChannel.send({
type: 'broadcast',
event: 'test',
payload: { message: 'hello, world' },
event: 'shout',
payload: { message: 'Hi' },
})
})
```
</TabPanel>
<TabPanel id="js-async" label="Async JavaScript">
We can send Broadcast messages using `channelB.send()`. Let's set up another client to send messages.
We can wrap the subscription in a promise to use async/await.
{/* prettier-ignore */}
```js
// Join a room/topic. Can be anything except for 'realtime'.
const channelB = supabase.channel('room-1')
const channelSubscribed = await new Promise((resolve, reject) => {
channelB.subscribe((status) => {
if (status === 'SUBSCRIBED') {
resolve(channel);
} else {
reject("Could not subscribe to channel");
}
})
});
channelSubscribed.send({
type: 'broadcast',
event: 'test',
payload: { message: 'hello, world' },
});
```
</TabPanel>
<TabPanel id="dart" label="Dart">
We can send Broadcast messages using `channelB.sendBroadcastMessage()`. Let's set up another client to send messages.
{/* prettier-ignore */}
```dart
// Join a room/topic. Can be anything except for 'realtime'.
final channelB = supabase.channel('room-1');
final myChannel = supabase.channel('test-channel');
channelB.subscribe((status, error) {
// Wait for successful connection
// Sending a message before subscribing will use HTTP
final res = await myChannel.sendBroadcastMessage(
event: "shout",
payload: { 'message': 'Hi' },
);
print(res);
// Sending a message after subscribing will use Websockets
myChannel.subscribe((status, error) {
if (status != RealtimeSubscribeStatus.subscribed) {
return;
}
// Send a message once the client is subscribed
channelB.sendBroadcastMessage(
event: 'test',
payload: {'message': 'hello, world'},
myChannel.sendBroadcastMessage(
event: 'shout',
payload: { 'message': 'hello, world' },
);
});
```
@@ -279,61 +271,146 @@ You can provide a callback for the `broadcast` channel to receive message. This
</TabPanel>
<TabPanel id="swift" label="Swift">
We can send Broadcast messages using `channelB.broadcast()`. Let's set up another client to send messages.
{/* prettier-ignore */}
```swift
let channelB = await supabase.channel("room-1")
let myChannel = await supabase.channel("test-channel") {
$0.broadcast.acknowledgeBroadcasts = true
}
await channelB.subscribe()
// Sending a message before subscribing will use HTTP
await myChannel.broadcast(event: "shout", message: ["message": "HI"])
try await channelB.broadcast(
event: "test",
// Sending a message after subscribing will use Websockets
await myChannel.subscribe()
try await myChannel.broadcast(
event: "shout",
message: YourMessage(message: "hello, world!")
)
```
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
We can send Broadcast messages using `channelB.broadcast()`. Let's set up another client to send messages.
```kotlin
val channelB = supabase.channel("room-1")
val myChannel = supabase.channel("test-channel") {
broadcast {
acknowledgeBroadcasts = true
}
}
channelB.subscribe(blockUntilSubscribed = true) //You can also use the channelA.status flow instead, but this parameter will block the coroutine until the status is joined.
// Sending a message before subscribing will use HTTP
myChannel.broadcast(event = "shout", buildJsonObject {
put("message", "Hi")
})
// Sending a message after subscribing will use Websockets
myChannel.subscribe(blockUntilSubscribed = true)
channelB.broadcast(
event = "test",
payload = YourMessage(message = "hello, world!")
event = "shout",
payload = YourMessage(message = "hello, world!")
)
```
</TabPanel>
<TabPanel id="python" label="Python">
We can send Broadcast messages using `channel_b.send_broadcast()`. Let's set up another client to send messages.
{/* prettier-ignore */}
```python
# Join a room/topic. Can be anything except for 'realtime'.
channel_b = supabase.channel('room-1')
my_channel = supabase.channel('test-channel')
# Sending a message after subscribing will use Websockets
def on_subscribe(status, err):
if status != RealtimeSubscribeStates.SUBSCRIBED:
return
# Send a message once the client is subscribed
channel_b.send_broadcast(
'test',
my_channel.send_broadcast(
'shout',
{ "message": 'hello, world' },
)
channel_b.subscribe(on_subscribe)
my_channel.subscribe(on_subscribe)
```
</TabPanel>
</Tabs>
{/* supa-mdx-lint-disable-next-line Rule001HeadingCase */}
### Broadcast from the Database
Before sending messages we need to ensure the client is connected, which we have done within the `subscribe()` callback.
<Admonition type="caution">
This feature is in Public Alpha. [Submit a support ticket](https://supabase.help) if you have any issues.
</Admonition>
You can send messages directly from your database using the `realtime.send()` function:
{/* prettier-ignore */}
```sql
select
realtime.send(
jsonb_build_object('hello', 'world'), -- JSONB Payload
'event', -- Event name
'topic', -- Topic
false -- Public / Private flag
);
```
It's a common use case to broadcast messages when a record is created, updated, or deleted. We provide a helper function specific to this use case, `realtime.broadcast_changes()`. For more details, check out the [Subscribing to Database Changes](/docs/guides/realtime/subscribing-to-database-changes) guide.
### Broadcast using the REST API
You can send a Broadcast message by making an HTTP request to Realtime servers.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="curl"
queryGroup="http"
>
<TabPanel id="curl" label="cURL">
{/* prettier-ignore */}
```bash
curl -v \
-H 'apikey: <SUPABASE_TOKEN>' \
-H 'Content-Type: application/json' \
--data-raw '{
"messages": [
{
"topic": "test",
"event": "event",
"payload": { "test": "test" }
}
]
}' \
'https://<PROJECT_REF>.supabase.co/realtime/v1/api/broadcast'
```
</TabPanel>
<TabPanel id="POST" label="POST">
{/* prettier-ignore */}
```bash
POST /realtime/v1/api/broadcast HTTP/1.1
Host: {PROJECT_REF}.supabase.co
Content-Type: application/json
apikey: {SUPABASE_TOKEN}
{
"messages": [
{
"topic": "test",
"event": "event",
"payload": {
"test": "test"
}
}
]
}
```
</TabPanel>
</Tabs>
## Broadcast options
@@ -495,7 +572,7 @@ You can pass configuration options while initializing the Supabase Client.
>
<TabPanel id="js" label="JavaScript">
You can confirm that Realtime received your message by setting Broadcast's `ack` config to `true`.
You can confirm that the Realtime servers have received your message by setting Broadcast's `ack` config to `true`.
{/* prettier-ignore */}
```js
@@ -580,204 +657,3 @@ You can pass configuration options while initializing the Supabase Client.
</Tabs>
Use this to guarantee that the server has received the message before resolving `channelD.send`'s promise. If the `ack` config is not set to `true` when creating the channel, the promise returned by `channelD.send` will resolve immediately.
### Send messages using REST calls
You can also send a Broadcast message by making an HTTP request to Realtime servers. This is useful when you want to send messages from your server or client without having to first establish a WebSocket connection.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="js"
queryGroup="language"
>
<TabPanel id="js" label="JavaScript">
<Admonition type="note">
This is currently available only in the Supabase JavaScript client version 2.37.0 and later.
</Admonition>
```js
const channel = supabase.channel('test-channel')
// No need to subscribe to channel
channel
.send({
type: 'broadcast',
event: 'test',
payload: { message: 'Hi' },
})
.then((resp) => console.log(resp))
// Remember to clean up the channel
supabase.removeChannel(channel)
```
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
// No need to subscribe to channel
final channel = supabase.channel('test-channel');
final res = await channel.sendBroadcastMessage(
event: "test",
payload: {
'message': 'Hi',
},
);
print(res);
```
</TabPanel>
<TabPanel id="swift" label="Swift">
```swift
let myChannel = await supabase.channel("room-2") {
$0.broadcast.acknowledgeBroadcasts = true
}
// No need to subscribe to channel
await myChannel.broadcast(event: "test", message: ["message": "HI"])
```
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
```kotlin
val myChannel = supabase.channel("room-2") {
broadcast {
acknowledgeBroadcasts = true
}
}
// No need to subscribe to channel
myChannel.broadcast(event = "test", buildJsonObject {
put("message", "Hi")
})
```
</TabPanel>
<TabPanel id="python" label="Python">
Unsupported in Python yet.
</TabPanel>
</Tabs>
## Trigger broadcast messages from your database
<Admonition type="caution">
This feature is currently in Public Alpha. If you have any issues [submit a support ticket](https://supabase.help).
</Admonition>
### How it works
Broadcast Changes allows you to trigger messages from your database. To achieve it Realtime is directly reading your WAL (Write Append Log) file using a publication against the `realtime.messages` table so whenever a new insert happens a message is sent to connected users.
It uses partitioned tables per day which allows the deletion your previous messages in a performant way by dropping the physical tables of this partitioned table. Tables older than 3 days old are deleted.
Broadcasting from the database works like a client-side broadcast, using WebSockets to send JSON packages. [Realtime Authorization](/docs/guides/realtime/authorization) is required and enabled by default to protect your data.
The database broadcast feature provides two functions to help you send messages:
- `realtime.send` will insert a message into realtime.messages without a specific format.
- `realtime.broadcast_changes` will insert a message with the required fields to emit database changes to clients. This helps you set up triggers on your tables to emit changes.
### Broadcasting a message from your database
The `realtime.send` function provides the most flexibility by allowing you to broadcast messages from your database without a specific format. This allows you to use database broadcast for messages that aren't necessarily tied to the shape of a Postgres row change.
```sql
select
realtime.send(
jsonb_build_object('hello', 'world'), -- JSONB Payload
'event', -- Event name
'topic', -- Topic
false -- Public / Private flag
);
```
### Broadcast record changes
#### Setup realtime authorization
Realtime Authorization is required and enabled by default. To allow your users to listen to messages from topics, create a RLS (Row Level Security) policy:
```sql
CREATE POLICY "authenticated can receive broadcasts"
ON "realtime"."messages"
FOR SELECT
TO authenticated
USING ( true );
```
See the [Realtime Authorization](/docs/guides/realtime/authorization) docs to learn how to set up more specific policies.
#### Set up trigger function
First, set up a trigger function that uses `realtime.broadcast_changes` to insert an event whenever it is triggered. The event is set up to include data on the schema, table, operation, and field changes that triggered it.
For this example use case, we want to have a topic with the name `topic:<record id>` to which we're going to broadcast events.
```sql
CREATE OR REPLACE FUNCTION public.your_table_changes() RETURNS trigger AS $$
BEGIN
PERFORM realtime.broadcast_changes(
'topic:' || NEW.id::text, -- topic
TG_OP, -- event
TG_OP, -- operation
TG_TABLE_NAME, -- table
TG_TABLE_SCHEMA, -- schema
NEW, -- new record
OLD -- old record
);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
```
Of note are the Postgres native trigger special variables used:
- `TG_OP` - the operation that triggered the function
- `TG_TABLE_NAME` - the table that caused the trigger
- `TG_TABLE_SCHEMA` - the schema of the table that caused the trigger invocation
- `NEW` - the record after the change
- `OLD` - the record before the change
You can read more about them in this [guide](https://www.postgresql.org/docs/current/plpgsql-trigger.html#PLPGSQL-DML-TRIGGER).
#### Set up trigger
Next, set up a trigger so the function runs whenever your target table has a change.
```sql
CREATE TRIGGER broadcast_changes_for_your_table_trigger
AFTER INSERT OR UPDATE OR DELETE ON public.your_table
FOR EACH ROW
EXECUTE FUNCTION your_table_changes ();
```
As you can see, it will be broadcasting all operations so our users will receive events when records are inserted, updated or deleted from `public.your_table` .
#### Listen on client side
Finally, client side will requires to be set up to listen to the topic `topic:<record id>` to receive the events.
```jsx
const gameId = 'id'
await supabase.realtime.setAuth() // Needed for Realtime Authorization
const changes = supabase
.channel(`topic:${gameId}`, {
config: { private: true },
})
.on('broadcast', { event: 'INSERT' }, (payload) => console.log(payload))
.on('broadcast', { event: 'UPDATE' }, (payload) => console.log(payload))
.on('broadcast', { event: 'DELETE' }, (payload) => console.log(payload))
.subscribe()
```

View File

@@ -1,118 +0,0 @@
---
title: 'Migrate to Broadcast Changes'
subtitle: 'How to migrate from Postgres Changes to Broadcast Changes'
description: 'How to migrate from Postgres Changes to Broadcast Changes'
sidebar_label: 'Migrate to Broadcast Changes'
---
Postgres Changes has some [limitations](/docs/guides/realtime/postgres-changes#limitations) as your application scales. To continue broadcasting database changes to users as you scale, you can use Broadcast Changes.
## Example application using Postgres Changes
Here we have a simple chess application that has a game id and we want to track whenever we have new moves happening for a given game id.
We store this information in a `public.moves` table and every time a new move is added to a given `game_id` we want to receive the changes in our connected Realtime client
<Image
alt="Schema used for our example"
src={{
light:
'/docs/img/guides/realtime/realtime-broadcast-changes-migration-schema-example-light.png',
dark: '/docs/img/guides/realtime/realtime-broadcast-changes-migration-schema-example-dark.png',
}}
/>
In our client we will have our implementation to receive insert events with the usual code:
```javascript
const gameId = '4a8bbe89-f601-4414-bd47-8d0f7ab2a31a'
const changes = supabase
.channel('chess-moves')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'moves',
filter: `game_id=eq.${gameId}`,
},
(payload) => console.log(payload)
)
.subscribe()
...
```
## Migrate to broadcast changes
To use Broadcast Changes, first familiarize yourself with the [Broadcast Changes implementation](/docs/guides/realtime/broadcast#trigger-broadcast-messages-from-your-database).
### Set up authorization
Broadcast Changes is private by default, using [Realtime Authorization](/docs/guides/realtime/authorization) to control access. First, set up RLS policies to control user access to relevant messages:
```sql
CREATE POLICY "authenticated can listen to game moves"
ON "realtime"."messages"
FOR SELECT
TO authenticated
USING (
EXISTS (
SELECT 1
FROM game_users
WHERE (SELECT auth.uid()) = user_id
AND (select realtime.topic()) = 'games:' || game_id::text
AND realtime.messages.extension = 'broadcast'
)
);
```
### Set up trigger function
We need to define our trigger function to adapt to our use case and use the provided function `realtime.broadcast_changes`
```sql
CREATE OR REPLACE FUNCTION public.broadcast_moves() RETURNS trigger AS $$
BEGIN
PERFORM realtime.broadcast_changes(
'games:' || NEW.game_id::text, -- topic
TG_OP, -- event
TG_OP, -- operation
TG_TABLE_NAME, -- table
TG_TABLE_SCHEMA, -- schema
NEW, -- new record
OLD -- old record
);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
```
### Setup trigger with created function
Now we need to setup our trigger to capture the events we want
```sql
CREATE TRIGGER chess_move_changes
AFTER INSERT ON public.moves
FOR EACH ROW
EXECUTE FUNCTION public.broadcast_moves();
```
### **Listen to changes in client**
Finally you can setup your client to listen for your events
```js
const gameId = '4a8bbe89-f601-4414-bd47-8d0f7ab2a31a'
await supabase.realtime.setAuth() // Needed for Realtime Authorization
const changes = supabase
.channel(`games:${gameId}`)
.on(
'broadcast',
{
event: 'INSERT',
},
(payload) => console.log(payload)
)
.subscribe()
```

View File

@@ -1,27 +1,101 @@
---
id: 'subscribing-to-database-changes'
title: 'Subscribing to Database Changes'
subtitle: 'Listen to database changes in real-time from your website or application.'
description: 'Listen to database changes in real-time from your website or application.'
sidebar_label: 'Videos'
---
Supabase allows you to subscribe to real-time changes on your database from your client application.
You can use Supabase to subscribe to real-time database changes. There are two options available:
You can listen to database changes using the [Postgres Changes](/docs/guides/realtime/postgres-changes) extension.
The following video shows how you can enable this feature for your tables.
1. [Broadcast](/docs/guides/realtime/broadcast). This is the recommended method for scalability and security.
1. [Postgres Changes](/docs/guides/realtime/postgres-changes). This is a simpler method. It requires less setup, but does not scale as well as Broadcast.
## Demo
## Using Broadcast
To automatically send messages when a record is created, updated, or deleted, we can attach a [Postgres trigger](/docs/guides/database/postgres/triggers) to any table. Supabase Realtime provides a `realtime.broadcast_changes()` function which we can use in conjunction with a trigger.
### Broadcast authorization
[Realtime Authorization](/docs/guides/realtime/authorization) is required for receiving Broadcast messages. This is an example of a policy that allows authenticated users to listen to messages from topics:
{/* prettier-ignore */}
```sql
create policy "Authenticated users can receive broadcasts"
on "realtime"."messages"
for select
to authenticated
using ( true );
```
### Create a trigger function
Let's create a function that we can call any time a record is created, updated, or deleted. This function will make use of some of Postgres's native [trigger variables](https://www.postgresql.org/docs/current/plpgsql-trigger.html#PLPGSQL-DML-TRIGGER). For this example, we want to have a topic with the name `topic:<record id>` to which we're going to broadcast events.
{/* prettier-ignore */}
```sql
create or replace function public.your_table_changes()
returns trigger
language plpgsql
as $$
begin
perform realtime.broadcast_changes(
'topic:' || NEW.id::text, -- topic - the topic to which we're broadcasting
TG_OP, -- event - the event that triggered the function
TG_OP, -- operation - the operation that triggered the function
TG_TABLE_NAME, -- table - the table that caused the trigger
TG_TABLE_SCHEMA, -- schema - the schema of the table that caused the trigger
NEW, -- new record - the record after the change
OLD -- old record - the record before the change
);
return null;
end;
$$;
```
### Create a trigger
Let's set up a trigger so the function is executed after any changes to the table.
{/* prettier-ignore */}
```sql
create trigger handle_your_table_changes
after insert or update or delete
on public.your_table
for each row
execute function your_table_changes ();
```
#### Listening on client side
Finally, on the client side, listen to the topic `topic:<record_id>` to receive the events. Remember to set the channel as a private channel, since `realtime.broadcast_changes` uses Realtime Authorization.
```jsx
const gameId = 'id'
await supabase.realtime.setAuth() // Needed for Realtime Authorization
const changes = supabase
.channel(`topic:${gameId}`, {
config: { private: true },
})
.on('broadcast', { event: 'INSERT' }, (payload) => console.log(payload))
.on('broadcast', { event: 'UPDATE' }, (payload) => console.log(payload))
.on('broadcast', { event: 'DELETE' }, (payload) => console.log(payload))
.subscribe()
```
## Using Postgres Changes
Postgres Changes are simple to use, but have some [limitations](/docs/guides/realtime/postgres-changes#limitations) as your application scales. We recommend using Broadcast for most use cases.
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/2rUjcmgZDwQ"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## Setup
### Enable Postgres Changes
You'll first need to create a `supabase_realtime` publication and add your tables (that you want to subscribe to) to the publication:
@@ -43,7 +117,7 @@ alter
publication supabase_realtime add table messages;
```
## Streaming inserts
### Streaming inserts
You can use the `INSERT` event to stream all new rows.
@@ -65,7 +139,7 @@ const channel = supabase
.subscribe()
```
## Streaming updates
### Streaming updates
You can use the `UPDATE` event to stream all updated rows.
@@ -86,12 +160,3 @@ const channel = supabase
)
.subscribe()
```
## More resources
- Learn more about the [Postgres Changes](/docs/guides/realtime/postgres-changes) extension.
- Client Libraries:
- [JavaScript](/docs/reference/javascript/subscribe)
- [Flutter](/docs/reference/dart/stream)
- [Python](/docs/reference/python/subscribe)
- [C#](/docs/reference/csharp/subscribe)

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB