<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
	<id>https://support.qbpro.ru/index.php?action=history&amp;feed=atom&amp;title=Tactoom.com_%D0%B8%D0%B7%D0%BD%D1%83%D1%82%D1%80%D0%B8_%E2%80%94_%D1%81%D0%BE%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%B1%D0%BB%D0%BE%D0%B3-%D0%BF%D0%BB%D0%B0%D1%82%D1%84%D0%BE%D1%80%D0%BC%D0%B0_%D0%BD%D0%B0_NodeJS%2FNoSQL</id>
	<title>Tactoom.com изнутри — социальная блог-платформа на NodeJS/NoSQL - История изменений</title>
	<link rel="self" type="application/atom+xml" href="https://support.qbpro.ru/index.php?action=history&amp;feed=atom&amp;title=Tactoom.com_%D0%B8%D0%B7%D0%BD%D1%83%D1%82%D1%80%D0%B8_%E2%80%94_%D1%81%D0%BE%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%B1%D0%BB%D0%BE%D0%B3-%D0%BF%D0%BB%D0%B0%D1%82%D1%84%D0%BE%D1%80%D0%BC%D0%B0_%D0%BD%D0%B0_NodeJS%2FNoSQL"/>
	<link rel="alternate" type="text/html" href="https://support.qbpro.ru/index.php?title=Tactoom.com_%D0%B8%D0%B7%D0%BD%D1%83%D1%82%D1%80%D0%B8_%E2%80%94_%D1%81%D0%BE%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%B1%D0%BB%D0%BE%D0%B3-%D0%BF%D0%BB%D0%B0%D1%82%D1%84%D0%BE%D1%80%D0%BC%D0%B0_%D0%BD%D0%B0_NodeJS/NoSQL&amp;action=history"/>
	<updated>2026-04-03T18:41:57Z</updated>
	<subtitle>История изменений этой страницы в вики</subtitle>
	<generator>MediaWiki 1.38.1</generator>
	<entry>
		<id>https://support.qbpro.ru/index.php?title=Tactoom.com_%D0%B8%D0%B7%D0%BD%D1%83%D1%82%D1%80%D0%B8_%E2%80%94_%D1%81%D0%BE%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%B1%D0%BB%D0%BE%D0%B3-%D0%BF%D0%BB%D0%B0%D1%82%D1%84%D0%BE%D1%80%D0%BC%D0%B0_%D0%BD%D0%B0_NodeJS/NoSQL&amp;diff=975&amp;oldid=prev</id>
		<title>imported&gt;Vix: Новая страница: «http://habrahabr.ru/post/130345/  Итак, пришло время раскрыть некоторые карты и рассказать о том, как ус…»</title>
		<link rel="alternate" type="text/html" href="https://support.qbpro.ru/index.php?title=Tactoom.com_%D0%B8%D0%B7%D0%BD%D1%83%D1%82%D1%80%D0%B8_%E2%80%94_%D1%81%D0%BE%D1%86%D0%B8%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%B1%D0%BB%D0%BE%D0%B3-%D0%BF%D0%BB%D0%B0%D1%82%D1%84%D0%BE%D1%80%D0%BC%D0%B0_%D0%BD%D0%B0_NodeJS/NoSQL&amp;diff=975&amp;oldid=prev"/>
		<updated>2013-09-09T20:19:22Z</updated>

		<summary type="html">&lt;p&gt;Новая страница: «http://habrahabr.ru/post/130345/  Итак, пришло время раскрыть некоторые карты и рассказать о том, как ус…»&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Новая страница&lt;/b&gt;&lt;/p&gt;&lt;div&gt;http://habrahabr.ru/post/130345/&lt;br /&gt;
&lt;br /&gt;
Итак, пришло время раскрыть некоторые карты и рассказать о том, как устроен Tactoom изнутри.&lt;br /&gt;
&lt;br /&gt;
В этой статье я расскажу о разработке и выведении в production веб-сервиса с использованием:&lt;br /&gt;
NodeJS (fibers), MongoDB, Redis, ElasticSearch, Capistrano, Rackspace.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
'''Вступление'''&lt;br /&gt;
&lt;br /&gt;
Три недели назад мы с Давидом (DMiloshev) запустили инфосоциальную сеть Tactoom.com. О том, что это такое можно почитать здесь.&lt;br /&gt;
&lt;br /&gt;
На фоне шума, недавно поднятого вокруг NodeJS, вероятно, многим интересно, что же эта технология представляет из себя не на словах, а на деле.&lt;br /&gt;
&lt;br /&gt;
NodeJS — это вовсе не панацея. Это просто еще одна технология, по сути, ничем не лучше других. Для того чтобы добиться хорошей производительности и масштабируемости, вам придется хорошенько попотеть — так же, как и везде.&lt;br /&gt;
&lt;br /&gt;
'''Архитектура приложения'''&lt;br /&gt;
&lt;br /&gt;
NodeJS приложение делится на два вида процессов:&lt;br /&gt;
#Web процесс (http)&lt;br /&gt;
#Cloud процесс (очереди)&lt;br /&gt;
&lt;br /&gt;
Все процессы абсолютно независимы друг от друга, могут находиться на разных серверах и даже в разных точках земного шара. При этом, масштабируется приложение как раз при помощи мультипликации этих процессов. Общение между ними происходит сугубо через централизированный сервер сообщений (redis).&lt;br /&gt;
&lt;br /&gt;
Web процессы обслуживают прямые http запросы пользователей. Каждый процесс может обрабатывать множество запросов одновременно. Учитывая специфику Eventloop, в зависимости от соотношения CPU/IO каждого конкретного запроса, предел параллельной обработки может либо снижаться, либо повышаться для отдельного процесса на момент времени.&lt;br /&gt;
&lt;br /&gt;
Cloud процессы производят операции, которые не связаны напрямую с пользовательскими запросами. Например: отправка email-ов, денормализация данных, поисковая индексация. Как и Web, один Cloud процесс может обрабатывать множество задач разных типов одновременно.&lt;br /&gt;
&lt;br /&gt;
Стоит отметить, что здесь очень важна «атомарность» задач/запросов. То есть, нужно следить за тем, чтобы емкая задача/вычисление было разбито на множество более мелких частей, которые затем будут равномерно распределены по остальным процессам. Это повысит скорость выполнения задачи, отказоустойчивость и снизит потребление памяти и коэффициент блокировки каждого процесса и сервера целиком.&lt;br /&gt;
&lt;br /&gt;
'''Web → Cloud'''&lt;br /&gt;
&lt;br /&gt;
Я стараюсь организовать Web процессы таким образом, чтобы повысить общий коэффициент времени IO против CPU, а значит сфокусировать их на быстрой выдаче http при высокой конкурентности запросов. Это значит, что Web делегирует high-cpu логику в Cloud, ждет ее выполнения, затем получает результат вычислений. Соответственно, в силу асинхронной архитектуры nodejs, во время ожидания Web может выполнять другие запросы.&lt;br /&gt;
&lt;br /&gt;
'''Кластеризация'''&lt;br /&gt;
&lt;br /&gt;
Архитектура Web и Cloud очень схожа, за исключением того, что вместо http сокета Cloud «слушает» redis очередь.&lt;br /&gt;
&lt;br /&gt;
Кластеризация node процессов происходит по следующим принципам:&lt;br /&gt;
#На каждом физическом сервере запущен один процесс-супервизор (node-cluster)&lt;br /&gt;
#Дочерние процессы супервизора и есть наши Web-ы и Cloud-ы, количество которых всегда равно количеству ядер сервера.&lt;br /&gt;
#Супервизор контролирует потребление памяти каждым дочерним процессом и, в случае превышения заданной нормы, перезапускает его (предварительно дождавшись завершения текущих запросов этого процесса).&lt;br /&gt;
&lt;br /&gt;
[[Файл:Nodejs-cluster.png]]&lt;br /&gt;
&lt;br /&gt;
'''Fibers'''&lt;br /&gt;
&lt;br /&gt;
Весь высокоуровневый слой приложения написан с использованием node-sync (fibers), без которого я вообще слабо себе представляю его разработку. Дело в том, что такие сложные вещи, как та же сборка статики, реализовать на «официальной» callback-driven парадигме весьма сложно, если не глупо. Тем, кто еще не видел код того же npm, настоятельно рекомендую на [https://github.com/isaacs/npm/blob/master/lib/install.js него] посмотреть, и попытаться понять, что там происходит, а главное — зачем. А холивары и троллинг, которые разрастаются вокруг асинхронной парадигмы nodejs чуть ли не каждый день, мягко говоря, вызывают у меня недоумение.&lt;br /&gt;
&lt;br /&gt;
Подробнее о node-sync вы можете узнать в моей статье:&lt;br /&gt;
[http://habrahabr.ru/blogs/nodejs/116124/ node-sync — псевдо-синхронное программирование на nodejs с использованием fibers][[node-sync — псевдо-синхронное программирование на nodejs с использованием fibers|сохраненная копия]]&lt;br /&gt;
&lt;br /&gt;
'''Web'''&lt;br /&gt;
&lt;br /&gt;
Общая логика Web приложения реализована на фреймворке expressjs в стиле «express». За исключением того, что каждый запрос заворачивается в отдельный Fiber, внутри которого все операции выполняются в синхронном стиле.&lt;br /&gt;
&lt;br /&gt;
В силу невозможности переопределить некоторые участки функциональности expressjs, в частности роутинга, его пришлось вынести из npm и включить в основной репозиторий проекта. Это же касается и ряда других модулей (особенно тех, которые разработаны LearnBoost), ибо contributing в их проекты дается очень большим трудом и вообще не всегда удается.&lt;br /&gt;
&lt;br /&gt;
CSS генерируется через stylus. Это действительно очень удобно.&lt;br /&gt;
Шаблонизатор — ejs (как на сервере, так и на клиенте).&lt;br /&gt;
Загрузка файлов — connect-form.&lt;br /&gt;
&lt;br /&gt;
Web работает очень быстро, поскольку все модули и инициализация загружаются в память процесса при запуске. Я стараюсь держать среднее время отклика Web процесса на любую страницу — до 300ms (исключая загрузку изображений, регистрацию и т.д.). Проводя профайлинг, я с удивлением обнаружил, что 70% этого времени отнимает работа mongoose (mongodb ORM для nodejs) — об этом ниже.&lt;br /&gt;
&lt;br /&gt;
'''i18n'''&lt;br /&gt;
&lt;br /&gt;
Долго искал подходящее решение для интернационализации в nodejs, и мои поиски сошлись на node-gettext с небольшим допиливанием. Работает как часы, файлы локалей подтягиваются «на лету» серверными nodejs процессами при обновлении.&lt;br /&gt;
&lt;br /&gt;
'''Кэш'''&lt;br /&gt;
&lt;br /&gt;
Функциональность кэширования со всей своей логикой уместилась в два экрана кода. В качестве cache-backend используется redis.&lt;br /&gt;
&lt;br /&gt;
'''Память'''&lt;br /&gt;
&lt;br /&gt;
У Web процессов память течет рекой, как позднее оказалось, из-за mongoose. Один процесс (днем, во время средней нагрузки), съедает до 800MB за два часа, после чего перезапускается супервизором.&lt;br /&gt;
Утечки памяти в nodejs искать довольно сложно, если вы знаете интересные способы — дайте мне знать.&lt;br /&gt;
&lt;br /&gt;
'''Данные'''&lt;br /&gt;
&lt;br /&gt;
Как показала практика, schema-less парадигма mongodb идеально подходит для модели Tactoom. Сама БД ведет себя хорошо (весит 376MB, из них 122MB — индекс), данные выбираются исключительно по индексам, поэтому результат любого запроса — не больше 30ms, даже при высокой нагрузке (большинство запросов вообще &amp;lt;1ms).&lt;br /&gt;
&lt;br /&gt;
Если интересно, во второй части могу более подробно рассказать о том, как удалось «приручить» mongodb для ряда нетривиальных задач (и как не удалось).&lt;br /&gt;
&lt;br /&gt;
'''mongoosejs (mongodb ORM для nodejs)'''&lt;br /&gt;
&lt;br /&gt;
О нем хочу отдельно сказать. Выбор списка 20-ти пользователей: запрос и выбор данных в mongo занимает 2ms, передача данных — 10ms, потом mongoose делает что-то еще 200ms (уже молчу про память) и в итоге получаю объекты. Если переписать это на более низкоуровневый node-mongodb-native, то все это займет 30ms.&lt;br /&gt;
&lt;br /&gt;
Постепенно, мне пришлось переписать практически все на mongodb-native, при этом, повысив быстродействие системы в целом раз в 10.&lt;br /&gt;
&lt;br /&gt;
'''Статика'''&lt;br /&gt;
&lt;br /&gt;
Вся статика Tactoom хранится на Rackspace Cloud Storage. При этом я использую статический домен cdnX.infosocial.net, где X — 1..n. Этот домен пробрасывает через DNS на внутренний домен контейнера в Cloud Storage, позволяя браузерам грузить статические файлы параллельно. Каждый статический файл хранится в двух копиях (plain и gzip) и имеет уникальное имя, в которое зашита версия. Если версия файла обновится — адрес изменится, и браузеры загрузят новый файл.&lt;br /&gt;
&lt;br /&gt;
Сборка статики приложения (клиентский js и css, картинки) происходит через самописный механизм, который определяет измененные файлы (через git-log), делает minify, делает gzip копию и загружает их на CDN. Скрипт сборки так же следит за измененными изображениями и обновляет их адреса в соответствующих css файлах.&lt;br /&gt;
Список (mapping) статики адресов всех файлов хранится в Redis. Этот список загружает в память каждый Web процесс при запуске либо при обновлении статических версий.&lt;br /&gt;
По сути, дэплоймент любых изменений статики осуществляется одной командой, которая делает все сама. Причем это не требует никакой перезагрузки, поскольку nodejs приложения подхватывают измененные адреса статических файлов на лету через redis pub/sub.&lt;br /&gt;
&lt;br /&gt;
Пользовательская статика тоже хранится на Rackspace, но в отличие от статики приложения, не имеет версий, а просто проходит определенную канонизацию, позволяющую по хэшу картинки получить адреса всех ее размеров на CDN.&lt;br /&gt;
&lt;br /&gt;
Для определений хоста (cdnX), на котором хранится конкретный статический файл, используется consistent hashing.&lt;br /&gt;
&lt;br /&gt;
'''Серверная архитектура'''&lt;br /&gt;
[[Файл:Tactoom-servers.png]]&lt;br /&gt;
&lt;br /&gt;
По сути, Tactoom раскидан на 3 гермозоны:&lt;br /&gt;
#Rackspace — площадка для быстрого масштабирования и хранения статики &lt;br /&gt;
#Площадка в Европе — здесь наши физические сервера&lt;br /&gt;
#Секрет (сюда ротэйтятся логи, производятся фоновые вычисления и собирается статистика)&lt;br /&gt;
&lt;br /&gt;
В мир смотрит только один сервер — nginx, с открытыми портами 80 и 4000. Последний используется для COMET соединений.&lt;br /&gt;
Остальные сервера общаются между собой по прямым ip, закрыты от мира через iptables.&lt;br /&gt;
&lt;br /&gt;
'''&amp;lt;nowiki&amp;gt;:80&amp;lt;/nowiki&amp;gt;'''&lt;br /&gt;
nginx проксирует запросы через upstream конфигурацию на Web серверы. На данный момент есть два апстрима: tac_main и tac_media. Каждый из них содержит список Web серверов, на которых работает node-cluster по 3000 порту, у каждого Web сервера есть свой приоритет при распределении запросов.&lt;br /&gt;
&lt;br /&gt;
tac_main — кластер Web серверов, которые находятся близко к базе данных и отвечают за выдачу большинства веб-страниц для зарегистрированных пользователей Tactoom.&lt;br /&gt;
&lt;br /&gt;
tac_media — кластер Web серверов, находящихся близко к CDN. Через них происходят все операции по загрузке и ресайзингу изображений.&lt;br /&gt;
&lt;br /&gt;
Сервера webN и cloudN изображены, чтобы показать, где я добавляю сервера при хабраэффекте и других приятных событиях.&lt;br /&gt;
Новые сервера поднимаются в течение 10 минут — по образу, сохраненному на CDN.&lt;br /&gt;
&lt;br /&gt;
'''&amp;lt;nowiki&amp;gt;:4000&amp;lt;/nowiki&amp;gt;'''&lt;br /&gt;
&lt;br /&gt;
Тут обычный proxy-pass на сервер comet, где работает nodejs COMET приложение Beseda, о которой я расскажу во второй части.&lt;br /&gt;
&lt;br /&gt;
'''tac1, tac2, data1'''&lt;br /&gt;
&lt;br /&gt;
Это главные сервера Tactoom: XEON X3440 4x2.53 ГГц 16 ГБ 2x1500 ГБ Raid1.&lt;br /&gt;
На каждом работает Mongod процесс, все они объединены в ReplicaSet с автоматическим failover-ом и дистрибьюцией операций чтения на slave-ы.&lt;br /&gt;
&lt;br /&gt;
На tac1 — главный Web кластер, на tac2 — Cloud кластер. В каждом кластере по 8 nodejs процессов.&lt;br /&gt;
&lt;br /&gt;
В ближайшее время создам еще один upstream tac_search, на который будут роутиться исключительно поисковые запросы. В нем будет Web-cluster, который поставлю рядом с elasticsearch (про него во второй части) сервером.&lt;br /&gt;
&lt;br /&gt;
'''Выводы'''&lt;br /&gt;
&lt;br /&gt;
Цитируя лозунг создателей NodeJS:&lt;br /&gt;
«Because nothing blocks, less-than-expert programmers are able to develop fast systems.»&lt;br /&gt;
«Из-за того, что ничего не блокируется, менее-чем-эксперты могут разрабатывать быстрые системы.»&lt;br /&gt;
&lt;br /&gt;
Это обман. Я использую nodejs уже почти 2 года и на собственном опыте знаю, что для того, чтобы на нем разрабатывать «быстрые системы», нужно не меньше опыта (а то и больше), чем на любой другой технологии. В реальности же с callback-driven парадигмой в nodejs и особенностями javascript в целом, скорее легче сделать ошибку (и потом очень долго ее искать), чем выиграть в производительности.&lt;br /&gt;
С другой стороны, троллинг господина Ted Dziuba тоже полный бред, ибо пример с «числами фибоначчи» высосан из пальца. Так будет делать только человек, не понимающий, как работает Eventloop и зачем он вообще нужен (что, кстати, доказывает пункт 1).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
После доклада на DevConf этой весной, мне часто задают вопросы о том, стоит ли делать новый проект на NodeJS. Мой ответ всем:&lt;br /&gt;
Если у вас куча времени и вы готовы проинвестировать его в развитие новой, сырой и спорной технологии — валяйте. Но если перед вами сроки/заказчики/инвесторы и у вас нет большого опыта с серверным JS за спиной — не стоит.&lt;br /&gt;
&lt;br /&gt;
Как показала практика, поднять проект на NodeJS — реально. Это работает. Но это стоило мне очень многого. Чего только стоит node open-source сообщество, о котором я еще постараюсь успеть написать.&lt;br /&gt;
&lt;br /&gt;
Часть 2&lt;br /&gt;
&lt;br /&gt;
Вторая часть статьи будет на днях. Вот краткий список того, о чем я в ней напишу:&lt;br /&gt;
1. Поиск (elasticsearch)&lt;br /&gt;
2. Почта (google app engine)&lt;br /&gt;
3. Деплоймент (capistrano, npm)&lt;br /&gt;
4. Очереди (redis, kue)&lt;br /&gt;
5. COMET сервер (beseda)&lt;br /&gt;
&lt;br /&gt;
Слишком много информации для одной статьи.&lt;br /&gt;
Если в комментариях увижу интересные вопросы, на них отвечу во второй части.&lt;/div&gt;</summary>
		<author><name>imported&gt;Vix</name></author>
	</entry>
</feed>