Comments 23
Сколько человеко-часов ушло на оптимизацию? Не проще было узкие места перенести на что-нибудь более подходящее, типа java
И потратить xN человеко-часов на доизучение нового стека? Сделать откровенную херню (т.к. опыта мало) и столкнуться с такими же/другими проблемами, но уже на java? И потом придёт @dmitry0141_2 и скажет, что надо было переписывать на Go/C++/что угодно другое:)
Или потратиться и найти толкового (а такой и недёшего будет стоить + время на поиск) java разработчика (а сколько их надо? 1? 2? 5?), которому понадобится ещё время вникнуть в проблему и потом потранить x? человеко-часов, чтобы написать заново то, что уже было написано до него.
Я правильно понял, что вместо того что бы написать обычное многопоточное приложение на java например у них было запущено 4000 копий браузера по сути? Скажите мне что я не прав, ну не может же быть так плохо все.
нет не браузеров. А node.js процессов. это разные вещи.
Java не очень подходит для маленьких микросервисов из за памяти.
Зачем им было городить огород, когда можно использовать лямбды… С их 5,3 миллиарда лямбды можно себе позволить :)
В нём отличная и простая для программирования асинхронность. Для IO вроде поддержки кучи одновременных соединений — самое то. Да и сам JS + v8 как минимум один из самых быстрых скриптовых языков, что позволяет и сложную логику с вычислениями на нём писать: ну будут они в 2 — 5 раз медленнее, чем нативный код на Си, но ведь это не такая большая разница в типичных задачах
Это plaid https://vc.ru/finance/101043-visa-dogovorilas-o-pokupke-agregatora-dlya-finteh-servisov-plaid-za-5-3-mlrd
Сейчас вроде как есть полноценный хром которым можно грузить сайты из того же go.
Самое удивительное, что они изначально выбрали Node.js, хотя и решили не использовать параллелизм. Это как выбрать, скажем, Typescript, но все переменные в коде объявлять как any.
У нас запущено 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 миллисекунд могут очень сильно понизить производительность всего сервера.
О 30-кратном увеличении параллелизма в Node.js