Эта строка отправляет сообщение веб-сокета для каждого столбца на основе равенства
Некоторые столбцы могут быть огромными текстовыми дампами или конфигурацией JSON, поэтому мы должны добавить здесь несколько разумных фильтров.
Название каналов имеет значение в ETS, оно может быть действительно огромным, даже гигабайтами.
Что касается этой ошибки, я думаю, она связана с проверкой:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/server/lib/realtime/subscribers_notification.ex#L140 -L142
В текущей реализации можно проверять ключи до 100 символов.
Хорошее спасибо @ abc3.
Я должен проверить свой код, прежде чем создавать проблемы 😅
Я настрою несколько тестов, чтобы @fracek подтвердил, что это проблема, поэтому стоит выяснить, где происходит сбой.
Проверил с ключом длиной 2500, все ок. Я с удовольствием исследую баг, если вы покажете, как его воспроизвести. :краснеть:
Я начинаю с новой копии реального времени (commit 894f4bb89230
), получаю зависимости для сервера ( mix deps.get
) и для примера node js ( yarn install
). Я запускаю базу данных из примера 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 один раз и вижу, как он растет (каждый раз, когда я запускаю скрипт, он увеличивается примерно на 40 КБ). @ w3b6x9 показал мне, что на экземпляре клиента этот размер составляет около 20 МБ. Через некоторое время операционная система убивает экземпляр реального времени (с помощью SIGKILL). Я перезапускаю экземпляр, и он медленно (очень медленно) проходит через отставание, но его регулярно убивают, потому что использование памяти продолжает расти.
Напомним, что при вставке больших строк есть две видимые проблемы:
Мое обоснованное предположение состоит в том, что есть две основные проблемы (возможно, связанные, а может и нет):
Но name = 'name' * 1000000
всего 40 МБ: confused:
В моем случае это работает, я углублюсь в пример
Кстати, мой тест показывает очень большую задержку ~ 10 секунд. Это многовато для доставки нескольких десятков МБ.
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://github.com/supabase/realtime/pull/120
Мы наблюдали такое поведение в Prod, поэтому пришлось отменить PR. Если после слияния PR все еще медленно, то нам обязательно нужно исправить задержку.
Я ошибся в расчетах: не 40, а 4МБ))
тестовая вставка через этот INSERT INTO users(name) VALUES(repeat('name', 1000000))
Я нашел это!
Время отклика с уровнем отладки и без:
Эти строки выполняются несколько секунд:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/server/lib/realtime/replication.ex#L72 -L73
Я могу воспроизвести исправление. События по-прежнему не распространяются через веб-сокеты, но, по крайней мере, экземпляр не перестает отвечать. Отличная работа!
Браузеру требуется некоторое время, чтобы декодировать и отображать длинные строки.
Будет нагляднее, если внести в пример некоторые изменения.
нужно удалить это:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/examples/next-js/pages/index.js#L28
заменить это
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/examples/next-js/pages/index.js#L44 -L53
с участием
messageReceived(channel, msg) {
console.log('got message')
}
и читать сообщения в консоли браузера
cur = conn.cursor ()
name = 'имя' * 1000000
cur.execute ("INSERT INTO users (name) VALUES (% s)", (name,))
conn.commit ()
@fracek странно, что это
Я пошел проверить, пока отлаживал. Я вставил 100 строк, каждая из которых имеет значение 'name' * 1000000
(4 МБ) в одной транзакции.
В процессе репликации я проверил оба значения задержки репликации (оставалось неизменным около 5,3 МБ):
и задержка слива:
_Это похоже на то, что я видел вчера при отладке экземпляра клиентской базы данных. Их задержка репликации будет расти, а затем оставаться постоянной с задержкой (12 МБ, 16 МБ, 20 МБ и т. Д.) И иметь задержку сброса где-то в пределах 3-5 минут.
После долгого ожидания я получил сообщения. Каждый из них содержал значение 'name' * 1000000
(4 МБ). Я также отслеживал, сколько времени занимает репликация (~ 30 минут) по сравнению с отправкой 100 сообщений, каждое из которых имеет значение 4 МБ (~ 90 секунд).
Я нашел кого-то, у кого возникла проблема.
- Phoenix, я думаю, использует тайм-аут 60 секунд или около того, чтобы получить сообщение о биении или около того.
- Если для отправки одного сообщения требуется больше времени, тогда Phoenix думает, что труба заморожена или перегружена или что-то в этом роде, поэтому он ее убивает.
Другой пользователь, sasajuric
, предложил следующее решение, которое сработало для него:
- сжатие / распаковка полезной нагрузки вне сериализатора
- сериализация / десериализация сообщений с использованием: erlang.term_to_binary и: erlang.binary_to_term
На данный момент просто о чем следует помнить, но если мы действительно обнаружим, что сообщения не отправляются из-за высокой частоты и огромной полезной нагрузки, то первое, что мы можем сделать, это использовать новый встроенный сериализатор Phoenix.Socket.V2.JSONSerializer
( в настоящее время мы используем V1
), прежде чем делать то, что предлагает sasajuric
.
это могло быть из-за этого: # 120
Да, если realtime
сервер продолжал перезапускаться и продолжал работать с того места, где он остановился, с постоянным слотом репликации, тогда я вижу, что это занимает все больше и больше времени (если когда-либо в случае, если он не может вернуться к рабочему состояние из-за ошибок :undefined.handle_message/4
), чтобы клиент мог получать сообщения.
Это может быть нормально, учитывая, что заказчик сразу выполняет огромные дампы обновлений ( @kiwicopple, я думаю, вы где-то упоминали об этом). # 120 был объединен и вскоре будет развернут для клиента, поэтому нам просто нужно продолжить мониторинг.
@ abc3 : кстати, ваш пример Nextjs с диаграммой времени маршрутизации # 118 потрясающий и был очень полезен, когда я отлаживал. Еще раз спасибо!
Спасибо! Я рад, что мой вклад оказался полезным: 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 сообщения.
Если одно сообщение 4 МБ, сервер отправит 16 МБ
Самый полезный комментарий
Я нашел это!
Время отклика с уровнем отладки и без:
Эти строки выполняются несколько секунд:
https://github.com/supabase/realtime/blob/894f4bb8923017467c78803711d8adbef8c090fe/server/lib/realtime/replication.ex#L72 -L73