簡體   English   中英

使用 firebase 的最低成本排行榜系統

[英]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 - 為什么在這里不合適?

使用 Cloud Functions 進行聚合避免了客戶端事務的一些問題,但有一組不同的限制:

  • 成本 - 添加的每個評級都會導致 Cloud Function 調用,這可能會增加您的成本。 有關更多信息,請參閱 Cloud Functions定價頁面
  • 延遲 - 通過將聚合工作卸載到 Cloud Functions,您的應用程序將不會看到更新的數據,直到 Cloud Functions 完成執行並且客戶端已收到新數據的通知。 根據您的 Cloud Functions 的速度,這可能比在本地執行交易花費更長的時間。
  • 寫入速率 - 此解決方案可能不適用於頻繁更新的聚合,因為 Cloud Firestore 文檔每秒最多只能更新一次。 此外,如果事務讀取在事務外修改的文檔,它會重試有限次數然后失敗。

結合其他方法

現在您正在為這個系統使用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}")
}

基本上我在這里所做的是:-

  1. 選擇集合
  2. 按名字升序排列,因此沒有重復的排名。
  3. 計算它們,然后傳遞給函數。

最后一步只是更新 hash map top3和用戶每小時/天/分鍾/排名。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM