[英]Global and friends-only leaderboard ranking using Cloud Firestore
[英]Lowest-Cost Leaderboard system using firebase
我的目標是有效地獲取數據傳輸數量最少的子項列表(有序*和索引**)。
* ordered:按每個用戶/數據庫子項的點數排序
** 索引:在當前用戶 [A specific child] 后面/之后排名 2 或更少(下面進一步詳細說明)
我的數據庫結構如下:-
我基本上想獲得按積分排序的前 3 個用戶(簡單):-
val usersRef = FirebaseDatabase.getInstance(DB_LINK).getReference("users").orderByChild("points")
usersRef.limitToFirst(3).addValueEventListener(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (ds in snapshot.children) {
val points: String = snapshot.child("points").getValue(String::class.java)!!
val firstName: String = snapshot.child("firstName").getValue(String::class.java) ?: ""
val uid: String = snapshot.key!!
// Update View
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
然后,假設當前登錄的用戶不是前三個用戶之一,我想獲得他的排名(根據整個數據庫中的點數排序),他之前的 2 個用戶和他之后的 2 個用戶,而不用查詢整個數據庫(它是一個用戶數據庫,最多可以有 5 萬個唯一用戶),因為查詢整個數據庫是一項非常昂貴的客戶端任務。
我檢查了firebase 數據過濾頁面,但發現根據某個孩子限制結果沒有任何用處。
這個答案不能滿足我的需求,因為它遍歷了整個數據庫(在我的例子中是 50K 條記錄)。 我需要一種有效的方法,因為我需要真正節省這些 firebase 賬單。
而且,我檢查了這個答案,但它不符合我的需求,因為它仍然查詢整個數據庫,這意味着它是無效的,並且會在當前用戶之前為每個節點計費。 (也許他在數據庫中排名第 40,000,所以我不應該每次都查詢整個數據庫以獲得他的排名並為 39,999 次讀取付費)
我搜索了一種以某種方式使用布爾值來過濾查詢的方法,但再次發現沒有任何用處。 這是我無效的代碼:-
// Gets all children.
usersRef.addValueEventListener(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (ds in snapshot.children) {
val points: String = snapshot.child("points").getValue(String::class.java)!!
val firstName: String = snapshot.child("firstName").getValue(String::class.java) ?: ""
val uid: String = snapshot.key!!
// Update View only if user is `2 <= usersRank - theirRank <= -2`
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
我想實現這樣的目標:-(樣式已經完成,邏輯仍然存在)
有沒有辦法做到這一點? 還有其他選擇嗎?
編輯:我發現 firestore 提供的聚合查詢可能有助於解決這種情況。 做更多的研究以進一步縮小成本。
此操作在 Firebase 實時數據庫上不可用。 更好的選擇是 Firestore。
好吧,Fire-Store 數據庫可以為您提供特定查詢中的對象計數。 這是 firebase 添加的新功能。 您基本上鍵入所需的查詢,然后在.get()
.count()
) ; 這樣它只會返回對象的數量。 這稱為聚合查詢。 在這里了解更多關於它們的信息。
使用 Cloud Functions 進行聚合避免了客戶端事務的一些問題,但有一組不同的限制:
現在您正在為這個系統使用COUNT()
,還有一種方法可以幫助進一步降低成本。 那就是定期更新。
誰會關心所有用戶的實時排名? 您可以讓排行榜每分鍾、每小時或每天更新一次。 例如,stack overflow 的排行榜每天更新一次!
這種方法確實適用於任意數量的播放器和任意寫入速率。 但是,隨着您的成長,您可能需要根據您的支付意願調整頻率。
對於每次正常閱讀,您需要支付一次閱讀費用。 很簡單的。 但是,對於一次計數,您需要為 0.001 次讀取付費(意味着 1000 次計數 = 1 次讀取)。 有關成本的更多信息,請查看 firebase 的這篇文章。
為了連接一切,我們現在將把它應用到我們的問題上。 首先,我們需要保持代碼的第一部分不變。 (抓住前 3 個用戶的部分),盡管有一些更改以將其移植到 firebase。
注意:不要忘記設置復合索引,因為我們同時按多個字段排序。
val top3 = HashMap<Int, HashMap<String, String>>()
Firebase.firestore.collection("users").orderBy("points", Query.Direction.DESCENDING)
.orderBy("firstName", Query.Direction.ASCENDING)
.get().addOnSuccessListener {
for ((index, doc) in it.documents.withIndex()) {
val firstName = doc.getString("firstName")!!
val points = doc.getString("points")!!
top3[index+1] = hashMapOf("first" to firstName, "points" to points, "uid" to doc.id)
}
}
然后,我們需要實現COUNT()
功能。
Firebase.firestore.collection("users")
.whereGreaterThan("points", UserProfile.getInstance()!!.getProfile().points)
.count().get(AggregateSource.SERVER).addOnSuccessListener {
println("Your rank is: ${it.count+1}")
}
基本上我在這里所做的是:-
最后一步只是更新 hash map top3
和用戶每小時/天/分鍾/排名。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.