CTO, Release manager, Teamlead
0,0
рейтинг
4 сентября 2011 в 17:43

Разработка → Node: Масштабирование в малом против масштабирования в целом перевод

Последние несколько недель я использую все свободное время, которое могу найти, чтобы обдумать, какие технологии мы будем использовать, чтобы реализовать первую версию BankSimple. Многие люди, вероятно, предположат, что я сразу же предпочёл Scala, в связи с тем, что я был соавтором книги об этом языке, но я подхожу к решению инженерных задач совершенно иначе. Каждая проблема имеет соответствующий набор применяемых технологий, и задача разработчика, — обосновать необходимость их использования.

(Кстати, Scala, может быть, хорошо подходит для BankSimple, в немалой степени из-за большого количества стороннего кода на Java, с которым мы должны интегрироваться, но это уже совсем другая тема для блога, и даже, скорее всего, для совершенно другого блога).

Одной из самых обсуждаемых технологий среди Hacker News является Node, окружение для разработки и запуска событийно-управляемых приложений JavaScript на виртуальной машине V8. В рамках выбора технологий для реализации проекта я выполнил оценку Node. Вчера я выразил некоторый общий скепсис относительно Node, и автор этого окружения, Райан Дал, попросил, чтобы я изложил свои мысли более подробно. Так вот, приступаю.

Я, конечно, не имею цели дискредитировать Райана, хорошего парня и великолепного программиста, который знает больше о низкоуровневом С, чем большинство из нас когда-либо смогут, и без понтов (в оригинале, — без neckbeard). И я не обсуждаю здесь сообщество энтузиастов, который быстро выросло вокруг Node; если вы нашли инструмент, с которым Вы любите работать, и стремитесь к росту вместе с ним, то это придаёт вам больше силы.

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

Для чего создан Node?


Раздел «О проекте» домашней страницы Node гласит:
«Цель Node, — предоставить простой путь к построению масштабируемых сетевых приложений».

Несколькими параграфами ниже заявлено:

«Так как ничего не блокируется, даже не эксперты в области программирования способны создавать быстрые системы [с Node]».

Итак, цель Node, — обеспечить простой способ создавать масштабируемые сетевые программы, или чтобы программистам, не являющимися экспертами, разрабатывать «быстрые системы»?

Хотя эти цели могут казаться связанными между собой, они очень отличаются на практике. Для того, чтобы лучше понять, почему, мы должны провести различие между тем, что я называю «масштабирование в малом» и «масштабирование в целом».

Масштабирование в малом


В системе незначительного масштаба, в целом, все работает.

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

Это замечательно, на самом деле. Это означает, что мы можем бездумно прототипировать, с использованием любых технологий, которые нам нравятся, и эти прототипы часто будут работать лучше, чем мы ожидали. А еще лучше то, что когда мы попадаем в пробку, объехать её тривиально. Движение вперед просто означает, что надо потратить несколько минут, обдумывая вашу проблему, и выбрать технологии реализации с несколько более высокими характеристиками производительности, чем те, что вы использовали раньше.

Здесь, я считаю, Node подходит идеально.

Если вы посмотрите на людей, которые используют Node, это в значительной степени веб-разработчики, которые работают в динамических языках с тем, что мы могли бы вежливо называть ограниченными характеристиками производительности. Добавление Node к их архитектурам означает, что эти разработчики пришли оттого, что у них не было параллелизма и была очень ограниченная производительность приложений во время выполнения, чтобы перейти к относительно неплохому параллелизму — жестко навязываемым окружением Node, работающему на виртуальной машине со сравнительно хорошей производительностью. Эти разработчики удалили болезненную часть своего приложения, которая больше подходила для асинхронной реализации, переписали её с помощью Node, и двигаются дальше.

Это замечательно. Такой результат определенно соответствует заявленной вторичной цели Node, — «менее, чем программист-эксперт» „в состоянии разработать быструю систему“. Тем не менее, он имеет очень мало общего с масштабированием в целом, в более широко понимании этого термина.

Масштабирование в целом


В системе значительных масштабов у нас нет волшебной пули.

Если ваша система сталкивается с водопадом работы, которую нужно выполнить, ни одна из технологий не способна сделать все это лучше. Когда вы работаете в большом масштабе, вы двигаетесь по острию бритвы, формируя согласованный танец хорошо применяемых технологий, методов разработки, статистического анализа, внутриорганизационных коммуникаций, разумного инженерного менеджмента, быстрой и надежной работы оборудования и программного обеспечения, бдительного мониторинга, и так далее. Масштабирование трудно. Так трудно, что, фактически, возможность масштабирования является глубоким конкурентным преимуществом из тех, которое вы не можете просто загружать, копировать, купить или украсть, просто выйдя на улицу.

В этом и заключается моя критика основной заявленной цели Node: «обеспечить простой способ создания масштабируемых сетевых программ». Я принципиально не верю, что есть простой способ создания масштабируемого чего-угодно. Люди путают легкие проблемы с простыми решениями.

Если у вас есть проблема, которую легко и удобно было решить путем перемещения кода из одного части чрезвычайно ограничивающей технологии на острие чуть менее ограниченной технологии, считайте что вам повезло, но это не значит, вы работаете в масштабе. Твиттер одержал легкую победу, когда часть сервиса, например, самописная очередь сообщений на Ruby, была переписана на Scala. Это было здорово, но это было масштабирование в малом. Твиттер все еще находится в тяжелой борьбе за масштабирование в целом, так как это означает гораздо, гораздо больше, чем выбор какой-либо технологии.

Рост Node


Что касается меня, я думаю, что Node будет тяжело расти вместе с разработчиками в процессе перехода от масштабирования в малом к масштабированию в целом (нет, я не аргументирую это тем, что „обратные вызовы превратятся в кучу спагетти-кода“, хотя, я думаю, вы слышите об этом снова и снова, потому что это фактически болезненная точка разработчиков асинхронных систем).

Смелым решением в архитектуре Node является то, что все операции асинхронны, вплоть до файлового ввода/вывода, и я восхищаюсь приверженностью Райана к последовательности и ясности в реализации данного тезиса в своем программном обеспечении. Инженеры, которые глубоко понимают нагрузку их систем, могут найти места, где модель Node хорошо подойдёт, и может быть хорошей и эффективной в течение неопределенного времени; этого мы не знаем, так как пока не наблюдаем долгосрочных и зрелых развертываний Node. Большинство систем, с которыми я работал, всё время меняются. Меняется рабочая нагрузка. Данные, с которыми вы работаете, меняются вместе с системой. То, что раньше хорошо подходило под асинхронное решение, вдруг стало лучше обслуживаться многопоточным решением, или наоборот, или вы столкнулись с некоторыми другими, непредсказуемыми, полными изменениями.

Если вы глубоко погрузились в Node, вы застряли на одном из путей достижения параллелизма, на одном из способов моделирования ваших проблем и решений. Если решение не вписывается в основу событийной модели, вы попали. С другой стороны, если вы работаете с системой, которая позволяет реализовать несколько различных подходов параллелизации (JVM, CLR, C, C++, GHC и т.д.), у вас есть возможность изменить свою модель параллелизма по мере того, как ваша система эволюционирует.

На данный момент основная предпосылка Node, — что события обязательно означают высокую производительность — до сих пор под вопросом. Исследователи из Калифорнийского университета в Беркли обнаружили, что „потоки исполнения могут обладать сильными сторонами событийной модели, включая поддержку высокого параллелизма, низкие накладные расходы, а также простую модель параллелизма“. Позднее исследование, основанное на предыдущей работе, показывает, что события и подход с моделью конвейера одинаково хороши, и что блокирующие сокеты могут реально увеличить свою производительность. В индустриальном мире Java периодически речь идет о том, что неблокирующий ввод/вывод не обязательно лучше подходит по сравнению с потоками исполнения. Даже один из самых цитируемых документов по этому вопросу с вопиющим заголовком «Почему потоки, — плохая идея» заканчивается выводом, что вы не должны отказываться от потоков для высокопроизводительных серверов. Там как раз указано, что не бывает решения, которое одинаково подходит всем в плане параллелизма.

На самом деле, принятие гибридного подхода к параллелизма, кажется, является движением вперед, если нет каких-либо противопоказаний. Ученые в области компьютерных наук из университета штата Пенсильвания обнаружили, что сочетание потоков и событий предлагает лучшее из обоих миров. Команда Scala в EPFL утверждает, что Actors объединяют программирование на основе потоков выполнения и программирование на основе событий в одну аккуратную, простую для понимания, абстракцию. Russ Cox, бывший сотрудник Bell Labs, теперь занятый проектом языка программирования Go в Google, заходит ещё дальше, утверждая, что бессмысленна сама дискуссия „потоки против событий“ (обратите внимание, что все это даже не затрагивает аспект распределения масштабирования системы; потоки — это конструкции для одного компьютера, и события, — конструкции для одного процессора; мы даже не говорим о распределении работы между машинами в простой манере; кстати, это включено в Erlang, и о нём стоит задуматься, если вы няньчите быстро растущую систему).

Утверждение: опытные разработчики используют смесь потоков и событий, а также альтернативные подходы, такие как Actors и, экспериментально, STM. Для них мысль, что „неблокирующий означает, что он быстр“ звучит, по крайней мере, немного глупо, это относится к мифологии масштабируемости. Ребята, которые платят большие деньги, чтобы поставить масштабируемые решения, не переписывают лихорадочно по ночам свои системы с использованием Node. Они делают то, что они всегда делали: измерение, тестирование, тестирование производительности, обдумывание, изучение научной литературы, относящейся к их проблемам. Это то, что необходимо для масштабирования в целом.

Заключение


Для моих инвестиций рабочего времени, я бы скорее основывался на системе, которая позволяет мне гибко смешивать асинхронный подход с другими способами моделирования параллелизма. Гибридная модель параллелизма не может быть столь же простой и чистой, как подход Node, но зато будет более гибкой. Хотя BankSimple находится в зачаточном состоянии, мы будем сталкиваться с радостными проблемами масштабирования в малом, и Node может быть разумным выбором для нас на этом раннем этапе. Но когда нам требуется масштабирование в целом, я бы предпочел ассортимент из различных вариантов, открытых для меня, и я бы не хотел столкнуться с перспективой большого переписывания под давлением обстоятельств.

Node является прекрасным экземпляром кода с сообществом энтузиастов, кнутом сопровождающего, и светлым будущим. В качестве „объединяющей технологии“, которая предлагает немедленное решение проблемы раннего масштабирования способом, который особенно доступен для поколения веб-разработчиков, которые в значительной степени происходят из пользователей динамических языков, это имеет смысл. Node больше, чем кажется, удовлетворяет его вторичной заявленной цели, привлекая приемлемой производительностью разработчиков с небольшим опытом, которым необходимо решить задачи, ориентированные на сети. Node для определенного типа программистов очень удобен и привносит удовольствие, и, бесспорно, с ним легко начать работать. Люди из сообщества Node находятся на хорошем этапе, изобретая колеса, инспирируемые другими известных веб-каркасами, менеджерами пакетов, библиотеками тестирования и т.д., и поэтому я не жалею их. Каждое сообщество программистов переосмысливает ранние вещи, приводя к своим нормам.

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

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

Это нелегко.
Перевод: Alex Payne
Акжан @akzhan
карма
24,0
рейтинг 0,0
CTO, Release manager, Teamlead
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

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

  • +1
    Всегда ассинхронный node можно трансформировать в синхронность на уровне потоков, ожидая завершения ассинхронного запроса и возвращая результат после этого. По скорости язык хорош, единственное что плохо в нём — работа с ядрами процессора, всё выполняется на одном. Но это, я надеюсь, однажды решит гугл во всем V8.
    • 0
      >>всё выполняется на одном. Но это, я надеюсь, однажды решит гугл во всем V8.
      Так же задумано by design.

      Проблема решается например воркерами
      • +1
        Сейчас гугл будет толкать свою хром-ос. Вместе с ней будут продвигаться игры и прочее на чистом хтмл5. И тут будет грешно не давать использовать весь потенциал процессора в пассивном режиме.
        • +1
          Использовать хоть 2, хоть 10 ядер процессора можно уже сейчас. Воркеры есть. Если сможете расспаралелить своё приложение.
          Но от однопоточной модели Javascript все равно никуда не денешься.
          • 0
            Однопоточная модель не является ограничением JavaScript, а скорее является ограничением его реализации в виде V8. Большая часть кода V8 не является потоко-безопасной.

            Хотя были форки с реализацией многопоточности, но они полудохлые, так как пока не появилось особой необходимости в них, в отличие от волокон.
            • +3
              Язык JavaScript (ECMAScript) не содержит примитивов синхронизации и не описывает семантику паралельного исполнения. Сравните, например, с Java — в Java Language Specification целая нетривиальная глава посвящена потокам и их семантике: java.sun.com/docs/books/jls/third_edition/html/memory.html

              Единственное, что безопасно можно уложить в JavaScript — это share nothing многозадачность, которая в рамках V8 API легко реализуется через изоляты.
              • 0
                Лет двадцать, если не больше, спецификация языка C++ не содержала примитивов синхронизации и не описала семантику параллельного выполнения. Вы бы ещё с C# сравнили.
                • 0
                  Что не мешало создавать многопоточные приложения. Вообще, введение примитивов многопоточности, — это весьма недавний тренд.
                  • +2
                    в Algol 68 были par и sem ;-)

                    Я уж не говорю про специализированные языки типа occam.

                    Я не отрицаю, можно и без них, но c ними удобнее и безопаснее.
                    • 0
                      Согласен, был чересчур категоричен :) Незнаком просто с Алголом.
                • 0
                  Я бы не стал сравнивать C++ с JavaScript.

                  С++ близок к железу и стандарт явным образом оставляет многое на усмотрению разработчику компилятора. У меня не под рукой старого стандарта, но новый явным образом описывает модель исполнения как nondeterministic abstract machine.

                  Ничего подобного в ECMA-262 нет. Есть только заданные на уровне синтаксического дерева правила вычислений. Как их совместить с недетерминизмом параллельного исполнения не совсем ясно.

                  Учитывая что JavaScript подразумевает достаточно толстый runtime и нетривиальную работу с объектами, легко представить ситуацию когда две программы с потоками будут давать совершенно разные результаты на двух разных реализациях или трудно диагнастируемые баги (вспомним как канонический пример проблемы с double checked locking до появления внятной memory model в Java). Или будет страдать производительность.

                  Да и без примитивов синхронизации писать код c синхронизациями действительно будет тяжко. В Java на уровне языка есть synchronized методы и блоки + volatile, на уровне библиотеки java.util.concurrent, в C++ (если мы смотрим на C++ до C++11) есть хотя бы деструкторы стековых объектов => паттерн RAII.

                  В JS будь там многопоточность без поддержки на примитивов синхронизации на синтаксическом уровне пришлось бы делать:
                  this.mutex.lock();
                  try {
                    this.f(this.x++);
                  } finaly {
                    this.mutex.unlock();
                  }
                  


                  волосато…

                  конечно можно оформить хелпер

                  function sync(receiver, mutex, f) {
                    mutex.lock();
                    try {
                      f.call(receiver);
                    } finaly {
                      mutex.unlock();
                    }
                  }
                  
                  sync(this, this.mutex, function () {
                    this.f(this.x++);
                  });
                  


                  но все равно мало радости.
                  • 0
                    У нас, наверное, разный бэкграунд. Я в своё время много писал и на C++, меня отсутствие примитивов не смущало, хотя большая часть приложений была многопоточной (Win32 API+ATL).

                    Хотя, конечно, оператор lock в C# изрядно порадовал.

                    А если считать за основу стандарт C++11, то, считай, больше тридцати лет в языке примитивов синхронизации не было.

                    Появятся, конечно, в связи с выпуском операционной системы Chrome OS.
                    • 0
                      Да я не отрицаю, можно и без примитивов. Хотя в том же MFC был CMutex и прочие радости, чтобы минимизировать вероятность прострела ноги при жонглировании с HANDLEами и WaitFor*, ReleaseMutex.

                      Я тут бы даже примитивы на первое место не ставил, а ставил то, что нет ни формальной семантики, ни хотя бы упоминания чего нибудь связанного с параллелизмом и недетерминизмом в стандарте ECMA-262.
                      • 0
                        MFC — вообще ни разу не стандарт.

                        С таким же успехом можно упомянуть класс Locker, включенный в V8.
                        • 0
                          > MFC — вообще ни разу не стандарт.

                          А я что сказал, что стандарт? Я CLocker к тому упомянул, что люди нуждаются в RAII-based велосипедах, которые бы для них управляли локами, чтобы не прострелить себе ногу ручным управлением. А вот если стандарт включает либо примитивы, либо хотябы библиотечку официальных «оберток», то потребность в велосипедах отпадает. Переносимость повышается. Карма очищается и чакры открываются ;-)
                          • 0
                            тьфу, CMutex, совсем уже крыша поехала ;-)
                          • 0
                            Ещё раз, — подобные классы есть в V8 (JavaScript VM). Просто ими редко пользуются.
                            • +1
                              Тут я потерял нить разговора если честно :-)

                              Locker в V8 — это несколько большее, чем обертка на простым мьютексом. Когда человек пишет на С++ он может синхронизировать или не синхронизировать потоки, как его душе угодно. Но в V8 как только ему захотелось использовать V8 из двух разных потоков — он обязан использовать Locker, для того чтобы a) гарантировать эксклюзивный доступ к VM b) гарантировать, что VM правильно инициализует внутренние структуры данных соответствующие потоку.

                              Если бы даже в C++ были примитивы синхронизации, Locker ими не заменить, он сложнее.
                              • 0
                                Согласен. А по-другому, увы, пока никак.

                                Сейчас многопоточную обработку делают на C++, как вручную, так и с помощью libeio.

                                А JS используют в качестве клея.
                            • 0
                              Ну и Locker (как, например, и другая фича V8 — preemption) — это скорее деталь реализации. Нестандартная плюшка, никаким образом не торчащая через pure js api.
                              • 0
                                до C++11 единственной стандартной плюшкой было ключевое слово volatile. всё остальное торчало тридцать лет с гаком через нестандартные библиотеки.
            • 0
              > Однопоточная модель не является ограничением JavaScript, а скорее является ограничением его реализации в виде V8

              V8 как таковой тут непричем, насколько я знаю все реализации JavaScript однопоточные. Различные штуки в виде WebWorkers позволяют использовать потоки, однако разделения на уровне переменных не происходит, так что это больше похоже по смыслу на отдельные процессы. И это кстати хорошо.
          • 0
            Достаточно раскидывать коллбэки на менее загруженные ядра для начала.
    • +3
      Это не является ограничением языка. Более того, на сегодня в Node.JS возможна и многопоточная обработка данных (Web Workers), и событийная машина на нескольких ядрах (передача FD дочернему процессу, multinode, cluster), и псевдосинхронная обработка (node-fibers).
  • +7
    Эта статья, также как и мое интервью с @ryah, годовалой давности, хотя критика асинхронности актуальна и сегодня.

    Большинство приложений можно и нужно писать в синхронном стиле. На Ноде это можно сделать с fibers и моей библиотекой Common Node.

    Ссылка на бенчмарки в README.
    • +3
      Мне понравилась эта статья именно своей корректностью (серебряной пули нет, снова и снова). Поэтому и перевёл.
      • +1
        Большое спасибо за перевод, я полностью согласен. Просто хотел сказать что дата опять не указана в начале статьи.
        • +1
          Смысла особого нет (все переводы пишу «как есть»). Всё до сих актуально.
          • НЛО прилетело и опубликовало эту надпись здесь
            • +1
              добавлю, — и полезно.

              слишком часто Node воспринимают как панацею.
              • НЛО прилетело и опубликовало эту надпись здесь
                • 0
                  Есть такое ощущение. Я, впрочем, до сих пор не понял, чего конкретно Ryan пытается достичь с node.
                  • +1
                    Помимо недавнего тренда с Windows/libux, я пока заметил только стремление сделать удобное окружение для создания сервисов, похожих на те, которые создавал раньше он на Си.

                    Фактически новые API ветки 0.4 намного удобнее прежних (0.2).

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

                    Больше ничего особо не надо.
                    • НЛО прилетело и опубликовало эту надпись здесь
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • 0
                      В этом я с вами согласен, по большей части моя работа с Node.js именно ради процесса идёт.
  • 0
    Я кстати вообще не понимаю как работает асинхронность в js… Может кто-нибудь объяснить?
    • +1
      Как везде. Операционная система предоставляет API для генерации событий и для ожидания оных.

      При этом, пока процесс ждёт возникновения событий, он не тратит времени CPU, а просто спит. В нужный момент ОС его разбудит.

      Помимо событий ввода/вывода, типичные события — срабатывание таймера, таймаут или завершение другого процесса.

      Кстати, часть асинхронности обеспечивается фоновым запуском (незаметным разработчику) отдельных потоков выполнения, если иначе обеспечить асинхронность нельзя. Типичный пример — node-mysqlclient.
      • +3
        Пожалуй тут важно добавить что система может ждать одновременно наступления множества событий. Вы установили обработчики и вернули управление. Пока ваш скрипт ждет вызова обработчиков, система может выполнять другие скрипты. А события наступают асинхронно — вы не знаете какой обработчик когда вызовется.
        Есть более прямолинейный вариант с фибрами — когда выполнение скрипта приостанавливается в некоторой точке до наступления события, а потом возобновляется с этой же точки.

        Оба варианта — разновидность кооперативной многозадачности. Она более легковесна чем полноценная вымещающая (потоки), в ней не нужны блокировки, но очень важно следить чтобы в код не закрался вечный цикл или синхронная операция, иначе не только ваш скрипт, но и вся система повиснет наглухо.

        Потоки (вымещающая многозадачность) не имеет этого недостатка, потому что там передача управления может произойти не только во время ожидания завершения операции, но и в любой другой момент. Вы не знаете в какой момент ваш код будет преостановлен и выполнен чужой код. Проблема в том что создание/удаление потоков — дорогая операция. Часто создавать поток выйдет дороже, чем сама операция ввода-вывода. Плюс бывают нужны блокировки, о которых тоже нельзя забывать. Раньше для решения проблемы использовали пулы заранее созданных потоков.

        Сейчас чаще совмещают кооперативную и вымещающую многозадачность. Наиболее красиво эта идея, да и вообще масштабирование, на мой взгляд сделано в Erlang. Очень рекоммендую…

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.