FStop.fm — нишевая социальная сеть, которую я создал в прошлом году, чтобы связывать фотографов с моделями. По сути, это смесь Tinder и Instagram с функциями, которые предназначены для визуальных художников, желающих общаться друг с другом. Я написал еще один пост о некоторых уроках, которые я извлек, выводя FStop на рынок; этот пост подробно расскажет о некоторых технических проблемах, с которыми я столкнулся.

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

FStop живет в Azure как служба приложений. Когда это получило освещение в прессе, сопутствующий всплеск трафика привел к падению моего маленького веб-приложения. Это произошло потому, что я не настроил автомасштабирование в своем экземпляре службы приложений более низкого уровня.

Azure позволяет смехотворно легко масштабировать службу приложений. Я установил несколько правил, основанных на потреблении ЦП и памяти, и через несколько минут мы снова были в сети. Базовая конфигурация выглядит примерно так, что было достижимо за несколько кликов:

Обратите внимание, что это всего лишь служба приложения. FStop работает на базе Azure SQL, который так же легко масштабируется несколькими щелчками мыши на портале Azure.

Устранение неполадок в работе службы приложений

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

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

После добавления этого расширения и перехода к нему из портала вы можете настроить расширение для захвата дампов из процесса w3wp.exe — рабочего процесса IIS, обеспечивающего отзывчивость вашего веб-приложения.

Это показало, что уведомления по электронной почте вызывали сбои. По какой-то причине клиент .NET SMTP задыхался от O365 и вызывал проблемы с исчерпанием портов, которые приводили к сбою приложения. Вместо того, чтобы тратить часы на устранение неполадок, я решил убить двух зайцев и заменить SmtpClient реализацией SendGrid — что-то, что все равно было в моем невыполненном списке и решило проблемы с производительностью и масштабированием.

Непрерывное бесшовное развертывание

DevOps от FStop выглядит примерно так:

  1. Я проверяю код.
  2. Выполняется сборка TFS, а затем выполняются модульные/интеграционные тесты.
  3. Приложение развертывается в промежуточном слоте службы приложений, строка подключения которой указывает на зеркало рабочей базы данных.
  4. Выполняется полный набор тестов пользовательского интерфейса Selenium.
  5. Зеркальное отображение рабочей базы данных, промежуточная стадия повышается до рабочей, и выполняются все необходимые миграции базы данных.
  6. Дымовые тесты Selenium запускаются в рабочей среде и откатывают развертывание, если что-то пойдет не так.

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

Некоторые ошибки:

  • Возможно, вам потребуется установить ключ компьютера в файле web.config, чтобы обеспечить по-настоящему бесшовную замену при использовании объединительной платы SignalR любого типа.
  • Возможно, вам потребуется реализовать настраиваемое действие прогрева в вашем файле web.config, чтобы обеспечить по-настоящему беспрепятственный обмен.

Репликация Azure SQL/аварийное восстановление

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

Выяснилось, что центр обработки данных Azure в выбранном мной регионе (восток США) простоял. Это длилось несколько часов, и я ничего не мог сделать. Это было очень напряжно, так как это произошло сразу после запуска нашего мобильного приложения. Время не могло быть хуже.

Я не тратил время на снижение этого риска, потому что никогда раньше с ним не сталкивался. Оказывается, есть много способов удовлетворить потребность в высокой доступности, однако мне просто нужен недорогой план смягчения последствий; Здесь я не использовал высокочастотную торговую платформу или решение для медицинской телеметрии. Это была просто социальная сеть, которая в худшем случае доставляла неудобства некоторым людям. Я решил создать отказоустойчивую базу данных с помощью георепликации, на которую я мог бы направлять запросы, если это когда-нибудь повторится.

Масштабирование служб приложений с помощью SignalR

Обмен сообщениями и уведомления FStop зависят от SignalR для их функциональности в реальном времени.

После масштабирования FStop до двух экземпляров службы приложений в результате горизонтального масштабирования мои тесты обмена сообщениями в реальном времени завершались неудачно. Что было действительно интересно, так это то, что сбои были вызваны тем, что почти ровно 50% отправленных сообщений не были получены целевым экземпляром браузера. Я бы отправил десять сообщений из браузера А в браузер Б; Браузер B получит только пять или шесть из них.

Это было вызвано тем, что если браузер A устанавливает соединение с экземпляром 1 моего веб-приложения, а браузер B устанавливает соединение с экземпляром 2 моего веб-приложения, SignalR не сможет синхронизировать эти сообщения без посторонней помощи. Именно здесь в игру вступает объединительная плата SignalR. Звучит просто, но мне понадобилось несколько часов, чтобы рвать на себе волосы, прежде чем я понял корреляцию между 50% отказом и двумя случаями в масштабе.

Всего за пару строк кода и пару щелчков мыши на портале Azure я смог развернуть объединительную панель SignalR на Redis, чтобы решить эту проблему, пройти тесты и развернуть код.

Создание трансляций

Пользователи FStop хотели иметь возможность отправлять «кастинги» другим пользователям. Это будет работать следующим образом:

  1. Джону, фотографу, пришлось укомплектовать съемку, для которой нужны были две модели бледно-блондинки.
  2. Джон открывает приложение FStop и соответствующим образом настраивает фильтр поиска: белый, высокий, блондин, в пределах 30 миль от центра Лос-Анджелеса.
  3. Джон нажимает значок «Трансляция» и составляет свое сообщение. Он выбирает количество пользователей, которым нужно отправить сообщение (не более 50), и нажимает «Отправить». Затем он получает подтверждение того, что эта трансляция скоро будет рассмотрена и одобрена сотрудником FStop.
  4. Сотрудник FStop видит очередь ожидающих трансляций и при необходимости одобряет/отклоняет.
  5. После утверждения кастинг отправляется 50 моделям, которые соответствуют критериям поиска Джона. Трансляция также размещена на FStop Feed.

Это простой набор требований, но было интересно выяснить, как упростить его в масштабе и по низкой цене. Общая архитектура выглядит так:

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

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

Очистка кеша SPA

FStop был создан как одностраничное приложение с использованием DurandalJS. Он связан и минимизирован как две полезные нагрузки — биты spa и биты поставщика:

Это стало возможным благодаря готовой комплектации ASP.NET. Но что, если Джон загрузит FStop.fm, а я запущу обновление? У него будут устаревшие биты, если он не обновится. Я решил эту проблему, добавив заголовок «BuildVersion» к своим ответам:

Имея эту информацию, клиенту просто нужно кэшировать свою первоначальную версию при загрузке, а затем проверять заголовок BuildVersion в последующих ответах сервера, чтобы определить, следует ли пользователю запрашивать перезагрузку приложения:

if (!warned){
  var newVersionNumber = response.getResponseHeader("BuildVersion");
  if (newVersionNumber != cachedVersionNumber){
    doSomething();
  }
}

Близкий поиск

Учитывая тысячу пользователей с местоположениями, сохраненными в виде координат широты/долготы, как вы собираетесь возвращать результаты для тех, кто хочет видеть пользователей в радиусе 50 миль от их координат?

Быстро вычислить близость на лету сложно, особенно с десятками или сотнями тысяч пользователей.

Шаг 1. Сохраните их местоположение как широту/долготу

Вам нужно получить в свои руки координаты широты / долготы. Я делаю это на клиенте — мобильные приложения получают это от GPS или почтовых индексов, введенных вручную; веб-приложения получают его через сторонний API-вызов API геокодирования Google.

Шаг 2. Посмотрите, как это здорово

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

var dapperQuery = $"SELECT {userColumns} FROM [USER] WHERE {filterCriteria}";
dapperQuery = $"dapperQuery AND " + 
   "([LocationLong] > @longMin and [LocationLong] < @longMax and [LocationLat] > @latMin and [LocationLat] < @latMax) AND " + // FIRST LINE
   "((geography::STPointFromText([LocationPoint], 4326).STDistance(@userPoint)) <= @searchRadius)"; // SECOND LINE

Это была одна из проблем, которые убили пользовательский опыт, когда я увеличил количество пользователей с 500 до 5 тысяч в день, когда FStop начал освещаться в прессе. Такая упаковка запроса привела к значительной коррекции производительности.

Денормализация лайков

FStop Feed позволяет пользователям «лайкать» сообщения:

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

Выполнение Join между Posts и Likes, скорее всего, замедлит работу страницы ленты, особенно когда обрабатываются тысячи сообщений и лайков. Поэтому я решил денормализовать эти биты и хранить Likes прямо в таблице Posts в виде UserIdsUpvoted. Это подскажет мне, сколько лайков у любого поста, а также пользователей, которым он понравился. На мой взгляд, денормализация модели предметной области — это прекрасно, если это делается во имя производительности.

Спасибо за чтение!

Мик