Holy Grail на стероидах: тотальная синхронизация и изоморфный JavaScript на Swarm.js

в 8:16, , рубрики: javascript, sync, synchronization, Блог компании Swarm, разработка

Сегодня на Хабре мы представляем технологию реплицированной модели, которая позволяет создавать коллаборативные и реалтаймовые веб приложения так же легко, как локальные десктопные. Мы считаем, что при разработке приложений, синхронизация данных в реальном времени должна быть доступна так же, как TCP поток, HTTP запрос или ток из розетки — сразу и без вопросов. HTML5 приложения, написанные на Swarm, в части автономности, локальности и скорости загрузки не уступают нативным.
Используя библиотеку Swarm, за выходные мы делаем больше, чем за месяц делали без Swarm. Что важней — можем делать то, что без неё делать вообще не могли. Эту библиотеку синхронизации мы предлагаем совершенно бесплатно.

Holy Grail на стероидах: тотальная синхронизация и изоморфный JavaScript на Swarm.js

Сегодня мы выкладываем TodoMVC++, реактивное HolyGrail-на-стероидах приложение, написанное на Swarm+React. Приведу список демонстрируемых в приложении возможностей:

  • Мгновенная загрузка — страница отрисовывается на сервере и приходит на клиента, как сжатый HTML; затем, подтягиваются код и данные, и страница оживляется. Isomorphic JavaScript в действии.
  • Кэширование данных в WebStorage — позволяет как убыстрить загрузку, так и работать в оффлайне, не теряя результатов работы.
  • Оффлайновая работа — с данными уже понятно, а если добавляем cache manifest, то HTML5 приложение может загружаться и работать без интернета!
  • Синхронизация в реальном времени — откройте несколько закладок (синхронизация через WebStorage) или откройте ту же страницу на телефоне/айпэде/другом браузере (WebSocket).
  • Сложносинхронизируемые структуры данных (да-да, именно сло-жно-син-хро-ни-зи-ру-е-мы-е).

В целом, приложение написано без каких-либо отсылок к сети, как простое (локальное) MVC приложение. Синхронизация и кеширование полностью происходит на уровне библиотеки Swarm, а приложение работает с локальными Backbone-подобными объектами модели.

Итак, вот само приложение: ppyr.us.
Вот код: github.com/gritzko/todomvc-swarm

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

Сегодня, синхронизация весьма востребована и на клиенте и на сервере. В серверном хозяйстве интернет-гигантов появляется всё больше различных хранилищ и средств обработки данных, которые требуют синхронизации. Принцип одной большой БД, как источника истины – трудномасштабируем. Но мы говорим, в первую очередь, о клиенте. Сегодня, рядовой пользователь обзавёлся множеством устройств. Когда данные, которые он видит на экране ноутбука, не соответствуют данным на экране айфона – пользователь огорчается. Синхронизация реплик нужна везде, и мы считаем, что система синхронизации данных (СД) скоро будет таким же айтишным ширпотребом, как БД.

Специфика текущего момента в том, что даже лидеры индустрии допинали свои решения по синхронизации лишь до стадии «типа работает». GDocs не вполне работает в оффлайне, GTalk и Skype систематически теряют реплики из истории чатов, Evernote славится самыми разнообразными багами. И это — лидеры. Вообще, проблема синхронизации на удивление сложна и многогранна. Возьмём Evernote. Если бы Evernote был локальным приложением, его 80/20 подмножество мог бы написать студент. Так же, как располагая MySQL и PHP, Цукерберг написал facebook.

В чём же фундаментальная сложность синхронизации? Давайте поймём, как работают классические технологии реплицирования и синхронизации, как они поддерживают идентичность реплик. Самый простой подход — это делать всё через центр. Все операции записи стекаются в центральную БД, обратно из центра идут новые результаты операций чтения. Вроде бы, надёжно и просто, но скоро сложности начинают надвигаться с трёх сторон.

  1. concurrency — пока шёл ответ на прежнюю операцию, клиент успел сделать что-то ещё, и как это теперь совместить — уже не вполне понятно,
  2. масштабирование схемы, где все операции проходят через одну точку,
  3. функционирование при плохом интернете, когда центр не отвечает клиентам или отвечает медленно.

Первый типичный шаг масштабирования этой схемы — это репликация по принципу master-slave — так, как она реализована в типичной БД. Мастер укладывает операции в линейный лог и раздаёт этот лог slave'ам, которые прикладывают его к своим репликам данных, в том же линейном порядке, и получают тот же результат. Это помогает масштабировать чтение, но добавляет элемент eventual consistency, т.к. slave'ы обновляются с некоторым лагом. Проблема записи остаётся — все записи идут через тот же центр. Линеаризацию можно растянуть на распределённые реплики с помощью consensus algorithm, типа Paxos или Raft, но и там линеаризацию делает «лидер», т.е. всё тот же центр. Когда центр всё-таки затыкается, приступают к горизонтальному масштабированию — распиливают базу на «шарды». Тут уже линеаризация, а с ней и весь ACID, рвётся на тысячу маленьких ACID'иков.

Ну и с оффлайновой работой центр и линеаризация трудносовместимы. Можно, конечно, сказать, что оффлайна «скоро не будет», но факт в том, что оффлайн случается и случается регулярно. Если мы что-то твитим или лайкаем, это можно потерпеть, а если что-то посерьёзней, то вряд ли. Если, например, официант ногой задел провод и интернет пропал, то мы не можем выгнать клиентов из ресторана, пока админ не приедет на машине с мигалками, и обслуживать бесплатно тоже не можем (пример от Макса Нальского, сооснователя IIKO).

Причём, все эти приключения на стороне сервера ещё никак не затрагивают сторону клиента. Клиент просто ждёт, пока сервера всё согласуют между собой и сообщат результат. В небезызвестном проекте Meteor пытались сделать синхронизацию клиентов в реальном времени, фактически кэшируя MongoDB на клиента. Чтобы всё работало живенько, ожидание ответа сервера замаскировали трюком «latency compensation». Клиент накатывает операцию на свой кэш, отправляет её серверу, сервер отвечает, успешно ли приложилось, а если нет — высылает исправление. Подход более чем сомнительный. «-Люся, ты машину в гараж поставила? -Да, частично!»

Вот такая сложная история с линеаризацией. Что ж, тем интересней посмотреть на популярные решения, которые на линеаризацию забили. Есть два хороших примера — Git и CouchDB. Git написал Линус Торвальдс, который среди разработчиков Linux был тем самым «центром». Вероятно, поэтому он хорошо прочувствовал, что центр медленный, центр не масштабируется. В git, синхронизация происходит по принципу master-master. Данные представляются, как орграф версий, все параллельные версии нужно когда-то смёржить. Масштабирование — идеальное, оффлайн — без проблем. Примерно то же и в CouchDB. Есть попытки вынесения CouchDB-подобной логики на клиента — pouchdb и hood.ie.

Что-то совсем новенькое в этой области — это CRDT, и о нём сегодня и речь, извините за долгое вступление. CRDT – это Convergent/Commutative/Cloud Replicated Data Types. Общий замысел CRDT — в использовании частичного порядка вместо линеаризации. Операции могут происходить параллельно на многих репликах, и некоторые операции конкурентны — т.е. произошли на разных репликах, не зная друг о друге, ни одна из них не «первая», и на разных репликах они прикладываются в разном порядке. Если используемые структуры данных выдерживают такое лёгкое переупорядочение операций, не нарушающее причинно-следственных связей, то все связанные с центром проблемы просто испаряются.

Другой вопрос — а много ли таких CRDT структур данных? Как оказалось, весь вычислительный ширпотреб — переменные, массивы, ассоциативные массивы — вполне себе реализуются в виде CRDT. А если мы деньги считаем? Тогда-то точно нужна линеаризация и гарантии ACID? Увы и ах, тут оказалось, что новое — это хорошо забытое старое. Обнаружилось, что используемые в бухгалтерии структуры данных — счета, балансы — вполне себе CRDT. Ведь в период Возрождения, когда сформировались традиции бухгалтерии, интернета не было, вот они и выкрутились без линеаризации.

Большая сияющая возможность CRDT — это живые реплики, полностью работоспособные даже в отсутствие соединения с центром. Ну и немедленное приложение всех операций, без пробежки до центра. Такая автономность и быстрота особенно актуальна в двух случаях. Во-первых, для мобильных устройств — их используют на ходу, при ненадёжном интернете. CRDT позволяет запасать данные впрок, и спокойно работать локально, с фоновой синхронизацией. Во-вторых, для приложений с функцией совместной работы, особенно — в реальном времени (думаем про Google Docs, Apple iCloud). В таких приложениях «состояние» большое и быстро меняется, и каждая пробежка до сервера и обратно — гвоздь в гроб.

Есть и не-CRDT технологии, позволяющие работать с данными в оффлайне. Своё API синхронизации предлагает Dropbox, есть StrongLoop, Firebase итд итп, их море. Все эти решения работают по принципу Last-Write-Wins (LWW) — каждой записи присваивается временная метка, запись с большей меткой перетирает прежние. На том же принципе построена Cassandra. И в нашей библиотеке Swarm, самый ходовой примитив — это LWW-объект. Преимущество Swarm — это те структуры данных, которые через LWW не решаются. Например, текст при одновременном редактировании.

Вообще, в Зазеркалье распределённых систем всё наоборот. В обычных языках программирования, самая простая операция — это инкремент переменной, ++. Чуть сложнее работа с массивами, ещё сложней — объекты и ассоциативные коллекции. В распределённых системах, всё с точностью до наоборот! LWW объекты и ассоциативные контейнеры не представляют особой сложности. Линейные структуры (массивы, текст) весьма сложны, а счётчики сложны чрезвычайно. Это можно видеть на примере Cassandra, где LWW объекты сделали в первую очередь, а счётчики, вроде бы, всё ещё допиливают.

Holy Grail на стероидах: тотальная синхронизация и изоморфный JavaScript на Swarm.js

Ближе к делу. Мы решили написать TodoMVC на Swarm+React, чтобы показать библиотеку в действии. Собственно, первый TodoMVC на Swarm+React был написан в июле Андреем Поппом меньше, чем за день, но тот код не был «идиоматическим». В этот раз, мы добавили линейные коллекции (Vector), серверный рендер и кучу всяких вкусностей. Более того, обычный TodoMVC показался нам скушноват и бесполезен. Например, глядя на React+flux TodoMVC, очень сложно понять, зачем авторы всё эти хитрости намутили в простейшем приложении. Поэтому, мы добавили одну фичу — рекурсивность. По нажатию Tab, пользователь переходит во вложенный «дочерний» список. Также, мы адаптировали интерфейс под реалтаймовую синхронизацию. Такое приложение уже представляет хоть какую-то практическую пользу. Также, стали показывать состояние приложения в URL — для лёгкого шаринга между пользователями. Вообще, нам было трудно остановиться. По сравнению с разработкой реалтаймовых проектов в прошлом, в лице Swarm мы имели этакий меч-кладенец, и всё время руки чесались укокошить кого-нибудь ещё.

Подробный разбор приложения и библиотеки — в следующем материале.

Следите за апдейтами в твиттере проекта @swarm_js.

Автор: gritzko

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js