Logux: Connection lost, data synchronized – интервью с Андреем Ситником (Злые Марсиане)

    Что происходит, если вдруг на клиенте пропадает интернет? Возможно, на долю секунды, а может быть, на более ощутимый период? Все мы как пользователи сталкиваемся с нестабильным сигналом, плавающим качеством связи. Иногда это неважно, ведь хочется посмотреть какое-то весёлое видео, иногда от этого может зависеть очень многое – представьте, что вам срочно надо купить билет на поезд или оплатить тот же самый интернет.

    Те сферы, где очень важен конечный пользователь – например, СМИ, говорят, что уже 13% пользователей уходят, если ваш сайт открывается больше четырёх секунд, не разбираясь в причинах. А теперь давайте представим такого пользователя, который еще пробует отправить комментарий, и он постоянно «отваливается» из-за проблем со связью?

    Процент уходов и отказов будет заведомо больше. Как этого избежать? Что можно сделать в ситуации, когда данные должны быть гарантированно отправлены как от клиента, так и со стороны сервера?

    На этот и другие вопросы отвечает Андрей Ситник – автор PostCSS и Автопрефиксера, ведущий фронтендер в «Злых Марсианах».









    – Почему мы вообще говорим о проблемах связи? Разве это не вопрос, исключительно связанный с физическими/сетевыми возможностями?

    – OSI тут не совсем в тему. Logux заменяет REST и AJAX. То есть это чисто прикладной уровень. Logux решает следующие проблемы:

    1. Сейчас требуется много кода для простых запросов.
    2. Живое обновление данных с сервера писать на порядок сложнее.
    3. Хорошую поддержку оффлайн вообще ад написать. Но оффлайн нужен всегда, так как у нас всегда есть «500 мс оффлайн» — эти постоянные крутилки на кнопках.

    Для этого Logux как бы создаёт особый виртуальный канал событий между клиентом и сервером.

    Клиент может положить в этот канал события — Logux сам их отправит, когда можно будет. И то же самое на сервер. Давай разберём это на техническом уровне пошагово:

    Разбор полетов


    1. На клиенте это будет библиотека с API один-в-один как у Redux (к Реакт не привязана, есть и более низкоуровневый API). Ты точно так же создаёшь action. Но у некоторых action можешь выставить ключ sync: true — тогда Logux доставит их сам на сервер. Эта библиотека держит веб-сокет, посылает пинг, проверяя, что связь есть.

    – Так, а чем хорошо то, что эта библиотека не привязана к Реакту?

    Не все разрабатывают на Реакте (и это очень хорошо для разнообразия среды). Кто-то может использовать Vue.js, Angular. Или просто иметь JS-приложение, где нет HTML, так что Реакт будет не так нужен. Идём дальше:

    2. Дополнительная библиотека, которая при потере связи покажет спец. бейджик «Нет связи» вверху страницы и изменит фавикон. Если в канале на отправку окажутся события, а связи не будет — она тоже скажет пользователю, что не все данные сохранились.

    – Зачем пользователю это знать? Для какого формата взаимодействия это может быть критически важным?

    – Если пользователь передал нам какие-то данные, то мы обязаны его уведомить, что они не сохранились. Я бы сказал, что это поведение должно быть по умолчанию. Библиотека опциональна во многом, чтобы написать самому такое уведомление и лучше встроить его в дизайн. Хотя есть случаи, когда можно не показывать проблемы со связью — например, если Logux используется для сбора действий пользователя для аналитики.

    Следующий шаг.

    3. Когда связь появится, Logux-клиент и сервер отправят друг другу те события, которые добавились за время отсутствия связи.

    – Понятно, логично, сколько действий может быть в очереди?

    – Обычно событий 10-20. Но у нас нет жёстких ограничений — сколько поместится в памяти клиента и сервера.

    – Порядок выполнения этих событий?

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

    – Не будет ли так, что какие-то события уже не надо выполнять? Взаимная проверка зависимости действий?

    – Клиент может чистить лог от уже ненужных событий. Это уже определяется бизнес-логикой.

    Следующий шаг: пользователь может закрыть браузер — неотправленные события сохранятся в localStorage. Но перед попыткой закрытия библиотека попросит пользователя сначала выйти в интернет, так как есть неотправленные данные.

    – Опять возвращаемся к вопросу ёмкости и важности, и что произойдёт, если пользователь принудительно закроет браузер?

    – Ничего страшного. Как только пользователь снова вернётся на сайт — данные сами отправятся.

    Теперь все оставшиеся шаги:

    5. Для написания Logux-сервера есть специальный серверный фреймворк. То есть это как express.js. Ты сам описываешь, как сервер реагирует на то или иное событие с клиента. При этом логика чуть хитрее — то, что событие пришло сейчас, не значит, что оно было создано сейчас. Поэтому у каждого события есть время создания. При соединении клиент и сервер определяют разницу времени и синхронизируют часы (и время создания старых событий).

    6. Фреймворк для сервера пока на node.js. Потом сделаем для Elixir и Go. Но можно использовать и Ruby, Python и PHP — просто поставить node.js сервер как прокси, чтобы держать сокет. А уже этот прокси-сервер будет посылать старый REST-запрос в ruby-сервер.

    7. Поскольку у нас есть этот лог событий и точное время, поверх него можно сделать CRDT как в Swarm.js — и автоматически разрешать конфликты во многих простых случаях.

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

    Logux и альтернативы


    – Спасибо, а чем Logux тогда лучше уже существующих решений?

    – Есть Relay/GraphQL — они сокращают количество кода при запросе данных. Но изменение самих данных тут тоже реализовано не очень просто. Живое обновление решено плохо. Оффлайн тоже не проработан.

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

    Есть ещё CRDT-библиотеки — например, Swarm.js. Logux копирует много идей у Swarm.js. Но Swarm.js сложно подружить с Реактом и Редаксом. Также сложно передавать в нём не-CRDT операции.

    – В чём сильные и слабые стороны?

    – Меньше кода, живое обновление данных из коробки, легко сделать приложение для работы в оффлайн. И всё это со знакомой семантикой Редакса.

    Главная слабая сторона — надо запускать дополнительный сервер. Но это есть у всех таких решений. Плюс этот сервер можно сделать прокси-сервером и всю логику продолжать хранить в PHP или Ruby on Rails.

    – Откуда вообще появилась потребность в этом выделенном решении?

    – На Амплифере нужно было показывать живую статистику публикаций и обновлять страницу с ошибками сразу же при появлении проблемы. И мы поняли, что хорошего решения не нашлось. Попробовали внедрить Swarm.js, но легко развернуть его не получилось. Так что я начал думать, как подружить Swarm.js и Редакс. В Питере в это время как раз был Дэн Абрамов, и в разговоре с ним родилась идея отдельной библиотеки.

    – Имеет ли это шансы на то, чтобы стать индустриальным стандартом?

    – Я стараюсь сделать Logux, как универсальное решение для всех веб-приложений. Но, мне кажется, в 2017 мы увидим много ещё попыток переделать AJAX/REST — сейчас это главная проблема веб-разработки, на мой взгляд. Кто победит в этой борьбе, узнаем только в 2018.

    Примеры из жизни


    – Есть ли 1-2 интересных практических кейса?

    – Да, даже TODO MVC — вам же хочется продолжить работать со списком задач даже, если связи нет. Или если вы добавили задание на компьютере, то хотите, чтобы оно появилось тут же и на телефоне, без перезагрузки страниц.

    Или комментарии — живое обновление, как в Фейсбуке или ВК, полезно для вовлечения людей.

    Есть ещё одно преимущество Logux для любого сайта — это «оптимистичный интерфейс» (о нём как раз рассказывал другой спикер HolyJS). Сейчас при каждом действии мы показываем пользователю «крутилку». Каждое сохранение блокирует интерфейс и нарушает поток пользователя. С помощью Logux на порядок проще делать оптимистичный интерфейс, где сохранение будет происходить в фоне, отвлекая пользователя, только если ошибка действительно произошла. Например, так работает Google Inbox. Любое веб-приложение выиграет от оптимистичного интерфейса, так как пользователи всегда любят быстрые интерфейсы.

    – Большое спасибо за интервью, и до встречи на конференции.



    Конечно, в интервью удалось обсудить только общий взгляд на библиотеку и ее философию, на HolyJS Андрей выступит с подробным часовым докладом о разработке и работе с Logux. Кроме того, можно будет сразу посмотреть доклады, посвященные и другим обсуждаемым технологиям:
    JUG.ru Group 1 151,88
    Конференции для взрослых. Java, .NET, JS и др. 18+
    Поделиться публикацией
    Комментарии 13
    • +1
      А как Logus подружить с Service Workers?

      Так как SW и служат для решения проблемы оффлайна, то логично всю логику Логуса исполнять там.
      • +1
        Есть две разные задачи в поддержке оффлайна:

        1. Кеширование HTML/JS/CSS-файлов приложение.
        2. Организация работы с данными приложения без связи.

        Эти задачи очень разные. Сервис-воркеры созданы для решения только первой задачи. Для решения второй задачи они не созданы.

        Можно конечно перенести работу с логом в сервис-воркер, но это только усложнит код и уменьшит поддержку браузеров.
        • 0
          Если запросы к API идут по HTTP(s), то в принципе можно кешировать их на уровне Service Workers.
          Когда связи нет, то выполнять запросы отложенно а клиенту возращать, например, что все ок.
          Как бонус — это прозрачно для приложения. Из минусов — не получится перехватить WebSockets, ну и меньше поддержка браузером
          • 0
            Да, так можно — но смысла мало. На клиенте всё равно больше кода для AJAX-запроса. Непонятно как понятно и прозрачно интегрировать получение таких же событий с сервера.

            Плюс запуск и обновление сервис-воркера — хитрая задача. В спеке есть не очень очевидные моменты (например, с адресом воркер-файла).

            Ну и преимуществ не будет никаких (в фоне воркер сокет не может держать).
      • +4
        Каким образом гарантируется доставка данных от клиента серверу и наоборот? Как бороться с повторной отправкой данных в случае, если сломались клиент, сервер или интернет между ними? Через идемпотентные операции?
        • +2
          Можно сказать, что и идемпотентные. Каждое событие имеет уникальный ID (он же время создания). Когда событие добавляется в лог, то проверяется, нет ли уже такого ID. Так что всё проблема повторной доставки сильно упрощается.
          • +1
            ну да, это и есть идемпотентность в чистом виде. Просто синтетическая — за счет синтетического ID события.
        • +1
          Я правильно понимаю, что Logux работает на WebSocket?
          Это не создаёт проблем? Старые брауззеры, прокси?
          • 0
            Прокси большая проблема для обычных веб-сокетов, но если перейти на сокеты поверх SSL (WSS), то большинство проблем решится. Мы стараемся везде в интерфейсе форсить WSS.

            Сокеты — IE 10+, это нормально. Логакс ориентирован на SPA, а для IE <10 SPA создают редко.

            Но, сам Логакс не ограничивает как передаются данные. Просто стандартный драйвер использует веб-сокеты. Можно заменить на AJAX + keep-alive. Или внедрить протокол Логакса внутрь какого-то нибудь корпоративного XML.
          • +1
            Может задам тупой вопрос, но почему бы в БД и на клиенте не хранить дату последнего изменения каждого поля?
            При синхронизации сверять даты и обновлять поля с датой меньше присланной. Побеждает тот, кто последний менял данные.
            • 0
              Правильно мыслите — собственно в CRDT примерно так и сделано.

              Но разделение по полям не идеальный способ. Есть типы данных, которые так мержить не получится — например, счётчики или длинный текст (где хочется параллельное редактирование). Для этого и придумали более абстрактный способ — хранить действие.

              Но сборщик мусора в логе и так удаляет все старые значения поля (если это CRDT Map). Так что хранится очень близко к вашей идей. Просто чуть универсальнее для других типов.
              • 0
                Кстати, как хранить конечные данные на сервере решаете вы. Так что даты полей можете не хранить, а использовать обычное хранение. Или хранить именно с датами, чтобы мерж работал, даже если клиента не было онлайн более месяца.

                В общем Логакс никак не ограничивает БД на сервер. Свой лог он хранит в отдельном файле и этот лог лишь дополнительная вещь. Его можно смело чистить через неделю.
              • 0

                Не нашёл ничегошеньки про SSR (Server Side Rendering). Изоморфный logux-client, или на сервере юзать redux?

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

                Самое читаемое