Pull to refresh

Comments 23

UFO just landed and posted this here

Сколько человеко-часов ушло на оптимизацию? Не проще было узкие места перенести на что-нибудь более подходящее, типа java

UFO just landed and posted this here

И потратить xN человеко-часов на доизучение нового стека? Сделать откровенную херню (т.к. опыта мало) и столкнуться с такими же/другими проблемами, но уже на java? И потом придёт @dmitry0141_2 и скажет, что надо было переписывать на Go/C++/что угодно другое:)
Или потратиться и найти толкового (а такой и недёшего будет стоить + время на поиск) java разработчика (а сколько их надо? 1? 2? 5?), которому понадобится ещё время вникнуть в проблему и потом потранить x? человеко-часов, чтобы написать заново то, что уже было написано до него.

Проглядел, что это перевод (свои 6 минусов за невнимательность уже получил:) )
Вопрос хотел задать автору, и цель была получить информацию, почему был выбран такой путь(в статье этот вопрос не поднимается), а не учить «что и как нужно делать», как Вы отразили у себя в ответе.
Оказалось, что используемый нами пакет Big Friendly JSON оказался совсем не дружелюбным.

Интересный вывод только по названию. Eго дружелюбность не в скорости. С того же npmjs, куда идёт ссылка:
Is it fast?
No.


BFJ дружелюбен к памяти при больших объёмах данных.

Я правильно понял, что вместо того что бы написать обычное многопоточное приложение на java например у них было запущено 4000 копий браузера по сути? Скажите мне что я не прав, ну не может же быть так плохо все.

нет не браузеров. А node.js процессов. это разные вещи.
Java не очень подходит для маленьких микросервисов из за памяти.
Зачем им было городить огород, когда можно использовать лямбды… С их 5,3 миллиарда лямбды можно себе позволить :)

Я предполагал, что node.js лучше подходит для ssr, демонстраций, простых сайтов

В нём отличная и простая для программирования асинхронность. Для IO вроде поддержки кучи одновременных соединений — самое то. Да и сам JS + v8 как минимум один из самых быстрых скриптовых языков, что позволяет и сложную логику с вычислениями на нём писать: ну будут они в 2 — 5 раз медленнее, чем нативный код на Си, но ведь это не такая большая разница в типичных задачах

А вопрос безопасности сложно решается(скриптовые инъекции)?

О каких инъекция речь? Если вы явно в коде не используете eval() на приходящие данные, то никаких инъекций не будет. Это же обычный серверный код, исполняющийся из файлов, лежащих локально на сервере. Всё остальное зависит только от логики самого кода

Очень подходит для микросервисов. А для простых сайтов больше подходит php
Если нода это браузер, то java это микроволновка. И ещё неизвестно на чём лучше решать такие задачи

Самое удивительное, что они изначально выбрали Node.js, хотя и решили не использовать параллелизм. Это как выбрать, скажем, Typescript, но все переменные в коде объявлять как any.

Честно говоря, статья оставила негативное впечатление от проекта, тем более, что он связан с банковским сектором. Но сама статья является очень полезным примером как не следует проектировать сервис на Node.js. Несколько выдержек из статьи:

У нас запущено 4000 контейнеров Node (или «воркеров»), обеспечивающих работу нашего сервиса интеграции с банками. Сервис изначально был спроектирован так, что каждый воркер был рассчитан на обработку только одного запроса за раз.
Простите, а это как? Node.js спроектирована так, что в ней нет блокирующих запросов из коробки. Пока вы не пытаетесь гигантские циклы или вычисления числа пи в главном потоке гонять — можно делать, мягко говоря, больше 1 запроса в момент времени. Если не понимать концепцию асинхронности Node.js, то лучше на ней вообще не писать.

Это напоминало последствия утечек памяти, с которыми мы уже встречались, когда процесс неожиданно завершался, выдавая похожее сообщение об ошибке. Подобное представлялось вполне ожидаемым: повышение уровня параллелизма ведёт к повышению уровня использования памяти.
Это же сколько жрал сервис изначально, что «повышение параллелизма» сразу привело к крешам. Видимо, сервис был кривой задолго до включения параллелизма.

Мы предположили, что помочь решить эту проблему может увеличение максимального размера кучи Node.js, который по умолчанию установлен в 1.7 Гб. Тогда мы начали запускать Node.js, устанавливая максимальный размер кучи равным 6 Гб
У нас течёт память, конечно же это нода виновата из-за маленького дефолтного размера кучи.

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

const base64CompressedData = await streamToString(
    bfj.streamify(data)
        .pipe(zlib.createDeflate())
        .pipe(new b64.Encoder()),
);
Мы в потоковом режиме формируем JSON, сжимаем его, кодируем в Base64 и перед отправкой сохраняем это в гигантскую строку. А вдруг это и есть причина утечки и повышенного потребления CPU? Да не, фигня какая-то. Простите, но тут JS и Node вообще не виноваты.
Ещё здесь налицо не чтение документации по AWS SDK, где чётко чёрным по белому написано, что метод upload принимает на вход stream. Это сразу решает все проблемы с производительностью и памятью в этом месте. И не пришлось бы менять эффективный по памяти bfj на другую библиотеку.

К сожалению, именно такие статьи и делают впечатление, что Node.js — это кривой и медленный инструмент для хипстоты, а приложения на нём пишут только безумцы.
А вдруг это и есть причина утечки и повышенного потребления CPU?

Не могли бы вы пояснить, в чём причина утечки и повышенного CPU в случае накопления большой строки из stream-ов? Я вижу проблему в том что они хранят большую строку в памяти за зря, но само хранение строки не является утечкой. Что предполагается тут должно течь? bfj? zlib, b64? Разве не предполагается что потоковый код не хранит никаких ссылок на уже отработанный материал и соответственно никуда не должен течь?


Про CPU я тоже не очень понял. В чём принципиальная разница между накопить информацию в одну переменную и отправить её, против отправлять шаг за шагом никуда не складируя. Не, я понимаю, преимущества первого подхода в контексте памяти. Но в чём проблема с CPU? Оно же просто append-ит строку, это должно быть достаточно быстрой операцией (без копирования всей строки целиком на каждое изменение) и не должно вызывать чудовищных просадок по CPU.


Не пытаюсь защитить их код. Просто не очень понимаю.

Что предполагается тут должно течь?
Это не утечка, в классическом её понимании, но необоснованный рост памяти. Судя по статье, этот метод логирования, где всё и происходит, выполняется намного дольше самого запроса. Получается, что при окончании запроса память не освободилась — значит это утечка в моём понимании. Если клиентский3 запрос выполняется за 1 секунду, а лог формируется и отправляется за 5, то даже при синхронной обработке в памяти будут висеть минимум 5 гигантских строк с логами. А при многопоточной вообще страшно представить что будет (хотя представить можно — в статье описано).
Разве не предполагается что потоковый код не хранит никаких ссылок на уже отработанный материал и соответственно никуда не должен течь?
Сам по себе часть с pipe нормальная, проблема в том, stream враппится в финальную строку — это и занимает память.

Но в чём проблема с CPU? Оно же просто append-ит строку, это должно быть достаточно быстрой операцией (без копирования всей строки целиком на каждое изменение) и не должно вызывать чудовищных просадок по CPU.
А вы забываете, что помимо относительно бесплатной операции конкатенации там ещё и происходят такие тяжёлые вещи как сериализация-сжатие-енкодирование. Сериализация и енкодинг в base64 выполняются в главном потоке, блокируя event loop (потоковое сжатие выполняется в thread pool со своими подводными камнями). Если послать stream напрямую в SDK, который сам запайпит stream в исходящий http stream, тогда сработает механизм backpressure.

А если вы будете сохранять всё в промежуточную переменную — у вас нет никаких ограничений кроме производительности процессора и памяти. В итоге получится, что в момент генерации логов ваш процессор будет молотить на максималке, ограниченный только скоростью алгоритмов сериализации-сжатия-енкодинга и конкатенации строки (и со строками есть отдельные подводные камни). А для Node это губительно блокировкой event loop — даже лишние 10 миллисекунд могут очень сильно понизить производительность всего сервера.

Спасибо, теперь я понял вашу мысль. Пойду читать этот гайд по backpressure

Вот точно такое же говно с сериализацией в XML > однопоточный zlib -> base64 сделала Adobe в Premiere несколько лет назад, из-за которого огромные бинарные данные типа сотни мегабайт данных Warp Stabilizer очень долго записываются при сохранении проекта, независимо от количества ядер в системе. Перешли называется с бинарного формата хранения проекта на чёртов XML.
Sign up to leave a comment.