Якуб Фийалковски

Firestore (и Firebase) - действительно отличное решение для множества различных случаев использования. Как и все, что делает так много, это очень и очень быстро усложняется, даже если на первый взгляд кажется простым.

Вот мои личные фавориты, которые могут вас сильно укусить, основываясь на более чем 10 мобильных приложениях во Flutter и React Native, а также на аудитах кода, которые мы провели в LeanCode.

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

Посты в серии:

  1. Почему Firestore, часть I: причины полюбить
  2. Почему Firestore, часть II: причины ненавидеть [вы сейчас читаете].
  3. Почему Firestore, часть III: 6 вещей, которые нужно знать перед использованием Firestore.
  4. Почему Firestore, часть IV: как избежать этого [будет опубликовано около 9 декабря].

Хотите узнать из нашего прошлого опыта, что может пойти не так с Firestore и Firebase и каковы их самые большие недостатки и ограничения? Прочтите, чтобы узнать почему!

Безопасность и проверка данных

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

А это плохая идея.

Прежде всего, серверные системы выполняют проверку и авторизацию. В большинстве случаев это отдельные проблемы. Firestore, с другой стороны, рассматривает их как равные и предлагает вам одно и то же решение для обоих. По сути, вам нужно написать правила авторизации, которые иногда выполняют проверку. Вы должны сделать это на собственном языке, который чем-то похож на JavaScript, но не является таковым. Обычно я бы сказал, что использование собственного языка - хорошая идея. Не здесь. Эти вещи слишком сложны, чтобы их можно было обрабатывать с помощью языка, который может выполнять только простые сравнения, извлекать связанные документы по идентификатору или выполнять простой запрос. И сделайте вид, что у вас есть функции. Вы не можете выразить ничего значимого и оставаться в здравом уме. Добавление валидации в микс на самом деле не помогает (так что в большинстве случаев валидации нет вообще!).

Конечно, есть случаи, когда это не проблема. Например, вы можете «легко» защитить себя от злоумышленников, просто игнорируя / очищая искаженные данные на клиенте. Бывают случаи, когда вам действительно не нужна никакая авторизация, кроме простой «эти пользователи могут только писать эти документы и читать из них». Я бы даже сказал, что большинство проектов изначально попадает в эту категорию. Затем возникает сложность, и вы оказываетесь глубоко в нарушенных правилах Firestore с кодом проверки повсюду в вашем приложении. И никакой безопасности.

Цены, расчеты использования и индексы

Ой ой. Я думаю, это то, что заставило меня (и, ну… моих клиентов) кричать от боли. Цены Firestore выглядят нормально - вы платите за вход / выход, хранилище и операции. Это понятно. Это затрудняет мониторинг расходов. А точнее - его отсутствие.

Firestore действительно не дает вам возможности проверить, сколько вы используете. Вы можете видеть, сколько операций вы уже использовали, но когда дело доходит до хранилища и входа / выхода, вы в значительной степени предоставлены сами себе. Firestore не дает вам там ничего значимого. Все, что у вас есть, - это одно сообщение «используемое хранилище» в вашем счете GCP. Он не говорит вам, сколько данных у вас на самом деле или сколько новых данных вы создаете, он только говорит вам, сколько они выставили вам счет. Ничего больше. Вы можете попытаться извлечь из него изменения, но это далеко не «точно».

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

Что считается под термином используемое хранилище? Ну все. Документы, коллекции (то есть пути, поскольку коллекция на самом деле не имеет значения, когда мы говорим о пространстве для хранения), индексы, вы называете это. Вы платите за каждый байт, который вы создаете, и за каждый байт, который Firestore создает для вас. И это создает много.

По умолчанию Firestore индексирует все поля в ваших документах. Все. Вы должны явно отключить индексы, и вы можете только создать 200 правил исключения (по состоянию на 21–09–2020). Это делает чрезвычайно важным тщательное моделирование данных, поскольку один неверный индекс может привести к огромному количеству неиспользуемых данных. Я сам виноват, что не заметил этого. В Activy, где мы используем Firestore для синхронизации действий и используем для синхронизации рейтингов, мы сгенерировали почти 24 ГБ индексов на каждый 1 ГБ данных. Мы не использовали ничего из этого.

Итак, когда дело доходит до ценообразования, вы должны быть действительно, действительно, действительно осторожны, даже в простых случаях. Как я уже сказал - достаточно одного злоумышленника, чтобы испортить ваши данные.

Задержка - это сложно

Google не дает никаких обещаний относительно задержки. Одно это может быть ключом к отказу от Firestore в качестве вашей базы данных. Без каких-либо гарантий вы не сможете хорошо разработать свой продукт. Даже если время будет высоким, но вы знаете, вы сможете обойти это. Вы можете скрыть задержку, запустив запрос раньше в процессе или, например, просто сделав это полностью в фоновом режиме. Это увеличило бы сложность (чего Firestore пытается избежать), но это выполнимо. Без известного RTT (время приема-передачи, время задержки 2) вы можете только измерить и надеяться, что оно будет согласованным.

И измерения не так хороши. Более одной секунды для небольшого запроса - это действительно долгий срок. Это в основном совпадает с нашими тестами в Activy, которая довольно широко использует Firestore. Работает примерно так же, как в статье, а именно:

  1. Один клиент загружает документ,
  2. Остальные клиенты получают уведомление и обрабатывают документ,
  3. Затем он устанавливает маркер «обработка завершена»,
  4. Первый клиент получает маркер «обработка завершена» с уведомлением об изменении документа.

Простая загрузка документа (по известному пути) в Activy занимает более 300 мс. Ожидание обработки и отправки уведомления занимает еще пару сотен мс (в нашем случае ~ 200 мс). Все это дает в лучшем случае 1 с.

Если сравнивать это с простым сервером WebSockets, работающим на самом маленьком экземпляре GCP (согласно статье), Firestore выглядит плохо. Даже если вы добавите обработку сообщений, небольшую базу данных и т. Д., Вы не получите больше, чем, скажем, 500 мс RTT в самых маленьких возможных случаях.

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

Запросы действительно просты

Firestore, хотя он в некоторой степени мощный, довольно ограничен по сравнению с традиционными базами данных (будь то СУБД или другая база данных документов). Комбинация базовых запросов с подходом индексирование всех по умолчанию дает вам отличную отправную точку, но вам необходимо заранее смоделировать свои данные для возможности поиска. Существует ряд ограничений, которые затрудняют использование Firestore. Некоторые из них (например, пределы OR или array-contains / array-contains-any) не так уж неудобны, но первое ограничение , а именно то, что вы можете выполнять запросы по диапазону только для одного поля, ужасно раздражает. Довольно распространено получить мне все транзакции из этого диапазона дат, которые оцениваются не ниже X, и это единственное правило запрещает это. Кроме того, Firestore не поддерживает отрицательные запросы (например, not-in или старый добрый! =), Что делает обычные запросы непредставимыми.

Базы данных документов, как правило, имеют ограниченные возможности обработки, то есть вы не можете вычислять значения на основе результатов запроса непосредственно в базе данных, например, SQL, и они не разрешают соединения. Чтобы решить эту проблему, Google представил подход MapReduce, который несколько смягчает эту проблему. MapReduce стал де-факто стандартом для баз данных документов (даже MongoDB поддерживает его!). К сожалению, в Firestore ничего подобного нет. Вы можете реализовать это самостоятельно, используя так называемые запросы агрегирования, но это решение действительно далеко от совершенства. У вас есть контроль над процессом, но Firestore не может оптимизировать ничего из этого. Фактически вы должны выполнять оптимистично-параллельные транзакции только для того, чтобы обновить коллекцию, которая работает как индекс уменьшения карты. Это может сработать для простых случаев, когда ваши исходные коллекции не изменяются часто, но если есть некоторая нагрузка, ваши повторные попытки съедят всю вашу производительность.

Firestore затрудняет нормальную работу с данными

Вы разрабатываете версию 1 своего мобильного или веб-приложения с помощью Firestore. Все отлично. Все правильно синхронизируется, все работает быстро, разработка доставила удовольствие. Ваша база пользователей становится больше, вы становитесь знаменитыми, и деньги текут. Чтобы сделать вас еще более богатым, вы начинаете думать о новых функциях. И вы решаете их реализовать.

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

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

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

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

Вы будете дублировать свои данные много раз

Поскольку количество запросов Firestore ограничено и нет map-reduce, одна модель данных не справится со всеми случаями. Вы либо загрузите гораздо больше данных, чем нужно, и будете делать все в памяти, либо скопируете данные. Вы можете использовать Облачные функции здесь, чтобы сделать это автоматически, но вам нужно будет самостоятельно обрабатывать все действия CRUD.

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

Если вы выберете дублирование данных (и выполните сопоставление самостоятельно), вы получите несколько отдельных копий одних и тех же данных, только структурированных по-разному. Это не только увеличивает стоимость хранения, но и значительно увеличивает сложность. Теперь у вас есть не только один документ для версии, но и несколько связанных документов, которые необходимо обновлять с осторожностью (и, возможно, атомарно, что может еще больше усложнить процесс).

Резервные копии

Этот абзац будет коротким.

В Firestore нет резервных копий.

Вы можете экспортировать данные в корзину GCS, но на самом деле это не резервная копия, а просто экспорт. Firestore не обеспечивает согласованности, резервное копирование в основном выполняется вручную (вы можете написать сценарий, но вам придется написать его самостоятельно), сроки непредсказуемы.

Это просто не резерв.

Резюме

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

В следующей статье из нашей серии мы расскажем, как правильно использовать преимущества Firestore и Firebase (которые описаны здесь) и какие шаги следует учитывать, если вы уже попали в описанные выше ловушки.

Перейдите по ссылке, чтобы прочитать Почему Firestore? 6 вещей, которые нужно знать перед использованием Firestore.