Пользователи LinguaLeo начинают изучать английский язык в Джунглях — хранилище тысяч материалов разного уровня сложности, формата и тематик; шаг за шагом учатся слышать и понимать речь носителей языка и расширять словарный запас. Кому нужна грамматика — идут в Курсы. Словарный запас пополняется не только из Джунглей, при добавлении незнакомых слов в Личный словарь, но и с помощью подготовленных Наборов слов, доступных для индивидуального изучения. В разделе Общение можно вести Диалоги на английском, чтобы практиковать язык с другими пользователями LinguaLeo в режиме real-time, выбирая темы для общения. Общение только на английском!
Для создания Диалогов на английском мы использовали Node.js, DynamoDB (все на AWS). Сейчас поделимся нашим опытом.
█ Почему Node.js?
«… Чтобы обеспечить нашим пользователям все возможности для общения, мы выбрали технологию Node.js. Почему? Node.js адекватен для работы со stream'ами, где существует огромное количество модулей. Кроме того, грамотный JavaScript-программист, работая на Node, сможет реализовать весь объём задачи, а не только её серверную часть (в отличие, например, от специалиста по Erlang).
В качестве «транспортного узла» между сервером и клиентом мы использовали WebSockets как наиболее быстрый способ доставки информации.
Для решения проблемы кросс-платформенности нами была выбрана библиотека Sock.JS. Во-первых, из-за соответствия стандартам W3C для WS, а во-вторых, Sock.JS отлично подходит для наших потребностей.
Итак, поехали. Демон Node.js мы запустили на «облачных» мощностях Amazon EC2. Деплоится приложение через git. За логирование и перезапуск отвечает модуль forever. К слову, у Amazon'а есть очень полезный сервис — Amazon Cloudwatch. Его функционал позволяет мониторить основные параметры системы, а главное его достоинство — настраиваемые нотификации, которые позволяют следить только за тем, что действительно важно. Для получения детальной информации о состоянии приложения мы используем nodetime.
В качестве драйвера для работы с DynamoDB используется модуль dynode. Он хорошо себя зарекомендовал, однако несовершенная техническая документация добавила дёгтя в бочку мёда. Официальный Node.js SDK от Amazon'а вышел позднее и всё ещё находится в стадии Developer Preview. Таким вот естественным путём образом было принято решение использовать продукты от Amazon'а, ибо они отвечают всем техническим требованиям.
█ Тонкости работы Node.js
Особое внимание мы уделили проблеме авторизации пользователя на сервере диалогов. У нас за авторизацию на сервере отвечают cookie. Это весьма «дорогое» решение с точки зрения скорости работы, но оно подкупает стабильностью и безопасностью, плюс независимой сервис-ориентированной архитектурой.
Как это работает? Cookie пользователя поступают через WebSockets на сервер диалогов → сервер диалогов проверяет валидность cookie, отправляя запрос с полученной кукой к нашему API, а оно уже отдаёт данные пользователя обратно. Если данные получены без ошибок, сервер «Диалогов» делает своё дело (авторизует) и отсылает данные на пользователя.
Мы знаем, что многие занимаются на LinguaLeo не только дома, но и на работе. Часто случается, что корпоративные фаерволы настроены чрезмерно строго, из-за чего все порты, кроме стандартных, бывают недоступны. Мы помним об этом, и для WebSocket-соединения используем порт 443 (без привязки к https). Это решение позволяет избегать возможных сетевых проблем.
█ Особенности DynamoDB
Чуть больше года назад Amazon выпустил распределённую NoSQL базу данных — Amazon DynamoDB. На этапе проектирования системной архитектуры перед нами стоял выбор между двумя продуктами: MongoDB и DynamoDB. От первого варианта мы отказались из-за сложностей с администрированием, и выбор пал на Amazon, так как поддержки в случае с его продуктом не требовалось. Ну и, конечно, самим было интересно «обкатать» технологию для дальнейшего использования.
Оказалось, работа с DynamoDB сильно отличается от всего того, что мы привыкли видеть. Являясь SaaS-продуктом, эта штука научила нас принимать в расчёт время на http-запрос. В пределах датацентра Amazon'а среднее время запроса составляет около 20 миллисекунд, из-за которых мы пришли к выводу: крайне желательно все выборки делать только через индексы (это быстрее и дешевле), а scan-запросы использовать исключительно для аналитики или миграций.
█ Структура данных
Dialogs — хранение метаданных диалога, кэширование последнего сообщения.
UserDialogs — список диалогов пользователя, кэширование + счетчик непрочитанных сообщений.
Messages — все сообщения пользователей.
User2User — участники диалога.
Пример 1
Процесс получения всех диалогов пользователя для страницы «Мои диалоги на английском» реализован нестандартно. На странице пользователь видит все свои диалоги, в том числе общее количество непрочитанных сообщений, а также последнее сообщение каждого диалога.
Данные выбираются за два запроса. Первый – это выборка всех dialogld из таблицы UserDialogs с помощью Query. Второй – это получение диалогов посредством BatchGetItem. Тут есть небольшой нюанс — BatchGetItem за один раз выбирает максимум 100 сообщений. Поэтому, если их у пользователя 242 штуки, нам потребуется сделать 3 запроса, что занимает около 70-100 миллисекунд. Помня об этом и стремясь к большей оптимизации производительности, мы кэшируем данные диалогов в ElasticCache, обновляя его при каждом новом сообщении.
Пример 2
Добавление нового сообщения в диалог тоже происходит нетривиально. Скорость базы данных нам не очень важна, поскольку мы не дожидаемся записи в DynamoDB. Нам ведь нужно провести большое количество записей, а это играет значительную роль для быстроты последующей выборки. Сначала мы записываем новое сообщение в таблицу Messages PutItem, а после кэшируем в таблице Dialogs UpdateItem. Дальше нам нужно заинкрементить messageCounter в таблице UserDialogs для каждого пользователя (UpdateItem). Всякое ведь бывает, вдруг один из пользователей решил удалить диалог и сбросил нам счетчик сообщений. В сумме = 4 запроса, по времени собираются около 70-100 миллисекунд. К сожалению, такие транзакции не поддерживаются в DynamoDB, что накладывает ощутимые ограничения для процессов, где сохранность данных критично важна.
А вообще, изменение требований к продукту — явление довольно частое. Иногда это влечёт за собой трансформацию структуры данных. В реляционных базах данных это решается с помощью ALTER TABLE, а вот в DynamoDB этой штуки просто нет. Изменение схемы тут обходится очень дорого. Нам приходится пересоздавать таблицы или использовать Elastic MapReduce. За оба варианта приходится платить. Много данных = много денег. Чтобы с этим как-то справиться, пришлось выбирать все данные Scan'ом по 1 мегабайту, а после записывать в новую таблицу. Отнимает очень много времени, но это плата за отсутствие DBA.
█ Впечатления от DynamoDB
Наш эксперимент по использованию DynamoDB удался. Это потрясающая база данных, которая отличается простотой масштабирования. Работаете с ней и забываете про администрирование. Но помните: взамен она требует аккуратного обращения на этапе проработки архитектуры. Иначе возможны всякие там неожиданные повороты и неприятные грабли. Мы рекомендуем использовать DynamoDB, если некритичных данных много, а работать с ними надо часто. Мы пользуемся — нам нравится» :)
***
Общайтесь легко и непринуждённо в Диалогах на английском — практикуйте английский язык в приятной компании и присоединяйтесь к нашей команде!
Следите за новостями на Facebook, Вконтакте и в Twitter, делитесь впечатлениями и получайте удовольствие. Свобода общения — это здорово!
Команда LinguaLeo
Автор: LinguaLeo