Esta linha está enviando uma mensagem de websocket para cada coluna, com base na igualdade
Algumas colunas podem ser despejos de texto enormes ou configuração JSON, portanto, devemos adicionar alguns filtros de filtro adequados aqui.
O nome dos canais é valor no ETS, pode ser muito grande, até GBs.
Quanto a esse bug, acho que está relacionado a essa verificação:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/server/lib/realtime/subscribers_notification.ex#L140 -L142
Na implementação atual, podem ser verificadas as chaves de até 100 símbolos.
Muito obrigado @ abc3.
Devo verificar meu próprio código antes de criar problemas 😅
Vou configurar alguns testes para fazer isso falhar - @fracek confirmou que era um problema, então vale a pena descobrir onde a falha está ocorrendo.
Verifiquei com o comprimento da chave 2500, tudo ok. Tenho o prazer de investigar o bug se você mostrar como reproduzi-lo. :corar:
Eu começo com uma nova cópia do realtime (commit 894f4bb89230
), pego as dependências para o servidor ( mix deps.get
) e para o exemplo do nó js ( yarn install
). Eu inicio o banco de dados a partir do exemplo node-js ( docker-compose up db
).
Eu inicio o servidor:
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
e o exemplo node-js ( yarn run start
).
Insiro alguns dados com o seguinte script:
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()
e eu posso ver os dados transmitidos para o exemplo node-js. Eu mudo o comprimento da string para 4000 ( name = 'name' * 1000
) e funciona. Eu aumento o tamanho da string incrementalmente (10 em 10) até name = 'name' * 1000000
( 1_000_000
). Eu mudei o script para inserir primeiro uma string grande e depois uma string pequena, neste caso node-js não recebe nenhum deles.
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()
Acredito que o problema esteja nos canais não sendo capazes de lidar com grandes cargas úteis.
Um último experimento é verificar o atraso de replicação. Eu uso o seguinte comando:
select
slot_name, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS replicationSlotLag, active
from pg_replication_slots ;
Normalmente, o resultado é:
slot_name | replicationslotlag | active
-----------+--------------------+--------
test_slot | 56 bytes | t
(1 row)
Eu executo meu script Python uma vez e o vejo crescendo (toda vez que executo o script, ele cresce cerca de 40kB). @ w3b6x9 me mostrou que na instância do cliente esse tamanho é de cerca de 20 MB. Depois de um tempo, a instância em tempo real é eliminada pelo sistema operacional (com SIGKILL). Eu reinicio a instância e lentamente (muito lentamente) atravessa o backlog, mas é eliminada regularmente porque o uso de memória continua aumentando.
Para recapitular, há dois problemas visíveis ao inserir linhas grandes:
Meu palpite é que há dois problemas principais (talvez relacionados, talvez não):
Mas name = 'name' * 1000000
tem apenas 40 MB: confused:
No meu caso funciona, vou mergulhar mais fundo com o exemplo @fracek .
A propósito, meu teste mostra uma latência muito grande de ~ 10 seg. É muito para entregar algumas dezenas de 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))
}
obrigado pelo teste @ abc3 - pode ser devido a isso: https://github.com/supabase/realtime/pull/120
Estávamos vendo esse comportamento no Prod, então tivemos que reverter o PR. Se ainda estiver lento após a fusão deste PR, então definitivamente precisamos corrigir a latência
Eu cometi um erro no meu cálculo: não 40, mas 4 MB))
inserção de teste por meio deste INSERT INTO users(name) VALUES(repeat('name', 1000000))
Eu encontrei!
Tempo de resposta com nível de depuração e sem:
Essas linhas são executadas em alguns segundos:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/server/lib/realtime/replication.ex#L72 -L73
Posso reproduzir a correção. Os eventos ainda não são propagados por meio de websockets, mas pelo menos a instância não para de responder. Bom trabalho!
O navegador precisa de algum tempo para decodificar e renderizar strings longas.
Será mais claro se fizer algumas alterações no exemplo.
precisa remover isso:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/examples/next-js/pages/index.js#L28
substitua isso
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/examples/next-js/pages/index.js#L44 -L53
com
messageReceived(channel, msg) {
console.log('got message')
}
e ler mensagens no console do navegador
cur = conn.cursor ()
name = 'name' * 1000000
cur.execute ("INSERT INTO usuários (nome) VALORES (% s)", (nome,))
conn.commit ()
@fracek estranho que isso esteja causando problemas em sua máquina.
Fui verificar enquanto estava depurando. Eu inseri 100 linhas, com cada linha tendo o valor 'name' * 1000000
(4 MB), na mesma transação.
Como a replicação estava em andamento, eu verifiquei o atraso de replicação (permaneceu consistente em cerca de 5,3 MB):
e o atraso de liberação:
_Isso é semelhante ao que vi ontem ao depurar a instância db do cliente. Seu atraso de replicação aumentaria e, em seguida, permaneceria consistente em um atraso (12MB, 16 MB, 20 MB, etc.) e teria um atraso de liberação em qualquer lugar entre 3-5 minutos._
Depois de esperar um bom tempo, recebi as mensagens. Cada um continha o 'name' * 1000000
(4 MB). Também rastreei quanto tempo a replicação levou (~ 30 min.) Em comparação com o envio de 100 mensagens, cada uma carregando o valor de 4 MB (~ 90 segundos).
Encontrei alguém com o problema de Desconectado do canal Phoenix ao enviar várias mensagens grandes em um pequeno intervalo de tempo . Um usuário mencionou:
- Acho que Phoenix usa um tempo limite de 60s ou mais para receber uma mensagem de batimento cardíaco ou algo assim.
- Se levar mais tempo para enviar uma única mensagem, então Phoenix pensa que o tubo está congelado ou sobrecarregado ou algo assim, então ele o mata.
E outro usuário, sasajuric
, ofereceu a seguinte solução que funcionou para ele:
- compactar / descompactar a carga fora do serializador
- serializando / desserializando mensagens usando: erlang.term_to_binary e: erlang.binary_to_term
Por enquanto, apenas algo para se ter em mente, mas se descobrirmos que as mensagens não estão sendo enviadas devido à alta frequência e à enorme carga útil, a primeira coisa que podemos fazer é usar o serializador embutido mais recente Phoenix.Socket.V2.JSONSerializer
( estamos usando V1
) antes de fazer o que sasajuric
sugeriu.
pode ser devido a isto: # 120
Sim, se o servidor realtime
continuou reiniciando e continuando de onde parou com o slot de replicação permanente, então posso ver que está demorando cada vez mais (se alguma vez no caso de não poder voltar a funcionar devido a :undefined.handle_message/4
erros) para o cliente receber mensagens.
Isso pode ser normal, considerando que o cliente executa grandes despejos de atualizações de uma vez ( @kiwicopple , acho que você mencionou isso em algum lugar). # 120 foi mesclado e será implantado para o cliente em breve, portanto, apenas teremos que continuar monitorando.
@ abc3 : btw, seu exemplo Nextjs com gráfico de tempo de roteamento # 118 é incrível e foi muito útil quando eu estava depurando. Obrigado novamente!
Obrigado! Fico feliz que minha contribuição seja útil: blush:
Por favor, compartilhe seu resultado de monitoramento se algo der errado no prod novamente.
A velha solução rápida também adiciona tempo 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
Portanto, o código acima e a implementação Realtime.SubscribersNotification.notify_subscribers
enviarão várias cópias dos dados semelhantes.
Por exemplo, ao inserir uma linha na tabela users
o cliente com assinaturas de this.addChannel('realtime:*')
e this.addChannel('realtime:public:users')
receberá 4 mensagens.
Se uma mensagem for de 4 MB, o servidor enviará 16 MB
Comentários muito úteis
Eu encontrei!
Tempo de resposta com nível de depuração e sem:
Essas linhas são executadas em alguns segundos:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/server/lib/realtime/replication.ex#L72 -L73