피드 및 팔로우 시스템을 구성하는 방법은 무엇입니까?
저는 소셜 네트워크 앱을 위해 파이어베이스 실시간 데이터베이스를 사용하고 있었습니다. 당신이 팔로우하는 사람들의 게시물을 팔로우하고 받을 수 있기 때문입니다.
내 데이터베이스:
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
다른 노드 "콘텐츠"에 모든 사용자 게시물의 ID가 포함되어 있습니다."A"가 "B"를 따르는 경우 B의 모든 게시물 ID가 A의 타임라인에 추가됩니다.그리고 만약 B가 무언가를 게시했다면 그것은 또한 모든 팔로워의 타임라인에 추가됩니다.
확장성 문제가 있습니다.
- 팔로워가 10,000명인 경우 팔로워의 모든 10,000명 타임라인에 새 게시물이 추가되었습니다.
- 만약 누군가가 많은 양의 게시물을 가지고 있다면, 모든 새로운 팔로워들은 그의 타임라인에 있는 모든 게시물을 받습니다.
확장 가능하다고 해서 파이어스토어로 변경하고 싶습니다.Firestore에서 실시간 데이터베이스 문제를 제거하기 위해 데이터베이스를 어떻게 구성해야 합니까?
당신의 질문을 조금 후에 봤지만, 제가 생각할 수 있는 최고의 데이터베이스 구조도 제공하도록 노력하겠습니다.이 대답이 유용하기를 바랍니다.
는 3개의 users
,users that a user is following
그리고.posts
:
Firestore-root
|
--- users (collection)
| |
| --- uid (documents)
| |
| --- name: "User Name"
| |
| --- email: "email@email.com"
|
--- 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
새로운 게시물보다 팔로워가 10,000명 더 많은 사람이 10,000명의 모든 팔로워 타임라인업로드 타임라인에 추가되었습니다.
이것이 컬렉션이 Firestore에 있는 이유이기 때문에 그것은 전혀 문제가 되지 않을 것입니다.Cloud Firestore 데이터베이스 모델링 공식 문서에 따르면 다음과 같습니다.
Cloud Firestore는 많은 양의 작은 문서를 저장하는 데 최적화되어 있습니다.
가 이이제추이유다니입한을 추가한 입니다.userFollowing
다른 개체를 저장할 수 있는 단순한 개체/맵이 아니라 컬렉션으로 사용할 수 있습니다.제한 및 할당량과 관련된 공식 문서에 따른 문서의 최대 크기는 다음과 같습니다.1 MiB (1,048,576 bytes)
컬렉션의 경우 컬렉션 아래에 있는 문서 수에 대한 제한은 없습니다.실제로 이러한 종류의 구조를 위해 Firestore는 최적화되어 있습니다.
이렇게 10,000명의 팔로워를 갖는 것은 완벽하게 잘 작동할 것입니다.또한 데이터베이스를 어디에도 복사할 필요가 없는 방식으로 쿼리할 수 있습니다.
보시다시피 데이터베이스는 매우 간단하게 쿼리할 수 있도록 거의 정규화되지 않았습니다.몇 가지 예를 들어 보겠습니다. 데이터베이스에 대한 연결을 생성하여uid
다음 줄의 코드를 사용하는 사용자:
FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
가 팔로우하는 " " " " " " 를 할 수 .get()
다음 참조 번호로 문의하십시오.
CollectionReference userFollowingRef = rootRef.collection("following/" + uid + "/userFollowing");
따라서 이러한 방식으로 사용자가 팔로우하는 모든 사용자 개체를 가져올 수 있습니다.그들의 uid를 가지면 당신은 간단히 그들의 모든 게시물을 얻을 수 있습니다.
모든 사용자의 최신 게시물 세 개를 타임라인에 올리고 싶다고 가정해 보겠습니다.매우 큰 데이터 세트를 사용할 때 이 문제를 해결하는 핵심은 데이터를 더 작은 청크로 로드하는 것입니다.저는 이 게시물의 답변에서 쿼리 커서를 다음과 결합하여 쿼리를 페이지로 이동할 수 있는 권장 방법을 설명했습니다.limit()
방법.더 잘 이해하기 위해 이 동영상을 보는 것도 추천합니다.따라서 모든 사용자의 최신 게시물 3개를 얻으려면 이 솔루션을 사용하는 것을 고려해야 합니다.따라서 먼저 추적 중인 처음 15개의 사용자 개체를 얻은 다음 이를 기반으로 해야 합니다.uid
그들의 최근 세 개의 게시물을 얻기 위해.단일 사용자의 최신 게시물 세 개를 얻으려면 다음 쿼리를 사용하십시오.
Query query = rootRef.collection("posts/" + uid + "/userPosts").orderBy("date", Query.Direction.DESCENDING)).limit(3);
아래로 스크롤할 때 다른 15개의 사용자 객체를 로드하고 최신 게시물 3개 등을 가져옵니다. date
다속성추수있습다니도가에 .post
개체(예: 좋아요, 댓글, 공유 등)를 입력합니다.
만약 누군가가 모든 새로운 팔로워보다 많은 양의 게시물을 가지고 있다면, 그의 타임라인에 있는 모든 게시물을 받았습니다.
말도 안 돼요. 이럴 필요 없어요.나는 이미 위에서 그 이유를 설명했습니다.
2019년 5월 20일 편집:
사용자가 팔로우하는 모든 사용자의 최근 게시물을 볼 수 있는 작업을 최적화하는 또 다른 해결책은 사용자가 해당 사용자에 대해 문서에 표시해야 하는 게시물을 저장하는 것입니다.
예를 들어 Facebook의 경우 각 사용자의 Facebook 피드가 포함된 문서가 필요합니다.그러나 단일 문서에 저장할 수 있는 데이터가 너무 많은 경우(1 Mib) 위에서 설명한 대로 해당 데이터를 집합에 넣어야 합니다.
두 가지 상황이 있습니다.
앱의 사용자는 팔로워 수가 적습니다.
당신의 앱에 있는 사용자들은 팔로워 수가 많습니다.만약 우리가 전체 팔로워를 하나의 배열에 하나의 문서에 저장할 것이라면, 파이어스토어.그러면 그것은 문서당 1MiB의 화재 저장 한도에 도달할 것입니다.
첫 번째 상황에서 각 사용자는 팔로어 목록을 단일 문서에 저장하는 문서를 단일 배열로 보관해야 합니다.을 사용하여
arrayUnion()
그리고.arrayRemove()
팔로워 리스트를 효율적으로 관리하는 것이 가능합니다.그리고 타임라인에 무언가를 게시할 때는 게시 문서에 팔로워 목록을 추가해야 합니다.그리고 아래에 주어진 쿼리를 사용하여 게시물을 가져옵니다.
postCollectionRef.whereArrayContains("followers", userUid).orderBy("date");
두 번째 상황에서는 팔로워 배열의 크기 또는 수에 따라 사용자 후속 문서를 구분하면 됩니다.배열 크기가 고정된 크기에 도달하면 다음 팔로워 ID가 다음 문서에 추가되어야 합니다.그리고 첫 번째 문서는 부울 값을 저장하는 "hasNext" 필드를 유지해야 합니다.새 게시물을 추가할 때 게시물 문서를 복제해야 하며 각 문서는 이전에 끊어진 팔로워 목록으로 구성됩니다.그리고 우리는 문서를 가져오기 위해 위에 주어진 것과 같은 쿼리를 만들 수 있습니다.
네트워크에서 적절한 양의 활동(예: 1,000명을 팔로우하는 사람 또는 1,000개의 게시물을 게시하는 사람)이 있는 경우 다른 답변은 매우 많은 비용이 듭니다.
모든 사용자 문서에 '최근 게시물'이라는 필드를 추가하는 것이 해결책입니다. 이 필드는 배열이 됩니다.
이 만들어질 마다 Write 기능이 그의 Write()를 합니다.recentPosts
사용자의 배열 해당 게시물에 대한 정보를 추가할 수 있습니다.
따라서 최근 게시물 배열 앞에 다음 맵을 추가할 수 있습니다.
{
"postId": xxxxxxxxxxx,
"createdAt": tttttt
}
최근 Posts 배열을 개체 1,000개로 제한하고 제한을 초과할 때 가장 오래된 항목을 삭제합니다.
이제 1,000명의 사용자를 추적하고 피드를 채우려고 합니다.1,000개의 사용자 문서를 모두 가져옵니다.이것은 1k 읽기로 계산됩니다.
문서가 각에는 1,000개의 문서 .recentPosts
클라이언트의 모든 어레이를 하나의 마스터 어레이로 병합하고 생성된 At를 기준으로 정렬합니다.
이제 당신은 잠재적으로 백만 개의 포스트의 의사를 가지고 있습니다.ID는 모두 시간순으로 정렬되어 있으며, 1,000개의 읽기만 가능합니다.이제 사용자가 피드를 스크롤할 때 해당 문서를 문서로 조회하기만 하면 됩니다.필요에 따라 한 번에 10개 정도의 신분증이 필요합니다.
Y X를 Y 팔로어로 할 수 .Y + X
따라서 100명의 팔로워로부터 2,000개의 게시물은 2,100개의 읽기에 불과할 것입니다.
따라서 1,000명의 팔로워로부터 1,000개의 게시물은 2,000개의 읽기에 불과할 것입니다.
타기......
편집 1) 추가 최적화.사용자 문서를 로드할 때 쿼리...를 사용하여 문서를 한 번에 10개씩 배치할 수 있습니다.일반적으로 이것은 배치되어 있음에도 불구하고 여전히 10개의 읽기이기 때문에 차이가 없습니다.그러나 다음과 같은 필드로 필터링할 수도 있습니다.recentPostsLastUpdatedAt
사용자 문서에 대해 캐시된 값보다 큰 값인지 확인하면 최근 Posts 배열을 업데이트하지 않은 사용자 문서는 읽을 수 없습니다.이를 통해 기본 읽기 비용을 이론적으로 10배 절감할 수 있습니다.
편집 2) 각 사용자에게도 청취자를 첨부할 수 있습니다. 최근 게시물이 변경될 때마다 피드를 새로 고쳐야 할 때마다 모든 팔로워에게 문의하지 않고 새 게시물을 받을 수 있습니다. (1,000명 이상의 스냅샷 청취자는 좋지 않은 관행일 수 있지만, 후드 아래에서 어떻게 작동하는지는 모르겠습니다.) (편집 3:Firebase는 프로젝트를 1,000명의 청취자로만 제한하므로 edit2는 확장 가능한 최적화가 아니었습니다.)
저는 그녀가 제안한 해결책에 대해 약간 고심해왔습니다. 대부분 기술적인 차이 때문에, 저에게 맞는 다른 해결책을 찾았습니다.
모든 사용자에 대해 해당 사용자가 팔로우하는 모든 계정뿐만 아니라 해당 사용자를 팔로우하는 모든 계정 목록이 포함된 문서가 있습니다.
앱이 시작되면 현재 사용자를 따르는 계정 목록을 확인할 수 있으며, 사용자가 게시물을 만들 때 게시물 개체의 일부는 해당 사용자를 따르는 모든 사용자의 배열입니다.
사용자 B가 팔로우하는 사람들의 모든 게시물을 얻고 싶을 때, 나는 단지 간단한 질문을 추가합니다.whereArrayContains("followers", currentUser.uid)
.
이 방법을 사용하면 원하는 다른 매개 변수에 따라 결과를 정렬할 수 있기 때문에 좋습니다.
기준:
- 구글 검색 결과 문서당 1mb는 1,048,576자로 보입니다.
- 파이어스토어가 UID를 생성했다는 사실은 28자 정도로 보입니다.
- 개체의 나머지 정보는 크기가 그리 크지 않습니다.
이 접근 방식은 약 37,000명의 팔로워를 가진 사용자에게 효과적입니다.
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만의 문제는 아닌 것 같습니다.
제가 뭔가를 놓치고 있지 않는 한 주요 문제는 타임라인 노드의 존재 자체인 것 같습니다.특정 사용자의 타임라인 보기를 생성하는 것이 더 쉽다는 것은 이해하지만, 이러한 모든 관계를 유지해야 하는 비용으로 인해 프로젝트가 크게 지연되고 있습니다.제출된 사용자를 기반으로 위와 유사한 구조에서 쿼리를 사용하여 타임라인을 즉시 구축하는 것이 너무 비효율적입니까?
좋습니다. 이 문제에 대해 생각한 후에 저는 이론적인 해결책을 생각해냈습니다(아직 테스트하지 않았기 때문입니다).이를 위해 Cloud Firestore를 사용할 예정입니다.
My Solution은 두 부분으로 구분됩니다.
데이터베이스 셰마 설계:
Firestore-root
|
_ _ users (collection):
|
_ _ uid (document):
|
_ _ name: 'Jack'
|
_ _ posts (sub-collection):
|
_ _ postId (document)
|
_ _ feed (sub-collection):
|
_ _ postId (document)
|
_ _ following (sub-collection):
|
_ _ userId (document)
|
_ _ followers (sub-collection):
|
_ _ userId (document)
1.1 설명:
처럼, 저는 여기보바같와다같음이이라는 이름의 .users
데이터베이스의 각 사용자를 나타냅니다. 각uid
에 있는 users
컬렉션에는 다음과 같은 고유한 필드가 있습니다.name
예를 들어 자신의 하위 컬렉션입니다. 각uid
에는 자체적으로 만든 게시물이 포함되어 posts
▁the▁the▁sub▁from-collect에 포함합니다.feed
서브 컬렉션마지막으로 다음을 나타내는 두 개의 하위 컬렉션을 포함합니다.following
그리고.followers
.
클라우드 기능 사용:
const functions = require("firebase-functions");
const firebaseAuth = require("firebase/auth");
const admin = require("firebase-admin");
admin.initializeApp();
const firestore = admin.firestore();
const uid = firebaseAuth.getAuth().currentUser.uid;
exports.addToUserFeed =
functions.firestore.document("/users/{uid}/posts/{postId}").onCreate(async
(snapshot,context) => {
const userId = context.params.uid;
const followers = await firestore.collection('users').doc(userId).collection('followers').where("userId", "==", uid).get();
const isFollowing = !followers.empty;
if (isFollowing == true) {
const docRef =
firestore.collection('users').doc(uid).collection('feed').doc();
const data = snapshot.data();
firestore.runTransaction(transaction => {
transaction.create(docRef, data);
});
}
});
2.1 설명:
가 자신의 컬렉션에 .posts
(현재사용피게드싶추로으므고하가을물시자에▁(▁since현)feed
사용자는 하수집위를 사용하여 했습니다)가 합니다.firebase auth
의 uid
constant된 작성자를 .uid
(는 (으(으)ㄹ 수 있습니다.)를 통해 할 수 context.params.uid
)를 점검은 다음을 수행하여 수행합니다.Query
어떤 것이 있는지 확인하기 위해userId
에 있는 followers
ID 하위컬션이와현사 ID 일니합과 합니다.uid
이는 다음을 반환합니다.QuerySnapshot
그리고 나서 우리는 확인합니다.QuerySnapshot
비어 있는지 여부.비어 있으면 현재 사용자가 다음을 따르지 않음을 의미합니다.context.params.uid
사용자입니다. 그렇지 않으면 따라갑니다., 는 새로 사용자에게 합니다.feed
트랜잭션을 사용한 하위 컬렉션입니다.
좋아요.이것이 누구에게나 도움이 되길 바랍니다.다시 말씀드리지만, 아직 테스트를 하지 않았기 때문에, 뭔가 해결되지 않을 수도 있지만, 그렇게 되기를 바랍니다.감사합니다!
업데이트: 3/7/23
이 게시물은 오래되었습니다.저는 개인적으로 당신이 파이어스토어 기능의 한계에도 불구하고 가능한 대량 복제를 해야 한다고 생각합니다.업데이트된 기사에 가능한 모든 버전을 정리했습니다.
https://code.build/p/GNWh51AdUxUd3B8vEnSMAM/확장성이 뛰어난 팔로어 피드-파이어 스토어 구축
원본 게시물
제 확장 가능한 아이디어는 사용자가 1,000,000명 이상의 팔로워를 가질 수 있지만 실제 사용자는 1,000명 이상의 사람을 팔로우하지 않는다는 것입니다.우리는 단순히 그들의 피드(게시물 모음)를 집계할 수 있었습니다.
컬렉션
/users
/users/{userId}/follows
/users/{userId}/feed
/posts
피드 채우기
채우기 피드는 먼저 실행되어야 하며, 솔직히 클라우드 기능에 있어야 합니다.비용을 피하기 위해 피드에 새 게시물만 표시되고 10일(또는 오래된 게시물은 표시되지 않습니다.
populateFeed()
이런 것들...
numFollowing = get('users/numFollowing');
lastUpdate = get('users/lastUpdate');
tenDaysOld = timestamp 10 days ago
// maybe chunk at 20 here...
for (numFollowing) {
docs = db.collection('posts')
.where('userId', '==', userId)
.where('createdAt', '>', lastUpdate)
.where('createdAt', '<', tenDaysOld);
db.collection('users/${userId}/feed').batch.set(docs);
users/${userId}/lastUpdate
현재 타임스탬프까지.
이렇게 하면 문서가 너무 많이 생성되지 않고(예: 10일이 경과한 문서), 이미 보유한 문서에 대한 읽기가 낭비되지 않습니다.
피드 읽기
피드가 집계된 게시물이 됩니다.
loadFeed()
이것을 다음과 같이 부릅니다.populateFeed()
db.collection('/users/${userId}/feed').orderBy('createdAt');
피드의 문서는 프론트 엔드에서 게시물을 풀 수 있기 때문에 CreatedAtdate 및 postId만 필요합니다. 변경되지 않을 것으로 예상되는 경우 모든 데이터를 저장할 수 있습니다.
postId: {
createdAt: date
}
또한 사용자 문서에는 다음 항목이 있습니다.
{
numFollowing: number,
lastUpdate: date
}
앱이 자동으로 호출해야 합니다.loadFeed()
적재 중인실행되는 버튼이 있을 수 있습니다.populateFeed()
호출 가능한 클라우드 기능(최고) 또는 로컬로 제공됩니다.피드가 관측 가능한 파이어베이스인 경우 피드가 채워지면 자동으로 업데이트됩니다.
이 문제를 해결할 다른 더 깨끗한 방법이 있을 수 있습니다.모든 팔로워에게 쓰기 피드의 게시물에서 필드를 업데이트할 수 있습니다.유일한 제약 조건은 시간이며, 일반적으로 60년대이지만 최대 9분까지 걸릴 수 있습니다.대량 업데이트를 비동기적으로 수행해야 합니다.
여기에서 저의 애드파이어 스토어 기능 패키지를 참조하십시오.
J
가지 은 제생에한가가지능다은성같음컬만것다입니라는 이름의 다른 최상위 합니다."users_following"
다음과 같은 이름의 문서가 포함되어 있습니다."user_id"
사용자가 추적하는 모든 사용자를 포함하는 배열의 필드입니다. 그안서 안에서."users_following"
문서 1은 해당 특정 사용자의 하위 컬렉션을 가질 수 있으며 모든 게시물 또는 최상위 컬렉션도 이 작업을 수행합니다.다음으로 중요한 것은 최근 게시물 하나를 내부에 저장해야 한다는 것입니다."users-following"
배열 또는 지도로 문서화합니다.기본적으로 이 정규화된 데이터는 여러분을 따르는 사람의 피드를 채우는 데 사용될 것입니다.단, 최근 2개의 게시물을 추가했거나 정상적으로 2~3개의 게시물을 저장해도 3개의 게시물이 한 번에 표시되는 것보다 한 사람당 1개의 게시물만 볼 수 있다는 것이 단점입니다(같은 사용자의 3개의 게시물처럼).하지만 사용자당 하나의 게시물을 보여주기만 하면 되는 것은 여전히 좋은 일입니다.
언급URL : https://stackoverflow.com/questions/46979375/how-to-structure-a-feed-and-follow-system
'programing' 카테고리의 다른 글
유형 스크립트에서 값으로 지도를 "필터링"할 수 있습니까? (0) | 2023.06.27 |
---|---|
Vuex 스토어에서 매개 변수를 사용하여 시작할 때 구성 요소에서 데이터 가져오기 (0) | 2023.06.27 |
SEEK_CUR과 함께 제로 오프셋 infseeek() 함수가 사용되는 용도는 무엇입니까? (0) | 2023.06.27 |
1.0 곱셈 정밀도 및 부동 변환으로의 int (0) | 2023.06.07 |
Google Cloud Platform을 통해 Firebase에서 자동 전자 메일 전송(타사 제품 제외) (0) | 2023.06.07 |