Всем привет, я создатель https://vatsim-radar.com/ и сегодня я чуть не умер.

В общем, дело такое. Мы - карта виртуальных самолетиков. Недавно нас пропиарили на официальном уровне, и теперь мы обслуживаем тысячи человек ежедневно - в подвале при открытии сайта будет показано, сколько там сидит прямо сейчас ("in Radar").

Ранее мы обновлялись раз в 15 секунд с реальной задержкой от игры в примерно 30 секунд. Это всё дело кэшировалось на Cloudflare, и мы прекрасно жили - пока нам в какой-то момент не подарили апдейты на событиях практически без задержек.

Я решил использовать вебсокеты и обновлять данные сразу по мере поступления. Всё шло плюс минус гладко, пока не произошел тот самый пиар - и мы стали отдавать террабайты данных в сутки. Я сократил трафик до удобоваримых 300ГБ в сутки, но это все равно слишком много - в будущем мы планируем переезд на зарубежный хостинг, а у них у большинства лимиты по трафику, из-за чего мы будем платить больше за трафик, чем за сами серверы.

Я решил, что с этим надо что-то делать, и после безуспешной попытки переноса сокетов в CloudFlare Workers + Durable Objects (все еще слишком высокий прайс), я решил сдаться и отказаться от сокетов в пользу старого доброго кэша CF, но при этом продолжить гонять сильно урезанные данные с очень небольшим кэшем.

Результат говорит сам за себя:

См. что произошло после всплеска в 15 часов
См. что произошло после всплеска в 15 часов

Теперь мы в пике потребляем ~72ГБ в сутки, и с этим уже можно работать. Но, разумеется, отказ от сокетов увеличил задержку в обновлении.

Наш скрипт обработки данных для разгрузки основного потока вынесен в отдельный через fork, и при каждом обновлении отправляет данные родителю через process.send, а тот потом их раздает по апишке.

Скрипт обрабатывал данные 3 секунды, что, с учетом кэша, создавало задержку до 9 секунд, что меня не устраивало. Я решил оптимизировать скрипт, и по итогу добился обработки раз в секунду, что уже было лучше (комментаторам просьба принять погоню за задержкой и секундами как данность).

Задержка уменьшилась в 3 раза, но...

На продуктиве вылез баг. В какой то момент обновления... просто замедлялись. При первоначальном деплое всё было нормально, после чего пользователи видели задержку в минуту... две... десять... И на сервере со страшной силой росло ОЗУ воркера.

Думаю, ну понятно. Создал по пути утечку памяти. Просмотрел весь код, присобачил к воркеру дебагер - не нахожу ровным счетом ничего. Локально всё нормально, задержек нет, после деплоя есть. Стал копать кэш CF, лезть напрямую - то же самое. Бред.

Покрыл код логами, деплойнул на next-стенд. И к моему удивлению...

Всё идеально. Работает как часы. Никаких задержек. При этом пользователи этих данных не получают, и становится хуже с каждой минутой.

В логах нет ошибок. Ни в ребенке, ни в родителе. У самого воркера память не растет. Процессы не растут. В коде ничего.

И тут до меня доходит.

Если воркер обновляется, а родитель нет, то о чем это говорит?

Что данные до родителя не поступают в принципе.

Работа process.send

Признаюсь честно, за всю свою карьеру я ни разу нормально не использовал спавн дочерних процессов ноды, не считая одноразовых. И уж тем более ни разу не общался через process.send.

Пошел гуглить - может быть, он не выполняется синхронно? Нахожу вот это

Ишью тогда исправили, и добавили функциональность, которая позволяет сделать его "асинхронным". Думаю, странно, баг то вообще не про то, ну ладно. Открываю документацию.

Ничего про синхронность. Нет ни описания того, что делает sendHandle, ни того, нужен ли в 2024 году этому методу callback.

Решил проверить работу несчастного колбека. Смотрю локально, и действительно: скрипт завершается раньше, чем срабатывает callback, но всего на 10 миллисекунд.

Решив сделать поправку на то, что в нашем облаке фиговые диски, плюс там крутится два инстанса на одной тачке, выкатил обертку с promise'ом для process.send на тот же next.

И - оп - утечки прекратились, как будто их и не было.

await new Promise<void>((resolve, reject) => {
  process.send!(JSON.stringify(radarStorage.vatsim), undefined, undefined, err => {
    if (err) return reject(err);
    resolve();
  });
});

Как я умудрился это получить? Почему этого не было раньше?

  1. Отправляю слишком большие данные в process.send

  2. Миграция в облако, где более медленный диск

  3. Ускорение скрипта с 1 до 3 секунд, что перестало давать время на выполнение предыдущего process.send

К чему эта статья?

В процессе гугления я не нашел ничего про данную проблему с process.send.

Проблему весьма сложно создать, если общаться мелкими данными, и не очень часто. Однако, если она возникла, то её крайне тяжело нагуглить и догадаться о причине из документации Node.

Эта ошибка свела меня с ума: я много поменял в коде, и был уверен, что то ли я создаю утечку памяти, то ли что-то не так с новым хостингом, то ли Cloudflare делает что-то не то. Самым странным оказалось именно время последнего обновления: оно постоянно скакало и как будто оставалось в прошлом, при этом весь сервер работал так же, как и прежде.

Извиняюсь за столь сумбурную статью про проблему всего лишь одного метода, и, надеюсь, она поможет кому-то, кто будет шерстить интернет в поисках его странной проблемы.

Благодарю за внимание =)

Комментарии (14)


  1. 19Zb84
    20.08.2024 22:02

    А почему worker_threads не используете ?
    https://nodejs.org/api/worker_threads.html

    const { MessageChannel } = require('node:worker_threads');
    const { port1, port2 } = new MessageChannel();
    port1.on('message', (message) => console.log('received', message));
    port2.postMessage({ foo: 'bar' });
    // Prints: received { foo: 'bar' } from the `port1.on('message')` listener 


    1. daniluk4000 Автор
      20.08.2024 22:02
      +1

      Мне нужно выполнять ровно один скриптик, эта штука кажется немного про другое


  1. Driver86
    20.08.2024 22:02
    +1

    >сегодня я чуть не умер

    Сначала подумал, что автор уничтожил БД на проде, с соответствующими последствиями от начальства.


    1. daniluk4000 Автор
      20.08.2024 22:02

      Это другая история :D

      Тут, возможно, излишний драматизм, но я правда в некотором отчаянии сидел, глядя на растущую память, жалобы пользователей, и очень странные, непонятные симптомы проблемы


  1. Vitalics
    20.08.2024 22:02

    Насколько я помню, у cloudflare свой рантаим для js, он отличается от годы(хотя апи такое же). Проверьте, точно ли у вас года запускается, или это косяк cloudflare


    1. daniluk4000 Автор
      20.08.2024 22:02
      +1

      Там как минимум урезанное апи, но не подошло не поэтому. Я смог завести свои сокеты, но дешевое не стало(


  1. anonymous
    20.08.2024 22:02

    НЛО прилетело и опубликовало эту надпись здесь


    1. daniluk4000 Автор
      20.08.2024 22:02

      Вы о чем? Чем хабр не лучше?

      В данный момент мы не используем внешних скриптов, вообще - не считая антибота Cloudflare


  1. zoto_ff
    20.08.2024 22:02

    Рекомендую пересесть с ноды на Bun. RPS станет больше, а потребление памяти меньше


    1. daniluk4000 Автор
      20.08.2024 22:02

      Манит попробовать уже пару месяцев...


      1. zoto_ff
        20.08.2024 22:02

        самое время - бан уже очень стабильный, спокойно держу на нём прод практически без происшествий


  1. slonopotamus
    20.08.2024 22:02

    Мы - карта виртуальных самолетиков.

    Ранее мы обновлялись раз в 15 секунд с реальной задержкой от игры в примерно 30 секунд.

    Даже прочитав всю статью два раза и сходив на ваш сайт я так и не смог понять о чём вообще всё это.


    1. daniluk4000 Автор
      20.08.2024 22:02

      Вы не поняли суть статьи или суть проекта? Эта информация просто о том, зачем нам понадобился этот воркер

      Сервис показывает самолеты в виртуальной сети полетов VATSIM. В ней реальные люди собираются и летают, их обслуживают диспетчера, которые также люди, и это всё симулирует реальные процессы, принятые в мире.

      Низкая задержка нужна в том числе для того, чтобы, если диспетчера онлайн нет, смотреть самолеты в округе и быть в курсе происходящего - но и с диспетчерами пользы много от низкой задержки.


      1. slonopotamus
        20.08.2024 22:02

        Сервис показывает самолеты в виртуальной сети полетов VATSIM. В ней реальные люди собираются и летают, их обслуживают диспетчера, которые также люди, и это всё симулирует реальные процессы, принятые в мире.

        Во, теперь понял)