diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 01d8fb4f702..aec69939b3f 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -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', - }, ], }, { diff --git a/apps/docs/content/guides/realtime.mdx b/apps/docs/content/guides/realtime.mdx index 9d5cd5d2026..4321d5e40c3 100644 --- a/apps/docs/content/guides/realtime.mdx +++ b/apps/docs/content/guides/realtime.mdx @@ -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. - - - - -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. - - - - - - -```sql -alter - publication supabase_realtime add table todos; -``` - - - - -From the client, we can listen to any new data that is inserted into the `todos` table: - - - - -```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() -``` - - - - -```dart -import 'package:supabase_flutter/supabase_flutter.dart'; - -void main() async { - // Initialize the Flutter client - Supabase.initialize( - url: 'https://.supabase.co', - anonKey: '', - 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(); -``` - - - - -```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) -} -``` - - - - -```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() -``` - - - - - -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 diff --git a/apps/docs/content/guides/realtime/architecture.mdx b/apps/docs/content/guides/realtime/architecture.mdx index 0d8ca4a077e..29f7a9b37e6 100644 --- a/apps/docs/content/guides/realtime/architecture.mdx +++ b/apps/docs/content/guides/realtime/architecture.mdx @@ -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. diff --git a/apps/docs/content/guides/realtime/benchmarks.mdx b/apps/docs/content/guides/realtime/benchmarks.mdx index a74a06438df..a33a83f75e3 100644 --- a/apps/docs/content/guides/realtime/benchmarks.mdx +++ b/apps/docs/content/guides/realtime/benchmarks.mdx @@ -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. diff --git a/apps/docs/content/guides/realtime/broadcast.mdx b/apps/docs/content/guides/realtime/broadcast.mdx index 579a9e726ed..0d9a63110a2 100644 --- a/apps/docs/content/guides/realtime/broadcast.mdx +++ b/apps/docs/content/guides/realtime/broadcast.mdx @@ -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 -### 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`: messageReceived(payload) ) .subscribe() @@ -122,8 +122,9 @@ You can provide a callback for the `broadcast` channel to receive message. This + {/* 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 ```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 + {/* prettier-ignore */} ```kotlin - val channelA = supabase.channel("room-1") + val myChannel = supabase.channel("test-channel") - //Listen for broadcast messages - val broadcastFlow: Flow = channelA.broadcastFlow("test") - .onEach { - println(it) - } - .launchIn(yourCoroutineScope) //you can also use .collect { } here + / Listen for broadcast messages + val broadcastFlow: Flow = myChannel + .broadcastFlow("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() ``` + + {/* 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() ``` -### Sending broadcast messages +## Send messages + +### Broadcast using the client libraries + +You can use the Supabase client libraries to send Broadcast messages. - 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' }, }) }) ``` - - 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' }, - }); - ``` - - - 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 - 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!") ) ``` - - 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!") ) ``` + - 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) ``` +{/* 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. + + +This feature is in Public Alpha. [Submit a support ticket](https://supabase.help) if you have any issues. + + + +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. + + + + + {/* prettier-ignore */} + ```bash + curl -v \ + -H 'apikey: ' \ + -H 'Content-Type: application/json' \ + --data-raw '{ + "messages": [ + { + "topic": "test", + "event": "event", + "payload": { "test": "test" } + } + ] + }' \ + 'https://.supabase.co/realtime/v1/api/broadcast' + ``` + + + + + {/* 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" + } + } + ] + } + ``` + + + ## Broadcast options @@ -495,7 +572,7 @@ You can pass configuration options while initializing the Supabase Client. > - 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. 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. - - - - - - This is currently available only in the Supabase JavaScript client version 2.37.0 and later. - - - - ```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) - - ``` - - - - ```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); - ``` - - - - ```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"]) - ``` - - - - ```kotlin - val myChannel = supabase.channel("room-2") { - broadcast { - acknowledgeBroadcasts = true - } - } - - // No need to subscribe to channel - - myChannel.broadcast(event = "test", buildJsonObject { - put("message", "Hi") - }) - ``` - - - - Unsupported in Python yet. - - - -## Trigger broadcast messages from your database - - - -This feature is currently in Public Alpha. If you have any issues [submit a support ticket](https://supabase.help). - - - -### 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:` 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:` 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() -``` diff --git a/apps/docs/content/guides/realtime/migrate-from-postgres-changes.mdx b/apps/docs/content/guides/realtime/migrate-from-postgres-changes.mdx deleted file mode 100644 index 4746e48cd11..00000000000 --- a/apps/docs/content/guides/realtime/migrate-from-postgres-changes.mdx +++ /dev/null @@ -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 - -Schema used for our example - -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() -``` diff --git a/apps/docs/content/guides/realtime/subscribing-to-database-changes.mdx b/apps/docs/content/guides/realtime/subscribing-to-database-changes.mdx index 05878e66d6e..138d27cc53c 100644 --- a/apps/docs/content/guides/realtime/subscribing-to-database-changes.mdx +++ b/apps/docs/content/guides/realtime/subscribing-to-database-changes.mdx @@ -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:` 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:` 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.
-## 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) diff --git a/apps/docs/public/img/guides/realtime/broadcast-from-database-performance.png b/apps/docs/public/img/guides/realtime/broadcast-from-database-performance.png new file mode 100644 index 00000000000..c04f36c4feb Binary files /dev/null and b/apps/docs/public/img/guides/realtime/broadcast-from-database-performance.png differ