使用 Aerospike 實現使用者通知列表
我需要為需要處理數十億條通知的通知系統選擇正確的數據庫。記錄結構如下:
[user_id, item_type, item_id, created_at, other_data]
插入件將在峰值時批量達到數十萬。它需要每分鐘支持數千次這樣的選擇:
select * from user_notifications where user_id=12345 order by created_at limit 10 select * from user_notifications where user_id=12345 and item_type='comment' order by created_at limit 10 --- and for the pagination next page: select * from user_notifications where user_id=12345 and item_type='comment' and created_at>'2020-11-01 10:50' order by created_at limit 10
它還應該允許快速更新和刪除,並且理想情況下在每條記錄上都有 TTL。現在它是使用 MySQL 實現的,我們只有 400M 行,它已經很慢了。批量清理是不可能的。
最初,我認為 ScyllaDB/Cassandra 非常適合。
[user_id, item_type, item_id]
如果我將插入/更新/刪除的主鍵設置為(user_id 是分區鍵)並[user_id, item_type, created_at]
作為二級索引。在這種情況下,CQL 看起來很簡單,它應該可以快速執行(如果我錯了,請糾正我)。問題是我們是基於 Ruby-On-Rails 的,並且沒有好的 ruby 客戶端庫。ScyllaDB 客戶列表 ( https://github.com/datastax/ruby-driver ) 中列出的那個處於維護模式,我不確定它是否會更新為新的 Ruby 版本等。最近,我聽說 Aerospike 和他們的基準測試看起來很酷,但我不知道如何使用 Aerospike 的架構來實現上述要求。特別是因為他們的二級索引似乎總是在記憶體中,這使得索引數十億行是不可能的。
在我看來,這種通知模式似乎很常見,但我仍然找不到一篇好的文章來描述實現它的所有理想方法。歡迎任何建議。
謝謝
由於 ScyllaGreg 對 Scylla 說得很好,我想我會代表我工作的 Aerospike。在所有 C* 變體中,我最喜歡 ScyllaDB,但我相信在您描述的情況下,通常對於面向行的情況,Aerospike 數據庫在使用更少硬體的情況下會比 ScyllaDB 執行得更好。我寫了幾篇 Medium 文章(@rbotzer),根據實際案例解釋了原因。
造型
這是在 Aerospike 中進行建模的一種可能方法。
我假設您總是想訪問特定使用者的通知。因此,記錄的鍵(Aerospike-speak 中的一行)將是user_id。其餘數據適合包含以下鍵排序Map結構的單個 bin :
{ epoch: [ item_type, item_id, other_data ] }
哪裡有一種方法可以將created_at
epoch
日期時間簡化為一個整數,表示自任意時期以來的分鐘、秒或毫秒,例如您的應用程序上線的日期(例如:自 2020-10-01 00:00 以來的分鐘數)。您可以選擇對您有意義的解析度以避免覆蓋。無論哪種方式,Aerospike 都使用 MessagePack 序列化此地圖數據,MessagePack 具有將數值調整為最小儲存大小的良好副作用。查詢
現在要查詢這些數據,我們將使用地圖操作 API:
- 添加任何資訊都是一個簡單的 Map
put
或put_items
,它將數據插入到 Map 中。- 要獲取特定使用者的所有通知,您只需user_id
get()
的記錄。- 要對特定使用者的通知進行分頁,您可以使用 Map
get_by_index_range(KeyValue, i, 10)
with i = 0, 10, 20, …- 要獲得兩個時間點(10 月 2 日)之間的所有通知,您可以使用 Map
get_by_key_interval(KeyValue, 1440, 2880)
- 要獲取“評論”類型的所有通知,您將使用 Map
get_all_by_value(KeyValue, ['comment', *])
- 要清除 2020 年 10 月的所有通知,您可以使用 Map
remove_by_key_interval(Count, 0, 44639)
您可以看到目前無法對日期範圍的查詢和項目類型的查詢進行 AND 運算。但是,Aerospike 的速度非常快,在查找單個鍵並對連續儲存的記錄數據進行操作後檢索數據。您可以輕鬆獲得更多數據(日期範圍內的所有數據)並在應用程序端過濾item_type。
請注意,這種建模方法需要零個二級索引。它只是利用 Aerospike 的均勻數據分佈、極快的主索引查找和從儲存中讀取單個 IO 記錄的強大功能。
查詢的未來
Aerospike 數據庫 5.2 添加了Expressions,它已經可以執行諸如鍊接 Map
get_by_index_range
=>get_all_by_value
=>之類的操作get_by_index_range
,但只能在過濾器的上下文中。這意味著您可以使用複雜的表達式作為將操作應用於查詢、掃描、批量讀取還是單條記錄操作的條件。在不久的將來,您將能夠將表達式應用於運算,這將解決 AND 限制。如果您想查找特定類型的時間範圍內的所有通知並通過它們進行分頁,您將
exp(get_by_key_interval(KeyValue, 1440, 2880), get_all_by_value(KeyValue, ['comment', *]), get_by_index_range(KeyValue, 0, 10))
替代建模
正如我在Aerospike 建模:物聯網感測器中所述,您可以選擇按天對使用者通知進行分區,鍵為
userID:YYYYMMDD
.如果您期望有太多通知,這將允許您保持合理的記錄大小。它還可以方便地通過刪除來清除超過特定年齡的數據。
但是,我懷疑你不會遇到這個問題。假設一個通知是 40 個字節,那麼 256 個通知最多佔用 10KiB。實際上,它們可能更小,正如我在文章中提到的,MessagePack 將調整其中一些數據類型的大小。如果您使用(Aerospike EE 功能)壓縮,它會更小。即使是 40 字節,預設的 1MiB 寫入塊也可以為單個使用者保存 26,000 條通知,您可以每天使用
remove_by_value_range
.按天分區的另一個好處是只有一條記錄被寫入(“今天”)。
現在來解決您對 Aerospike 中二級索引的評論,確實它們目前儲存在記憶體中,但是記憶體成本並沒有您預期的那麼糟糕。
首先,Aerospike 不像所有 C* 變體那樣在記憶體中緩衝數據。在經典的 Aerospike 數據庫部署中,您將所有數據儲存在 SSD 上,只有索引會消耗記憶體。如果您閱讀了我的 Medium 文章,我將了解為什麼 Aerospike 在從任意大的記錄堆棧中檢索隨機記錄時比 ScyllaDB 更快。
每條記錄在主索引中都有一個 64 字節的條目。至於二級索引,如果您對每個不同的值都有一個索引(並且在這種情況下您永遠不應該使用二級索引) ,那麼絕對最壞的情況將花費您85 個額外的字節。
10 億條記錄的快速計算是 10^9 * (64+85) = 139GiB。如果您有一個 3 節點 Aerospike 集群,則每個節點的記憶體成本為 46GiB。考慮到您不需要儲存數據的記憶體,並且除此之外的成本非常小,您最終會得到一個適合小型集群的相當實惠的 RAM 消耗。
展望未來,Aerospike 中的二級索引將發生相當大的變化,但我仍然建議以類似於上述的方式進行建模。與在 C* 數據庫中使用二級索引的查詢相比,對單個記錄使用 Map 和 List 操作將更快且消耗更少的資源。
完全披露 - 我在 ScyllaDB 工作,所以老實說,我偏向於我認為非常棒的軟體。
如果“ScyllaDB/Cassandra 非常適合”,為什麼不直接使用 Scylla 呢?