
Год назад я написал статью о моем первом опыте масштабирования Meteor. Одним словом, на Метеоре я создал популярный сайт в жанре фэнтези-футбол. В какой-то момент мой сервис начал тормозить. Единственный сервер, на котором я запускал игру, больше не справлялся с нагрузкой. Я смог решить эти проблемы раннего масштабирования, среди прочего, добавив дополнительные серверы.
Что ж, когда прошлым летом начался новый футбольный сезон, я снова столкнулся с проблемами масштабирования. Одно только добавление серверов не решит этих проблем. Но мне удалось их преодолеть.
Эта статья объяснит то, что я узнал на этот раз, разбитая на шесть практических советов.
Одна вещь, которая изменилась со времени моей последней статьи, - это то, что Meteor Development Group наконец-то выпустила Galaxy, который дает вам хостинг Meteor по цене 29 долларов за контейнер в месяц. Сюда не входит хостинг баз данных, но для этого можно использовать что-то вроде Compose или mLab. Как вариант, вы можете самостоятельно разместить базу данных на AWS или DigitalOcean. Это будет дешевле, но с вашей стороны потребуется больше работы.
Я сам использую DigitalOcean для хостинга. Стоимость сайта составляет 5 долларов в месяц, капли размером 512 МБ с одним экземпляром Meteor, работающим на каждую каплю. Я использую Meteor Up (Mup) для развертывания и Compose.io для размещения баз данных.
Выбирать DigitalOcean или Galaxy - решать вам. Galaxy сделает кучу всего за вас и сэкономит ваше время. Переход по маршруту DigitalOcean сэкономит вам 24 доллара на контейнер в месяц. Для многих компаний переход на Galaxy имеет наибольший смысл, поскольку зарплаты разработчиков будут намного дороже. В любом случае я оставлю бизнес-решения на ваше усмотрение.
Двигаемся дальше. Вот несколько вещей, которые действительно помогли масштабировать наше приложение Meteor этим летом. У нас были плохие дни. Иногда это действительно было нелегко, но мы справились с этим.
Резюме извлеченных уроков
Вот основные уроки, которые я извлек за год масштабирования:
Урок №1: индексы MongoDB очень важны!
Урок №2: Слишком много экземпляров Meteor - проблема!
Урок №3: не беспокойтесь о масштабировании Nginx.
Урок №4: отключите пользователей, если они какое-то время отсутствовали
Урок # 5: Уилл Григгс горит
Урок № 6: Обратите внимание на то, как они масштабировали Pokemon Go
Давайте пройдемся по ним один за другим.
Урок №1: индексы MongoDB очень важны!
Так что это была любительская ошибка. В каждой статье о масштабировании Meteor (или MongoDB) говорится об использовании индексов. И я сделал! Но один указатель отсутствовал, и я обгорел за него - действительно сгорел - в самую важную для нас ночь в году.
Объяснение индексов в качестве примера. Если у вас 10 000 очков игроков и вы хотите найти наивысший результат, в обычном случае Монго должен будет пройти через все эти очки, чтобы найти самый высокий. Если у вас есть индекс для оценки, то Mongo сохраняет копию всех оценок в возрастающем или убывающем порядке и найдет наивысшую оценку за небольшой промежуток времени. Вы можете узнать больше об индексах на сайте MongoDB.
В проекте Meteor я рекомендую иметь один файл publishing.js, содержащий все ваши публикации. Под каждой публикацией у вас должен быть код, который создает необходимый индекс для каждой публикации. Код для создания индекса в Meteor выглядит примерно так:
Meteor.startup(function () {
Teams._ensureIndex({ userId: 1 });
});
Поле _id по умолчанию имеет индекс, поэтому вам не о чем беспокоиться.
Разбираемся в деталях. Я использую Compose.io для хостинга MongoDB. С ними все в порядке, и с поддержкой тоже все в порядке, но не слушайте их, когда они думают, что все ваши проблемы можно решить, добавив больше оперативной памяти. Это неправда. Иногда это могло сработать, но в моем случае это был просто бессмысленный совет.
Я использую Kadira.io для мониторинга производительности. Каждое приложение Meteor должно использовать Kadira, а базовый пакет отличный и бесплатный, поэтому нет причин не использовать его. (Обновление: в настоящее время Кадира по-прежнему является очевидным выбором для приложений Meteor, но команда, стоящая за Kadira, недавно отказалась от Meteor, так что остерегайтесь этого в будущем.)
В Кадире я видел такие графики:

В определенный момент время отклика PubSub и методов становится смехотворно большим. Ответить на все, что превышает 1000 мс, проблематично. Даже время отклика 500 мс может быть плохим. Но среднее время ответа 10–20 секунд в течение часа подряд означает, что пользователи ненавидят вас, а ваше приложение практически не работает на них.
В общем, когда дела идут медленно, я просто добавляю больше серверов. И здесь я тоже сделал то же самое, за исключением того, что на этот раз добавление дополнительных серверов только ухудшило положение. Гораздо хуже. Но мы вернемся к этому позже.
Итак, на этом этапе вы копаетесь в Google и спамите StackOverflow и форумы Meteor.
В конце концов я наткнулся на эту жемчужину на дашбордах Kadira:

Из этого мы видим, что базе данных требуется вечность, чтобы ответить. Добавление дополнительных экземпляров Meteor нам здесь не поможет. Нам нужно разобраться с Монго.
Кадира не смогла показать мне, почему база данных так медленно отвечает. Каждая публикация и метод демонстрировали очень высокое время отклика базы данных.
Ответ пришел от посещения Compose.io в часы пик. На панели управления вы можете просмотреть текущие операции (текущие операции), которые выполняются в любой момент. Я видел что-то вроде этого (но намного хуже):

Я понятия не имел, что это за чушь, но вы увидите, что в каждой операции есть поле secs_running. На изображении выше для всего указано 0 секунд, и это здорово! Но то, что я видел в пиковое время, было 14 секунд, 9 секунд, 10 секунд… для различных операций, которые происходили! И все это исходило от того же запроса, сделанного моим приложением.
Я сам выполнил этот запрос, и мне действительно потребовалось около 16 секунд, чтобы получить ответ! Нехорошо! И запуск его с объяснением (как предлагали некоторые на форумах Meteor) показал, что сканировалось 180 000+ документов! Вот один из проблемных вопросов:

В любом случае… о чудо, для этого запроса не настроен индекс. Я добавил следующие индексы:
Meteor.startup(function () {
HeadToHeadMatches._ensureIndex({ team1Id: 1, gameweek: 1 });
HeadToHeadMatches._ensureIndex({ team2Id: 1, gameweek: 1 });
});
После этого вся база данных снова начинает работать быстро. Этот один проблемный запрос замедлял работу всей базы данных!
ОБНОВЛЕНИЕ №1: на основе комментария Джоша Оуэна, лучший способ добавить индексы - использовать Collection.rawCollection и createIndex, но приведенный выше код будет работать для вас по крайней мере до тех пор, пока Метеор 1.4.2.
ОБНОВЛЕНИЕ №2: индексы сложнее, чем я думал, когда снова столкнулся с ними на этой неделе. Вы, вероятно, не сможете найти все свои запросы, требующие индексов, без просмотра журналов.
Вам необходимо найти все запросы, использующие COLLSCAN. Это означает, что запрос не использует индекс, и чтобы найти документ, Mongo должен пройти через всю коллекцию, чтобы проверить, существует ли документ, который вы ищете.
Если вы используете Compose.io и классическую версию MongoDB, вам нужно будет написать в службу поддержки по электронной почте, чтобы узнать, какие запросы используют COLLSCAN. Если вы используете их план MongoDB 3.2, вы сможете найти эти запросы на их панели управления.
Кроме того, если вы подозреваете, что запрос является проблематичным, запустите запрос с помощью объяснять (), и вы сможете увидеть, как сканируются документы. Если nscanned равно количеству документов во всей коллекции, у вас проблема и вам нужен индекс. Один плохой индекс может серьезно повлиять на всю вашу базу данных, поскольку он заблокирует ее для всех запросов.
Урок №2: Слишком много экземпляров Meteor - проблема!
Итак, как только вы научитесь масштабироваться до нескольких экземпляров, вы надеетесь, что это конец всем страданиям, связанным с масштабированием. Увы, это не так. А добавление слишком большого количества серверов в определенный момент снизит производительность.
Это связано с тем, что Mongo использует дополнительную оперативную память для каждого подключения к базе данных. В какой-то момент у меня, должно быть, было около 60–70 экземпляров, подключенных к моей базе данных, и Монго это не нравилось, да и мне не нужно было столько. Экземпляры Meteor не были узким местом для производительности.
Конечно, вы можете дать Mongo больше оперативной памяти, но будьте осторожны с тем, что произойдет, когда вы продолжите добавлять больше серверов. Возможно, вы снимаете нагрузку с каждого экземпляра Meteor, но добавляете нагрузку в Mongo, создавая новое узкое место.
Урок №3: не беспокойтесь о масштабировании Nginx
Этим летом я беспокоился только о том, что Nginx станет моим узким местом. Так бывает редко. Nginx должен без проблем обрабатывать тысячи одновременных пользователей.
Я разговаривал с компанией, у которой несколько месяцев назад были проблемы с Nginx. Им пришлось обрабатывать пару тысяч одновременных подключений. Вы можете прочитать эту статью, чтобы получить еще несколько советов по оптимизации Nginx под высокие нагрузки трафика.
Некоторые моменты из статьи, которые стоит использовать сразу:
Отключить журналы доступа:
По умолчанию nginx будет записывать каждый запрос в файл на диске для ведения журнала, вы можете использовать это для статистики, проверок безопасности и т. Д., Однако это происходит за счет использования ввода-вывода. Если вы не используете журналы доступа ни для чего, вы можете просто выключить их и избежать записи на диск.
Рабочие процессы и связи:
Рабочие процессы
Рабочий процесс - это основа nginx, как только мастер привязан к требуемым IP / портам, он будет порождать рабочих, как указано пользователь, и они затем возьмут на себя всю работу. Рабочие не являются многопоточными, поэтому они не распределяют соединение между ядрами ЦП. Таким образом, для нас имеет смысл запускать несколько воркеров, обычно по одному на ядро процессора. Для большинства рабочих нагрузок все, что превышает 2–4 воркера, является излишним, поскольку nginx столкнется с другими узкими местами до того, как процессор станет проблемой, и обычно у вас будут просто незанятые процессы. Если ваши экземпляры nginx привязаны к ЦП после 4 воркеров, надеюсь, вам не нужно, чтобы я вам сообщал.
Аргумент в пользу большего числа рабочих процессов может быть приведен, когда вы имеете дело с ситуациями, когда у вас много блокирующих операций ввода-вывода диска. Вам нужно будет протестировать свою конкретную настройку, чтобы проверить время ожидания для статических файлов, и если оно велико, попробуйте увеличить рабочие процессы.
Рабочие связи
Рабочие связи эффективно ограничивают количество подключений, которое каждый рабочий может поддерживать одновременно. Эта директива, скорее всего, предназначена для предотвращения запускаемых процессов и в случае, если ваша ОС настроена так, чтобы допускать больше, чем может обрабатывать ваше оборудование. Как отмечает разработчик nginx Валентин в списке рассылки nginx, nginx может закрывать поддерживающие соединения, если он достигает предела, поэтому нам не нужно беспокоиться о нашем значении keep-alive здесь. Вместо этого нас интересует количество активных в данный момент соединений, которые обрабатывает nginx. Формула для максимального количества подключений, которое мы можем обработать, будет выглядеть следующим образом:
worker_processes * worker_connections * (K / среднее $ request_time)
Где K - количество активных в данный момент соединений. Кроме того, для значения K мы также должны рассмотреть обратное проксирование, которое откроет дополнительное соединение с вашим сервером.
В конфигурационном файле по умолчанию для директивы worker_connections установлено значение 1024, если учесть, что браузеры обычно открывают 2 соединения для ресурсов сайта с прокладкой каналов, тогда у нас остается максимум 512 пользователей, обрабатываемых одновременно. С проксированием это еще меньше, хотя, надеюсь, ваш бэкэнд довольно быстро откликается на бесплатное соединение.
Учитывая все обстоятельства, связанные с подключениями рабочих, должно быть достаточно ясно, что если вы увеличиваете объем трафика, вы в конечном итоге захотите увеличить количество подключений, которые может выполнять каждый рабочий. 2048 подойдет большинству людей, но, честно говоря, если у вас есть такой трафик, вы не должны сомневаться, насколько высоким должно быть это число.
Урок №4: отключение бездействующих пользователей
Это важно! Я не понимаю, почему это не так важно в сообществе Meteor!
Отключите пользователей, когда они только что оставили свою вкладку открытой. Это так просто сделать и сэкономить драгоценные ресурсы.
Для автоматического отключения вы можете использовать этот пакет: mixmax: smart-disconnect.
Урок # 5: Уилл Григгс горит
Если вы зашли так далеко в своем посте, вы, вероятно, чувствуете себя очень вдохновленным и настроены на футбольную скандальную песню. Представляю вам Уилла Григгса:
На самом деле в этом не было никакого смысла. Просто казалось, что это уместно написать на данном этапе статьи. Но если мы действительно хотим извлечь из этого урок, то вот что:
Если вы разработчик-одиночка и тысячи людей полагаются на ваше приложение в работе прямо сейчас, это может вызвать стресс. Мой совет вам (и себе): успокойтесь, черт возьми. Послушайте немного Уилла Григгса в огне. Надеюсь, у вас все получится, и даже если что-то пойдет не так, это, вероятно, не так плохо, как вы думаете.
Pokemon Go поначалу был ужасен. Сервера постоянно были перегружены, но люди продолжали возвращаться поиграть. С точки зрения бизнеса, Niantic по-прежнему приносил прибыль. Шумиха улеглась, но это не имеет ничего общего с проблемами масштабирования или множеством ранних ошибок. Это просто конец моде.
Итак, жизненный урок: слушайте Уилла Григгса, когда вы в стрессе.
Урок № 6: Обратите внимание на то, как они масштабировали Pokemon Go
Что касается Pokemon Go, давайте немного поговорим о том, что произошло. Во-первых, Pokemon Go с вами не случится. У Pokemon Go была сильная команда бывших гуглеров, которые знали, как справляться с огромными нагрузками, но даже они были пойманы популярностью приложения. Они были готовы к большой нагрузке, но не к нагрузке размером с Твиттер.
Также появились некоторые приложения вокруг Pokemon Go. Приложения для чата Pokemon Go и инстаграммы Pokemon Go начали появляться и стали очень популярными, очень быстро с миллионом пользователей за считанные дни. Некоторые из этих приложений были разработаны индивидуальными разработчиками, и управление нагрузкой было для них проблемой.
Есть эта статья о том, как кто-то создал приложение Pokemon Go в Instagram с 500 000 пользователей за 5 дней и запустил его на сервере за 100 долларов в месяц. Это поразительно. Вывод из статьи заключается в том, что вы можете быстро создать масштабируемый MVP, если будете знать, что делаете.
Если вы можете это сделать, это определенно здорово, но если вы молодой и неопытный разработчик, это может быть невозможно. Я бы порекомендовал пойти и создать приложение своей мечты и не слишком беспокоиться о том, что произойдет, когда вам понадобится масштабирование.
Если вы можете создавать вещи правильно с самого начала, это определенно плюс, и определенно стоит попросить совета у более опытных разработчиков, чтобы попытаться сделать все правильно с самого начала. Но не позволяйте страху масштабирования удерживать вас от создания приложения своей мечты. Суровая реальность такова, что людям, вероятно, не понравится ваше приложение, и было бы впечатляюще, если бы вы смогли заставить его использовать 100 человек.
Но следуя принципам бережливого стартапа, лучше что-то построить, передать в руки реальных пользователей и получить обратную связь, чем никогда не запускать из-за страха не справиться с большой нагрузкой.
Смотря вперед
Эти эпизоды, связанные с масштабированием, были обузой, и в идеале я бы предпочел не заниматься этими проблемами. Было бы здорово, если бы все работало, и вы могли как можно дольше отталкивать проблемы масштабирования. Из-за этого я начал искать другие платформы, которые лучше справляются с масштабированием.
Одна из таких платформ - Elixir, построенная на Erlang. Erlang - это то, что использует Whatsapp, и благодаря ему команда из 35 инженеров расширилась до 450 миллионов пользователей! Даже сегодня, имея около 1 миллиарда пользователей, в Whatsapp работает всего 50 инженеров! Это невероятно, и вы можете прочитать больше здесь. Как они достигли такого огромного масштаба для приложения реального времени с таким небольшим количеством людей? Ответ - Эрланг. И сегодня вы можете использовать возможности Erlang с Phoenix Framework и Elixir. Мы все еще используем Meteor, но некоторые аспекты приложения я рассматриваю для перехода на Elixir, что позволит нам получать крупномасштабные обновления в реальном времени.
Я бы также посмотрел на Apollo, который будет работать с Meteor или любым сервером Node.js. Apollo поможет вам масштабировать Meteor, потому что вам не нужно, чтобы каждая публикация была реактивной при использовании Apollo (что является самым большим расходом ресурсов ЦП сервера для приложений Meteor). Вы можете достичь аналогичного результата, используя методы Meteor для отправки данных вместо публикации.
И последнее: несмотря на то, что многие влиятельные разработчики Meteor недавно покинули сообщество, в области масштабирования произошли некоторые изменения. Ознакомьтесь с пакетом redis-oplog и обсуждением, чтобы узнать больше. Это очень новый пакет, и я бы сказал, что он находится в стадии бета-тестирования, исходя из моего небольшого опыта игры с ним неделю назад.
Если вам понравился этот пост, отдайте ему сердце, и если вы хотите быть в курсе последних вдохновляющих статей о масштабировании, дайте мне подписаться.