Firestore - как структурировать фид и следовать системе

Я использовал базу данных Firebase в реальном времени для своего тестового приложения в социальных сетях, в котором вы можете просто следить за людьми и следить за ними. Традиционная социальная сеть. Я структурировал свою базу данных как this-

Users
--USER_ID_1
----name
----email
--USER_ID_2
----name
----email

Posts
--POST_ID_1
----image
----userid
----date
--POST_ID_2
----image
----userid
----date

Timeline
--User_ID_1
----POST_ID_2
------date
----POST_ID_1
------date

У меня также есть другой узел "Содержимое", в котором содержится только идентификатор всех сообщений пользователя. Итак, если "A" следует за "B", чем весь идентификатор сообщения B, добавленный к временной шкале A. И если B отправил что-то, чем это было добавлено ко всей его временной шкале.

Теперь это было моим решением для базы данных реального времени, но оно явно имеет некоторые проблемы с масштабируемостью

  • если у кого-то есть 10 000 последователей, чем новая запись была добавлена ко всей временной шкале из 10 000 человек.
  • Если у кого-то есть много сообщений, чем каждый новый последователь получил все эти должности на своей временной шкале.

Это были некоторые из проблем.

Теперь я думаю переложить все это на firestore, поскольку его заявили "Масштабируемым". Итак, как я должен структурировать свою базу данных, чтобы проблемы, с которыми я столкнулся в базе данных реального времени, можно было устранить в firestore.

Ответ 1

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

Я имею в виду схему, в которой есть три коллекции верхнего уровня для users, users that a user is following и posts:

Firestore-root
   |
   --- users (collection)
   |     |
   |     --- uid (documents)
   |          |
   |          --- name: "User Name"
   |          |
   |          --- email: "[email protected]"
   |
   --- following (collection)
   |      |
   |      --- uid (document)
   |           |
   |           --- userFollowing (collection)
   |                 |
   |                 --- uid (documents)
   |                 |
   |                 --- uid (documents)
   |
   --- posts (collection)
         |
         --- uid (documents)
              |
              --- userPosts (collection)
                    |
                    --- postId (documents)
                    |     |
                    |     --- title: "Post Title"
                    |     |
                    |     --- date: September 03, 2018 at 6:16:58 PM UTC+3
                    |
                    --- postId (documents)
                          |
                          --- title: "Post Title"
                          |
                          --- date: September 03, 2018 at 6:16:58 PM UTC+3

если у кого-то есть 10000 подписчиков, то на все 10000 подписчиков добавляется новая запись.

Это не будет проблемой, потому что именно по этой причине коллекции хранятся в Firestore. Согласно официальной документации моделирования базы данных Cloud Firestore:

Облачный Firestore оптимизирован для хранения больших коллекций небольших документов.

По этой причине я добавил userFollowing как коллекцию, а не как простой объект/карту, которая может содержать другие объекты. Помните, что максимальный размер документа в соответствии с официальной документацией относительно лимитов и квот составляет 1 MiB (1,048,576 bytes). В случае сбора, нет никаких ограничений в отношении количества документов под коллекцией. Фактически, для такого рода структур оптимизирован Firestore.

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

Как вы можете видеть, база данных в значительной степени денормализована, что позволяет очень просто запрашивать ее. Давайте рассмотрим пример, но перед тем, как создать соединение с базой данных и получить uid пользователя, используя следующие строки кода:

FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();

Если вы хотите запросить базу данных, чтобы получить всех пользователей, за которыми следит пользователь, вы можете использовать вызов get() по следующей ссылке:

CollectionReference userFollowingRef = rootRef.collection("following/" + uid + "/userFollowing");

Таким образом, вы можете получить все объекты пользователя, за которыми следит пользователь. Имея их UID, вы можете просто получить все их сообщения.

Допустим, вы хотите, чтобы на вашем графике были последние три сообщения каждого пользователя. Ключом к решению этой проблемы при использовании очень больших наборов данных является загрузка данных небольшими порциями. В своем ответе из этого поста я объяснил рекомендованный способ разбиения запросов на страницы путем объединения курсоров запросов с помощью метода limit(). Я также рекомендую вам взглянуть на это видео для лучшего понимания. Таким образом, чтобы получить последние три сообщения от каждого пользователя, вы должны рассмотреть возможность использования этого решения. Итак, сначала вам нужно получить первые 15 пользовательских объектов, за которыми вы следите, а затем на основе их uid, чтобы получить их последние три сообщения. Чтобы получить последние три сообщения одного пользователя, используйте следующий запрос:

Query query = rootRef.collection("posts/" + uid + "/userPosts").orderBy("date", Query.Direction.DESCENDING)).limit(3);

По мере прокрутки вниз загружайте другие 15 пользовательских объектов и получайте их последние три сообщения и так далее. Помимо date вы также можете добавить другие свойства к вашему объекту post, такие как количество лайков, комментариев, публикаций и так далее.

Если у кого-то большое количество постов, то каждый новый подписчик получал все эти посты на своей временной шкале.

Ни за что. Нет необходимости делать что-то подобное. Я уже объяснил выше, почему.

Изменить 20 мая 2019 года:

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

Так что, если мы возьмем пример, скажем, Facebook, вам нужно будет иметь документ, содержащий ленту Facebook для каждого пользователя. Однако, если имеется слишком много данных, которые может содержать один документ (1 Mib), вам необходимо поместить эти данные в коллекцию, как описано выше.

Ответ 2

Я думаю переложить все это на firestore

Хорошее децинирование. Зачем?


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

  • Он тратит впустую различные виды ресурсов в очень огромной сумме. К ним относятся квота данных, память в облаке, системная память и мощность процессора.
  • Вопросы, связанные с организацией заседаний в хронологическом порядке.

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


Редизайн базы данных

Избегайте дублирования данных полностью. Вот хороший пример структуры базы данных для социальных сетей.

-root
    -users
        -0001
            -name:"name"
            -profile_image:"https://www.example.com/profileimages/profileimage"
            -followings:"002, 003"
            -posts
                -0001
                    -timestamp:"1535650853"
                    -title:"title"
                    -content: "This is a dummy content"
                    -media: "https://www.example.com/medias/media"
                -0002
                    -timestamp:"1535650853"
                    -title:"title"
                    -content: "This is a dummy content"
                    -media: "https://www.example.com/medias/media"
        -0002
            -name:"name"
            -profile_image:"https://www.example.com/profileimages/profileimage"
            -posts
                -0001
                    -timestamp:"1535650853"
                    -title:"title"
                    -content: "This is a dummy content"
                    -media: "https://www.example.com/medias/media"
        -0003
            -name:"name"
            -profile_image:"https://www.example.com/profileimages/profileimage"
            -followings:"001"


Как получить сообщения

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

step 1 : Get a list of UIDs of all following users
step 2 : Take first UID
step 3 : Get all post with the UID and add to list of posts
step 4 : If next UID exists do step 3 with it
step 5 : Sort all according to the timestamp

Если вам нужна разбивка на страницы, вы можете пойти на решение даже для осложнений. Очевидно, что это приводит к значительному повышению производительности.

step 1 : Get a list of UIDs of all following users
step 2 : Take first UID
step 3 : Get the latest post with the UID (using orderByChild(), limitToLast()) and add to a priority queue in appropriate position.If no element exists, skip the step.
         (A priority queue means an array in of elements which is about to be added to the resultant array. It should be sorted in such a way that the first element can be the next element in the resultant array.)
step 4 : If next UID exists do step 3 with it. Other wise, it means One cycle completed. Go to next step in that case.
step 5 : If limit is not exceeded, get the top element from the queue and add it to resultant array. Then remove from the priority queue. Stop otherwise.
step 6 : Get the next element from the array and add to the priority queue in appropriate position. If no element exists, skip the step.
step 7 : Go to step 5

Ответ 3

Я просмотрел некоторые из документации Firebase, и я смущен тем, почему предлагаемая реализация на https://firebase.google.com/docs/database/android/structure-data#fanout не будет работать в вашем случае, Что-то вроде этого:

users
--userid(somedude)
---name
---etc
---leaders: 
----someotherdude
----someotherotherdude

leaders:
--userid(someotherdude)
---datelastupdated
---followers
----somedude
----thatotherdude
---posts
----postid

posts
--postid
---date
---image
---contentid

postcontent
--contentid
---content

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

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

Ответ 4

Там есть две ситуации

  1. Пользователи в вашем приложении имеют небольшое количество подписчиков.

  2. Пользователи в вашем приложении имеют большое количество подписчиков. Если мы собираемся хранить целых последователей в одном массиве в одном документе в FireStore. Тогда он достигнет лимита пожарного хранилища в 1 MiB за документ.


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

    И используйте запрос, указанный ниже, чтобы получить сообщения

    postCollectionRef.whereArrayContains("followers", userUid).orderBy("date");
    
  2. Во второй ситуации вам просто нужно разбить пользователя на следующий документ на основе размера или количества массивов подписчиков. После достижения размера массива до фиксированного размера следующий идентификатор подписчика должен быть добавлен в следующий документ. И первый документ должен содержать поле hasNext, в котором хранится логическое значение. При добавлении нового сообщения вы должны дублировать почтовый документ, и каждый документ состоит из списка подписчиков, который разрывается ранее. И мы можем сделать тот же запрос, который приведен выше, для получения документов.

Ответ 5

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

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

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

Когда пользователь B тоже хочет получить все сообщения людей, за которыми они whereArrayContains("followers", currentUser.uid), я просто whereArrayContains("followers", currentUser.uid) в запрос простой whereArrayContains("followers", currentUser.uid).

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

На основании:

  • 1 МБ на документ, который по поиску Google, который я сделал, кажется, содержит 1 048 576 символов.
  • Тот факт, что Firestore генерирует UID, кажется, имеет длину около 28 символов.
  • Остальная информация в объекте не занимает слишком много места.

Этот подход должен работать для пользователей, у которых есть приблизительно 37 000 подписчиков.

Ответ 6

Вам нужно поддерживать отношения между последователями:

Followers
-leading_id
-follower_id
-created_at

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

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