Realtime: WarnSendingBroadcastMessage when broadcasting from the database
如果你从数据库使用 realtime.send 或 realtime.send_binary 进行广播,你可能会在 Postgres 日志中看到这个:
🌐 If you broadcast from the database with realtime.send or realtime.send_binary, you may see this in your Postgres logs:
1WARNING: WarnSendingBroadcastMessage: <reason>这意味着函数无法将行插入 realtime.messages,所以那条消息没有被广播。这种情况出现在一种极端情况里,即消息本来就不会被任何人收到。这份指南解释了什么时候会出现这种情况,以及什么时候警告指向真正的问题。
🌐 It means the function could not insert the row into realtime.messages, so that message was not broadcast. This shows up in an edge case where the message would not have reached anyone anyway. This guide explains when that is the case and when the warning points to a real problem.
丢失的消息为什么没有被送达 #
🌐 Why the dropped message would not have been delivered
通过复制槽实时广播 realtime.messages 表的数据库更改。只有当客户端通过 WebSocket 连接时,该槽才会启动,并且连接过程会开始流式传输。这个槽是临时的,不会回放历史数据,所以它只会传送客户端连接后插入的消息。
🌐 Realtime broadcasts database changes by streaming the realtime.messages table through a replication slot. That slot only starts once a client connects over WebSocket and the connection process begins streaming. The slot is temporary and does not replay history, so it only delivers messages inserted after a client connected.
所以如果你在没有任何人连接的情况下调用 realtime.send,即使插入成功,消息也不会到达任何人。没有连接就意味着没有监听者。丢弃它的结果也是一样的。
🌐 So if you call realtime.send while nobody is connected, the message would not reach anyone even if the insert succeeded. No connection means no listener. Dropping it is the same outcome.
这就是为什么日志行是警告而不是错误。
🌐 This is why the log line is a warning and not an error.
通常是什么引发的 #
🌐 What usually triggers it
最常见的原因是在 realtime.messages 上缺少每日分区。这个表是按天分区的,而 realtime.send 或 realtime.send_binary 本身不会创建分区。如果当天没有分区,插入就会失败,并出现类似 no partition of relation "messages" found for row 的信息,同时你会收到 WarnSendingBroadcastMessage 的警告。
🌐 The most common cause is a missing daily partition on realtime.messages. The table is partitioned by day, and realtime.send or realtime.send_binary does not create partitions itself. If no partition exists for the current day, the insert fails with a message like no partition of relation "messages" found for row, and you get the WarnSendingBroadcastMessage warning.
在以下情况下会创建分区:
🌐 Partitions are created on these occasions:
| 位置 | 运行时间 |
|---|---|
| 客户端通过 WebSocket 连接 | 第一次客户端加入项目通道时,在数据库迁移完成后 |
| 定时清理任务 | 定期运行(大约每 3~4 小时一次,具体取决于环境配置),用于删除旧消息并重新创建分区窗口。它只涵盖当前已连接的项目,或者自上次运行以来有过连接的项目 |
每一个都会创建一个滚动的分区窗口:昨天、今天和接下来的三天。
🌐 Each of these creates a rolling window of partitions: yesterday, today, and the next three days.
这些情况取决于客户的连接:连接会创建分区,也会让保洁员在下一次运行时看到项目。保洁员的覆盖不是永久的,所以一个停止成功连接的项目会从中消失。
🌐 These occasions depend on a client connecting: the connection creates the partitions, and it also makes the project visible to the janitor on its next run. Janitor coverage is not permanent, so a project that stops connecting successfully drops out of it.
不包括什么
一个从来没有 WebSocket 客户端连接过的项目没有可以插入的分区,所以在那之前从数据库广播就会产生警告。
🌐 A project that has never had a WebSocket client connect has no partition to insert into, so broadcasting from the database before that point produces the warning.
调用 realtime.send 或 realtime.send_binary 这个广播 REST 端点,以及订阅或复制操作,都不会创建分区。租户健康检查端点也不会创建分区,所以在 Supabase 控制面板打开实时(Realtime)板块对它们没有影响。
🌐 Calling realtime.send or realtime.send_binary, the broadcast REST endpoint, and subscribe or replication operations do not create partitions. The tenant health check endpoint does not create them either, so opening the Realtime section in the Supabase Dashboard has no effect on them.
如何避免它 #
🌐 How to avoid it
- 在从数据库广播之前先连接客户端。一个实时的 WebSocket 连接既可以创建分区,也可以启动接收消息的消费者。
- 如果你按计划从数据库广播,确保发送时至少有一个订阅者连接。否则消息就没有去处。
如果没有客户端能够连接 #
🌐 If no client can connect
因为这两种机制都需要客户端连接,所以如果客户端无法连接,项目就永远拿不到新的分区。警告会一直出现,看起来分区维护好像自己停止了。
🌐 Because both mechanisms require a client to connect, a project whose clients cannot connect never gets new partitions. The warning keeps firing, and it looks like partition maintenance stopped on its own.
通常的原因是身份验证。当每次连接尝试都被拒绝时,不会创建分区,旧的分区也从未被清理。最新的分区会停留在客户端最后一次连接的那一天。
🌐 The usual cause is authentication. When every connection attempt is rejected, no partition is created, and old ones are never cleaned up. The newest partition stays at the last day a client connected.
检查你的客户是否已连接 #
🌐 Check whether your clients are connecting
在把警告当成分区问题处理之前,先完成这些检查:
🌐 Work through these checks before treating the warning as a partition problem:
- 在你项目的实时日志中查找连接错误,比如 JWT 签名验证失败。
- 使用 Realtime Inspector 加入一个通道,它会使用你项目本身的密钥连接。如果 Inspector 能连接,而你的应用不能,说明你的应用发送了不同的密钥或过期的用户会话。
- 如果你更换了 JWT 密钥或者改用了非对称的 JWT 签名密钥,那么在更改前发出的会话将不再有效。现有用户需要先退出,然后重新登录才能获取用当前密钥签发的令牌。
一旦客户端连接,分区就会再次创建和维护。
🌐 Once a client connects, the partitions are created and maintained again.
当它是个真正的问题时 #
🌐 When it is a real problem
如果你在客户端连接且分区存在时反复看到这个警告,那么插入失败是由于其他原因。实际原因在日志行的 <reason> 部分(底层的 SQLERRM)。检查你的 Postgres 日志中是否有这个内容,而不要以为是缺少分区。常见的例子有约束违反或 realtime.messages 上的权限问题。
🌐 If you see this warning repeatedly while clients are connected and partitions exist, the insert is failing for another reason. The actual cause is in the <reason> part of the log line (the underlying SQLERRM). Check your Postgres logs for that text rather than assuming it is a missing partition. Common examples are a constraint violation or a permissions issue on realtime.messages.