[Перевод] Эта база данных в огне…

pn-h4xssrkuzk7v6agvtsmyuvzg.jpeg
Позвольте мне рассказать техническую историю.

Много лет назад я разрабатывал приложение со встроенными в него функциями совместной работы. Это был удобный экспериментальный стек, в котором использовался полный потенциал раннего React и CouchDB. Он в реальном времени синхронизировал данные по JSON OT. Его использовали во внутренней работе компании, однако широкая применимость и потенциал в других сферах были очевидными.

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

Two users interacting through a mobile app


На самом деле это и стало проблемой. Наше демо работало именно так, как имитировали работу своих приложений все остальные. Если конкретно, то информация мгновенно передавалась от А к Б, даже если это большие медиафайлы. После входа в систему каждый пользователь видел новые записи. При помощи приложения разные пользователи могли совместно работать чётко над одними и теми же проектами, даже в случае прерывающегося Интернет-подключения где-нибудь в деревне. В неявной форме подобное подразумевается в любом нарезанном в After Effects видео о продукте.

Несмотря на то, что все знали, для чего нужна кнопка Refresh, никто совершенно не понимал, что веб-приложения, которые они просят нас создавать, обычно подвержены своим ограничениям. И что если они больше не понадобятся, то опыт пользователя будет совершенно иным. В основном они замечали, что можно «чатиться», оставляя собеседникам заметки, поэтому задавались вопросом, чем это отличается, например от Slack. Уф-ф-ф!

Дизайн повседневных синхронизаций


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

Классическим примером этого является пользователь, в течение двадцати минут смотрящий на spinner.gif, гадая, когда же наконец завершится работа. Разработчик бы понял, что процесс, вероятно, завис, и что gif никогда не пропадёт с экрана. Эта анимация имитирует выполнение работы, но не связана с её состоянием. В подобных случаях некоторые технари любят закатывать глаза, поражаясь степени заблуждения пользователей. Однако заметьте, кто из них указывает на вращающиеся часы и говорит, что они на самом деле стоят неподвижно?

An animated activity spinner


В этом и состоит суть ценности реального времени. В наши дни базы данных реального времени по-прежнему используются крайне мало, и многие относятся к ним с подозрением. Большинство таких баз данных активно склоняется к стилю NoSQL, из-за чего обычно используют решения на основе Mongo, о которых лучше забыть. Однако для меня это означает комфорт работы с CouchDB, а также изучение проектирования структур, которые будет способен заполнять данными не только какой-нибудь бюрократ. Думаю, что я трачу своё время оптимальнее.

Но настоящая тема этого поста — то, что я использую сегодня. Не по своему выбору, а из-за равнодушно и слепо применяемой корпоративной политики. Поэтому я приведу Совершенно Честное и Беспристрастное сравнение двух тесно связанных продуктов для работы с базами данных реального времени Google.

75czjdjwypwtyu_ajlidpik0bjm.png


В названиях обоих есть слово Fire. Одно я вспоминаю с нежностью. Второе для меня — другой вид огня. Я не тороплюсь говорить их названия, потому что как только я это сделаю, мы столкнёмся с первой большой проблемой — именами.

Первый называется Firebase Real-Time Database, а второй — Firebase Cloud Firestore. Оба они являются продуктами из Firebase suite Google. Их API называются, соответственно, firebase.database(…) и firebase.firestore(…).

Так получилось потому, что Real-Time Database — это просто исходный Firebase до его покупки Google в 2014 году. Затем в Google решили в качестве параллельного продукта создать копию Firebase на основе big data компании, и назвали её Firestore with a cloud. Надеюсь, вы ещё не запутались. Если всё-таки запутались, не волнуйтесь, я сам переписывал эту часть статьи десять раз.

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

Если бы существовала награда за самый худший нейминг программных продуктов, то этот случай определённо стал бы одним из претендентов. Расстояние Хэмминга между этими названиями настолько мало, что запутывает даже опытных инженеров, чьи пальцы печатают одно название, хотя голова думает о другом. Это с треском провалившиеся планы, придуманные с самыми благими намерениями; они исполнили пророчество о том, что база данных будет в огне. И я ничуть не шучу. Человек, придумавший такую схему наименований, стал причиной крови, пота и слёз.

aba0cede3fb97d12fb8cca93e766833e.jpg


Пиррова победа


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

Однако беглый взгляд на два продукта может сбить вас с толку: кажется, что они делают одно и тоже, через в основном одинаковые API и даже в одной и той же сессии баз данных. Различия малозаметны и обнаруживаются только при тщательном сравнительном изучении пространной документации. Или когда пытаешься портировать идеально работающий на Firebase код, чтобы он работал с Firestore. Уже тогда ты выясняешь, что интерфейс базы данных загорается, как только пытаешься выполнить перетаскивание мышью в реальном времени. Повторюсь, я не шучу.

Клиент Firebase вежлив в том смысле, что он буферизует изменения и выполняет автоматический повтор попыток обновления, при которых приоритет отдаётся последней операции записи. Однако Firestore имеет ограничение в 1 операцию записи на документ на пользователя в секунду, и это ограничение налагается сервером. При работе с ним вы сами должны найти способ обойти его и реализовать ограничитель частоты обновлений, даже когда вы просто пытаетесь создать своё приложение. То есть Firestore — это база данных реального времени без клиента реального времени, которая маскируется под него с помощью API.

На этом мы начинаем видеть первые признаки смысла существования Firestore. Возможно, я ошибаюсь, но подозреваю, что кто-то высоко в руководстве Google посмотрел после покупки на Firebase и просто сказал: «Нет, боже мой, нет. Это неприемлемо. Только не под моим руководством».

5df23e9f2cdd044086611c04e75d5adc.jpg


Он явился из своих покоев и провозгласил:

«Один большой документ JSON? Нет. Вы разделите данные на отдельные документы, каждый из которых будет размером не более 1 мегабайта».

Похоже, что такое ограничение не переживёт первого столкновения с любой достаточно мотивированной базой пользователей. Вы знаете, что это так. У нас на работе, например, есть полторы с лишним тысяч презентаций, и это Совершенно Нормально.

При таком ограничении вы будете вынуждены смириться с тем фактом, что один «документ» в базе данных не будет похож ни на один объект, который пользователь мог бы назвать документом.

«Массивы массивов, которые могут рекурсивно содержать другие элементы? Нет. Массивы будут содержать только объекты или числа фиксированной длины, как задумано Господом».

Поэтому если вы надеялись поместить в свою Firestore GeoJSON, то обнаружите, что это невозможно. Недопустимо ничего неодномерного. Надеюсь, вы любите Base64 и/или JSON внутри JSON.

«Импорт и экспорт JSON по HTTP, инструменты командной строки или панель администратора? Нет. Вы сможете только экспортировать и импортировать данные в Google Cloud Storage. Так, кажется, оно сейчас называется. И когда я говорю «вы», то обращаюсь только к тем, кто имеет полномочия Project Owner. Все остальные могут пойти и создать тикеты.»

Как видите, модель данных FireBase описать легко. Она содержит один огромный документ JSON, связывающий ключи JSON с путями URL. Если вы запишете при помощи HTTP PUT в / FireBase следующее:

{
  "hello": "world"
}


То GET /hello вернёт "world". В основном это работает именно так, как вы ожидаете. Коллекция объектов FireBase /my-collection/:id эквивалентна словарю JSON {"my-collection": {...}} в корне, содержимое которого доступно в /my-collection:

{
  "id1": {...object},
  "id2": {...object},
  "id3": {...object},
  // ...
}


Это работает отлично, если каждая вставка имеет ID без коллизий, для чего в системе есть стандартное решение.

Другими словами, база данных на 100% совместима с JSON (*) и отлично работает с HTTP, например, с CouchDB. Но в основном вы используете её через API реального времени, который абстрагирует websockets, авторизацию и подписки. Панель администратора имеет обе возможности, позволяя и выполнять редактирование в реальном времени, и импорт/экспорт JSON. Если в своём коде вы будете придерживаться того же, то удивитесь, какой объём специализированного кода пропадёт, когда вы поймёте, что patch и diff JSON позволяют решить 90% рутинных задач по обработке постоянного состояния.

Модель данных Firestore похожа на JSON, но отличается от него в некоторых критически важных аспектах. Я уже упоминал отсутствие массивов внутри массивов. Модель sub-collections заключается в том, чтобы они были концепциями первого класса, отдельными от содержащего их документа JSON. Поскольку для этого не существует готовой сериализации, для получения и записи данных требуется специализированный путь выполнения кода. Для обработки собственных коллекций необходимо писать свои скрипты и инструменты. Панель администратора позволяет вам вносить только небольшие изменения по одному полю за раз, и не имеет возможностей импорта/экспорта.

Они взяли базу данных NoSQL реального времени и превратили её в медленную не-SQL с автообъединением и отдельным столбцом не-JSON. Что-то в духе GraftQL.

d16e19f8d3456d548285e1d5a330d87b.jpg


Горячий Java


Если Firestore должен был стать более надёжным и масштабируемым, то ирония в том, что среднестатистический разработчик получит менее надёжное решение, чем при выборе FireBase «из коробки». То ПО, которое необходимо Ворчливому Администратору Базы Данных, требует такого уровня усилий и калибра специалистов, что это просто нереалистично для ниши, в которой, предположительно, должен быть хорош продукт. Это похоже на то, как HTML5 Canvas совсем не является заменой Flash, если нет инструментов разработки и проигрывателя. Более того, Firestore погрязла в стремлении к чистоте данных и стерильной валидации, что просто не соответствует тому, как средний бизнес-пользователь любит работать: для него всё необязательно, потому что до самого конца всё является черновиком.

Основной недостаток FireBase в том, что клиент был создан на несколько лет раньше своего срока, ещё до того, как большинство веб-разработчиков узнало об иммутабельности. Из-за этого FireBase предполагает, что вы будете изменять данные, а потому не использует преимущества обеспечиваемой пользователем иммутабельности. Кроме того, он не использует данные повторно в передаваемых пользователю снэпшотах, из-за чего выполнять diff намного сложнее. Для крупных документов его механизм транзакций на основе изменяемых diff просто неадекватен. Ребята, у нас ведь уже есть WeakMap в JavaScript. Это удобно.

Если придать данным нужную форму, и не делать деревья слишком объёмными, то эту проблему можно обойти. Но мне любопытно, стал бы FireBase гораздо интереснее, если бы разработчики выпустили по-настоящему хороший клиентский API, использующий иммутабельность в сочетании с серьёзным практическим советом по структуре баз данных. Вместо этого они, похоже, попытались починить то, что не сломано, и от этого стало хуже.

Я не знаю всей логики, лежавшей в основе создания Firestore. Рассуждения о мотивах, возникающих внутри чёрного ящика — это тоже часть развлечения. Такое противопоставление двух чрезвычайно похожих, но несравнимых баз данных встречается довольно редко. Как будто кто-то подумал: «Firebase — это просто функция, которую мы можем эмулировать в Google Cloud», но при этом ещё не открыл для себя концепцию определения требований реального мира или создания полезных решений, удовлетворяющих всем этим требованиям. «Пусть об этом думают разработчики. Просто сделайте UI красивым… А можно добавить больше огня?»

Я понимаю пару вещей о структурах данных. Я точно вижу, что концепция «всё в одном большом дереве JSON» — это попытка абстрагировать из базы данных любое ощущение крупномасштабной структуры. Ожидать, что ПО просто справится с любым сомнительным фракталом структуры данных — это просто безумие. Мне не нужно даже представлять, насколько плохо всё может быть, я проводил жёсткие аудиты кода и видел такое, что вам, людям, и не снилось. Но я также знаю, как выглядят хорошие структуры, как их использовать и почему это нужно делать. Я могу представить мир, в котором Firestore казалась бы вполне логичной, а создавшие её люди считали бы, что проделали хорошую работу. Но мы живём не в этом мире.

Поддержка построения запросов в FireBase плоха по любым стандартам, её практически не существует. Она определённо требует улучшения или хотя бы пересмотра. Но Firestore ненамного лучше, поскольку она ограничена теми же одномерными индексами, которые есть в простой SQL. Если вам нужны запросы, которые люди выполняют с хаотичными данными, то требуется полнотекстовый поиск, фильтры на несколько диапазонов и произвольный задаваемый пользователем порядок. При внимательном изучении функции простого SQL сами по себе слишком ограничены. Кроме того, единственные SQL-запросы, которые люди могут выполнять в продакшене — это быстрые запросы. Вам понадобится специализированное решение для индексирования с продуманными структурами данных. Для всего остального, по крайней мере, должно быть инкрементное map-reduce или что-то подобное.

Если вы поищите информацию об этом в документах Google, то вам, надеюсь, укажут в направлении чего-то типа BigTable и BigQuery. Однако все эти решения сопровождаются таким объёмом густого жаргона корпоративных продаж, что вы быстро вернётесь назад и начнёте искать что-то другое.

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

(*) Это шутка, нет такого понятия, как совместимость с JSON на 100%.


На правах рекламы


Подыскиваете VDS для отладки проектов, сервер для разработки и размещения? Вы точно наш клиент :) Посуточная тарификация серверов самых различных конфигураций, антиDDoS и лицензии Windows уже включены в стоимость.

8p3vz47nluspfyc0axlkx88gdua.png

© Habrahabr.ru