该行基于相等性为每一列发送一个websocket消息
有些列可能是巨大的文本转储或JSON配置,因此我们应在此处添加一些明智的过滤器过滤器。
通道的名称在ETS中很重要,甚至GB都可能很大。
对于该错误,我认为与该检查有关:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/server/lib/realtime/subscribers_notification.ex#L140 -L142
在当前的实现中,最多只能检查100个符号的键。
很好,谢谢@ abc3。
在创建问题之前,我应该检查自己的代码😅
我将进行一些测试以使此失败- @fracek确认这是一个问题,因此值得弄清楚发生故障的位置。
我检查了键2500的长度,一切正常。 如果您演示如何重现该错误,我很高兴会调查错误。 :脸红:
我从实时的新副本开始(提交894f4bb89230
),获取服务器( mix deps.get
)和节点js示例( yarn install
)的deps。 我从node-js示例( docker-compose up db
)启动数据库。
我启动服务器:
PORT=4000 \
HOSTNAME=localhost \
DB_USER=postgres \
DB_HOST=localhost \
DB_PASSWORD=postgres \
DB_NAME=postgres \
DB_PORT=5432 \
DB_PORT=5432 \
SLOT_NAME=TEST_SLOT \
mix phx.server
和node-js示例( yarn run start
)。
我使用以下脚本插入一些数据:
import psycopg2
import time
def main():
conn = psycopg2.connect(
host="localhost",
database="postgres",
user="postgres",
password="postgres"
)
cur = conn.cursor()
name = 'name'
cur.execute("INSERT INTO users(name) VALUES(%s)", (name, ))
conn.commit()
main()
我可以看到数据流传输到node-js示例。 我将字符串长度更改为4000( name = 'name' * 1000
),它可以工作。 我逐步增加字符串的大小(10个为10),直到name = 'name' * 1000000
( 1_000_000
)。 我将脚本更改为首先插入一个大字符串,然后再插入一个小字符串,在这种情况下,node-js不会接收到它们中的任何一个。
import psycopg2
import time
def main():
conn = psycopg2.connect(
host="localhost",
database="postgres",
user="postgres",
password="postgres"
)
cur = conn.cursor()
name = 'name' * 1000000
cur.execute("INSERT INTO users(name) VALUES(%s)", (name, ))
conn.commit()
name = 'name'
cur.execute("INSERT INTO users(name) VALUES(%s)", (name, ))
conn.commit()
main()
我认为问题在于渠道无法处理较大的有效载荷。
最后一个实验是检查复制延迟。 我使用以下命令:
select
slot_name, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS replicationSlotLag, active
from pg_replication_slots ;
通常结果是:
slot_name | replicationslotlag | active
-----------+--------------------+--------
test_slot | 56 bytes | t
(1 row)
我运行了python脚本一次,并看到它不断增长(每次运行脚本时,它都会增长大约40kB)。 @ w3b6x9向我表明,在客户实例上,此大小约为20MB。 一段时间后,实时实例被os(使用SIGKILL)杀死。 我重新启动实例,它缓慢地(非常缓慢地)经历了积压,但是由于内存使用量不断增加,它被定期杀死。
概括地说,插入大行时存在两个明显的问题:
我的有根据的猜测是,有两个主要问题(可能是相关的,也许不是):
但是name = 'name' * 1000000
只有40MB:confused:
就我而言,它可以工作,我将通过@fracek示例进行更深入的
顺便说一句,我的测试显示约10秒的巨大延迟。 交付几十MB太多了。
export default async (req, res) => {
const values = Array(1000000).fill("name").join('')
const text = `INSERT INTO stress(value) VALUES('${values}') RETURNING id`
const q = await pool.query(text)
res.json(JSON.stringify(q.rows))
}
感谢您测试@ abc3-这可能是由于以下原因: https :
我们在Prod中看到了这种行为,因此必须还原PR。 如果合并此PR后它仍然很慢,那么我们绝对需要解决延迟问题
我在计算时犯了一个错误:不是40而是4MB))
通过此INSERT INTO users(name) VALUES(repeat('name', 1000000))
测试插入
我找到了!
有调试级别且没有以下情况的响应时间:
这些行执行几秒钟:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/server/lib/realtime/replication.ex#L72 -L73
我可以重现此修复程序。 事件仍然不会通过websocket传播,但至少实例不会变得无响应。 做得好!
浏览器需要一些时间来解码和呈现长字符串。
如果在示例中进行一些更改,将更加清楚。
需要删除此:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/examples/next-js/pages/index.js#L28
和
messageReceived(channel, msg) {
console.log('got message')
}
并在浏览器控制台中阅读消息
cur = conn.cursor()
名称='名称'* 1000000
cur.execute(“插入用户(名称)值(%s)”,(名称,))
conn.commit()
@fracek很奇怪,这给您的计算机带来了问题。
我在调试时去检查了一下。 我在同一笔交易中插入了100行,每行的价值'name' * 1000000
(4MB)。
在复制过程中,我检查了两个复制延迟(保持一致,约为5.3MB):
和冲洗滞后:
_这类似于我昨天调试客户的数据库实例时看到的内容。 他们的复制滞后会上升,然后以一个滞后(12MB,16MB,20MB等)保持一致,并且在3-5分钟之间的任何时间都有刷新滞后。
等待了一段时间后,我收到了消息。 每个包含'name' * 1000000
(4MB)的值。 我还跟踪了复制花费了多长时间(〜30分钟),与之相比,发送100条消息每个都带有4MB值(〜90秒)。
在短时间内发送多条大消息时,我确实找到了问题
- 我认为Phoenix使用60左右的超时时间来接收心跳消息。
- 如果发送一条消息所需的时间比发送一条消息所需的时间长,则phoenix认为该管道已冻结或不堪重负,或因此而被杀死。
另一个用户sasajuric
提供了适用于他的以下解决方案:
- 压缩/解压缩串行器外部的有效负载
- 使用:erlang.term_to_binary和:erlang.binary_to_term对消息进行序列化/反序列化
现在,要记住一些事情,但是如果我们发现由于高频和巨大的负载而没有发送消息,那么我们要做的第一件事就是使用更新的内置串行器Phoenix.Socket.V2.JSONSerializer
(在执行建议的sasajuric
之前,我们目前正在使用V1
)。
这可能是由于:#120
是的,如果realtime
服务器继续重新启动并从永久复制插槽中停下来的地方继续取书,那么我会看到它花费的时间越来越长(如果曾经无法恢复工作,状态(由于:undefined.handle_message/4
错误)导致客户接收消息。
考虑到客户一次执行大量更新转储,这可能是正常的( @kiwicopple,我想您已在某处提到过此问题)。 #120已合并,不久将为客户部署,因此我们只需要继续监视即可。
@ abc3 :顺便说一句,您的带有路由时间图表118的Nextjs示例非常棒,在调试时非常有用。 再次感谢!
谢谢! 我很高兴我的贡献对您有所帮助:blush:
如果产品再次出现问题,请分享您的监视结果。
旧的快速解决方案也增加了时间https://github.com/supabase/realtime/issues/8#issuecomment -564551365
https://github.com/supabase/realtime/blob/af6344c7746e8a8af6a11a9b498721c1f97e339b/server/lib/realtime_web/channels/realtime_channel.ex#L21 -L24
因此,上面的代码和实现Realtime.SubscribersNotification.notify_subscribers
将发送相似数据的多个副本。
例如,当在表users
插入一行时,订阅了this.addChannel('realtime:*')
和this.addChannel('realtime:public:users')
的客户端将收到4条消息。
如果一条消息为4MB,服务器将发送16MB
最有用的评论
我找到了!
有调试级别且没有以下情况的响应时间:
这些行执行几秒钟:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/server/lib/realtime/replication.ex#L72 -L73