Esta línea envía un mensaje websocket para cada columna, basado en la igualdad
Algunas columnas pueden ser grandes volcados de texto o configuraciones JSON, por lo que deberíamos agregar algunos filtros sensibles aquí.
El nombre de los canales es valioso en ETS, puede ser realmente enorme, incluso GB.
En cuanto a ese error, creo que está relacionado con esa verificación:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/server/lib/realtime/subscribers_notification.ex#L140 -L142
En la implementación actual se pueden verificar las claves solo hasta 100 símbolos.
Bien gracias @ abc3.
Debo verificar mi propio código antes de crear problemas 😅
Configuraré algunas pruebas para que esto falle: @fracek confirmó que era un problema, por lo que vale la pena averiguar dónde está ocurriendo la falla.
Lo he comprobado con la longitud de la llave 2500, todo está bien. Me complacerá investigar el error si muestra cómo reproducirlo. :rubor:
Empiezo con una copia nueva de tiempo real (confirme 894f4bb89230
), obtengo los departamentos para el servidor ( mix deps.get
) y para el ejemplo del nodo js ( yarn install
). Empiezo la base de datos desde el ejemplo de node-js ( docker-compose up db
).
Enciendo el 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
y el ejemplo de node-js ( yarn run start
).
Inserto algunos datos con el siguiente 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()
y puedo ver los datos transmitidos al ejemplo de node-js. Cambio la longitud de la cadena a 4000 ( name = 'name' * 1000
) y funciona. Aumento el tamaño de la cadena de forma incremental (10 en 10) hasta name = 'name' * 1000000
( 1_000_000
). Cambié el script para insertar primero una cadena grande y luego una cadena pequeña, en este caso node-js no recibe ninguno de ellos.
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()
Creo que el problema está en los canales que no pueden manejar grandes cargas útiles.
Un último experimento consiste en comprobar el retraso de replicación. Yo uso el siguiente 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 el resultado es:
slot_name | replicationslotlag | active
-----------+--------------------+--------
test_slot | 56 bytes | t
(1 row)
Ejecuto mi script de Python una vez y lo veo crecer (cada vez que ejecuto el script crece unos 40kB). @ w3b6x9 me mostró que en la instancia del cliente este tamaño es de alrededor de 20 MB. Después de un tiempo, el sistema operativo mata la instancia en tiempo real (con SIGKILL). Reinicio la instancia y lentamente (muy lentamente) atraviesa la acumulación, pero se elimina regularmente porque el uso de memoria sigue aumentando.
Para recapitular, hay dos problemas visibles al insertar filas grandes:
Mi conjetura es que hay dos problemas principales (tal vez relacionados, tal vez no):
Pero name = 'name' * 1000000
solo tiene 40 MB: confuso:
En mi caso, funciona, profundizaré con el ejemplo de @fracek .
Por cierto, mi prueba muestra una latencia muy grande de ~ 10 segundos. Es demasiado para entregar unas pocas decenas 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))
}
gracias por probar @ abc3 - podría deberse a esto: https://github.com/supabase/realtime/pull/120
Estábamos viendo este comportamiento en Prod, así que tuvimos que revertir el PR. Si aún es lento después de fusionar este PR, entonces definitivamente necesitamos corregir la latencia
Cometí un error en mi cálculo: no 40, sino 4 MB))
inserto de prueba a través de este INSERT INTO users(name) VALUES(repeat('name', 1000000))
¡Lo encontré!
Tiempo de respuesta con nivel de depuración y sin:
Estas filas se ejecutan en pocos segundos:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/server/lib/realtime/replication.ex#L72 -L73
Puedo reproducir la solución. Los eventos aún no se propagan a través de websockets, pero al menos la instancia no deja de responder. ¡Buen trabajo!
El navegador necesita algo de tiempo para decodificar y renderizar cadenas largas.
Será más claro si realiza algunos cambios en el ejemplo.
Necesito eliminar esto:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/examples/next-js/pages/index.js#L28
reemplazar eso
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/examples/next-js/pages/index.js#L44 -L53
con
messageReceived(channel, msg) {
console.log('got message')
}
y leer mensajes en la consola del navegador
cur = conn.cursor ()
nombre = 'nombre' * 1000000
cur.execute ("INSERT INTO usuarios (nombre) VALORES (% s)", (nombre,))
conn.commit ()
@fracek es extraño que esto le esté dando problemas en su máquina.
Fui a comprobarlo mientras depuraba. Inserté 100 filas, y cada fila tiene un valor 'name' * 1000000
(4MB), en la misma transacción.
Mientras la replicación estaba en progreso, verifiqué tanto el retraso de replicación (se mantuvo constante en aproximadamente 5.3MB):
y el retraso de color:
_Esto es similar a lo que vi ayer mientras depuraba la instancia de base de datos del cliente. Su retraso de replicación aumentaría y luego se mantendría constante en un retraso (12 MB, 16 MB, 20 MB, etc.) y tendría un retraso de descarga en cualquier lugar entre 3-5 minutos.
Después de esperar bastante, recibí los mensajes. Cada uno contenía el 'name' * 1000000
(4MB). También hice un seguimiento de cuánto tiempo tomó la replicación (~ 30 min.) En comparación con el envío de 100 mensajes cada uno con el valor de 4 MB (~ 90 segundos).
Encontré a alguien con el problema Desconectado del canal Phoenix al enviar varios mensajes grandes en un período de tiempo reducido . Un usuario mencionó:
- Creo que Phoenix usa un tiempo de espera de 60 o más para recibir un mensaje de latido del corazón.
- Si se tarda más en enviar un solo mensaje, entonces Phoenix piensa que la tubería está congelada o abrumada o algo así, por lo que la mata.
Y otro usuario, sasajuric
, ofreció la siguiente solución que funcionó para él:
- comprimir / descomprimir la carga útil fuera del serializador
- serializar / deserializar mensajes usando: erlang.term_to_binary y: erlang.binary_to_term
Por ahora, algo a tener en cuenta, pero si descubrimos que los mensajes no se envían debido a la alta frecuencia y la gran carga útil, lo primero que podemos hacer es usar el serializador integrado más nuevo Phoenix.Socket.V2.JSONSerializer
( actualmente estamos usando V1
) antes de hacer lo que sasajuric
sugirió.
podría deberse a esto: # 120
Sí, si el servidor realtime
siguió reiniciando y retomando donde lo dejó con la ranura de replicación permanente, entonces puedo ver que tarda más y más (si alguna vez en el caso de que no pueda volver a funcionar estado debido a errores :undefined.handle_message/4
) para que el cliente reciba mensajes.
Esto podría ser normal considerando que el cliente realiza grandes volcados de actualizaciones a la vez ( @kiwicopple creo que lo mencionaste en alguna parte). # 120 se ha fusionado y se implementará para el cliente en breve, por lo que tendremos que seguir monitoreando.
@ abc3 : por cierto, su ejemplo de Nextjs con el gráfico de tiempo de enrutamiento # 118 es increíble y fue muy útil cuando estaba depurando. ¡Gracias de nuevo!
¡Gracias! Me alegro de que mi contribución sea útil: rubor:
Por favor, comparta su resultado de monitoreo si algo sale mal en la producción nuevamente.
La antigua solución rápida también agrega tiempo 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
Entonces, el código anterior y la implementación Realtime.SubscribersNotification.notify_subscribers
enviarán varias copias de los datos similares.
Por ejemplo, cuando se inserta una fila en la tabla users
el cliente con suscripciones a this.addChannel('realtime:*')
y this.addChannel('realtime:public:users')
recibirá 4 mensajes.
Si un mensaje de 4 MB, el servidor enviará 16 MB
Comentario más útil
¡Lo encontré!
Tiempo de respuesta con nivel de depuración y sin:
Estas filas se ejecutan en pocos segundos:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/server/lib/realtime/replication.ex#L72 -L73