Netflix 如何設計一個能滿足 5 倍增長量的時序資料儲存新架構?
2016 年 1 月,Netflix 在全球範圍內擴充套件業務。越來越多的會員、越來越多的語言和越來越多的視訊回放將時間序列資料儲存架構擴充套件到了它的臨界點(詳見第 1 部分文章 ofollow,noindex">《Netflix 實戰指南:規模化時序資料儲存》 )。在第 2 部分中,我們將探索該架構的侷限性,並介紹我們如何為下一個演進階段重新構建架構。
2016 年 1 月,Netflix 在全球範圍內 擴充套件 業務,向另外 130 個國家開放服務,總共支援 20 種語言。2016 年晚些時候,在瀏覽體驗中加入了 視訊預覽 。越來越多的會員、越來越多的語言和越來越多的視訊回放讓 Netflix 的時間序列資料儲存架構達到了臨界點。在這篇文章中,我們將探索該架構的侷限性,並介紹我們如何為下一個演進階段重新構建架構。
臨界點
之前的架構將所有的觀看資料同等對待,而不考慮型別(完整觀看或視訊預覽)或時間(多久之前觀看過)。隨著該功能擴充套件到更多裝置,預覽與完整觀看的比例正迅速增長。到 2016 年底,資料儲存在一個季度內增長了 30%;由於對資料儲存的潛在影響,視訊預覽功能被推遲。最簡單的解決方案是擴充套件底層的 Cassandra 叢集以適應這種增長,但是,它已經是正在使用中的一個最大的叢集,並且已接近叢集規模限制。我們必須做點什麼,而且要快。
重新思考我們的設計
我們開始重新思考我們的方法,並設計出一個至少能滿足 5 倍增長量的方法。我們可以重用之前架構中的模式,但這還不夠,我們需要新的模式和技術。
分析
我們首先分析資料集的訪問模式。我們分析出三種不同型別的資料:
- 完整視訊播放
- 視訊預覽播放
- 語言首選項(即播放哪種字幕 / 配音)
對於每一類資料,我們發現了另外一種模式——大多數資料訪問都是針對最近的資料。資料越舊,其詳細級別就應該降得越低。把這些見解與我們同資料消費者的交流相結合,我們就可以判定哪些資料需要在什麼樣的詳細級別上儲存多長時間。
儲存效率問題
對於增長最快的資料集——視訊預覽和語言資訊,我們的合作伙伴只需要最近的資料。非常短的視訊預覽被我們的合作伙伴過濾掉,因為它們不能反映出會員對內容的觀看意向。此外,我們發現,對於他們觀看的完整視訊,大多數會員選擇了相同的字幕 / 配音語言。為每個觀看記錄儲存相同的語言首選項會導致大量的資料重複。
客戶端複雜性
我們研究的另一個限制因素是觀看資料服務的客戶端庫如何滿足呼叫者在特定時間段內對特定資料的特殊需求。呼叫者可以通過指定以下條件來檢索觀看資料:
- 視訊型別 — 完整視訊或視訊預覽;
- 時間範圍— 過去 X 天 /X 月 /X 年,其中 X 會隨不同使用場景而變化;
- 詳細級別 —完整或摘要;
- 是否包含字幕 / 配音資訊。
對於大多數場景,這些過濾器會在從後端服務獲取到完整資料後將其應用於客戶端。你可能想到了,這會導致大量不必要的資料傳輸。此外,對於較大的觀看資料集,效能會迅速下降,導致 99 百分位讀取延遲出現巨大變化。
重新設計
我們的目標是設計一個能夠擴充套件到 5 倍增長量的解決方案,並且具有合理的成本效率以及更可預測的延遲。通過對上述問題的分析和了解,我們進行了此次重大的重新設計。以下是我們的設計準則:
資料類別
-
按資料型別分片;
-
減少資料欄位,僅保留基本元素。
資料年齡
-
按資料年齡分片;
-
對於最近資料,在設定好的 TTL 之後
過期;
-
對於歷史資料,彙總並轉入歸檔叢集。
效能
- 並行化讀取為近期資料和歷史資料提供了統一的抽象。
叢集分片
之前,我們將所有資料組合到一個叢集中,並使用一個客戶端庫根據型別 / 年齡 / 詳細級別過濾資料。現在,我們顛倒了這種方法,我們按照型別 / 年齡 / 詳細級別對叢集進行分片,每個資料集的增長率就可以互相分離,簡化了客戶端,並改善了讀取延遲。
儲存效率
對於增長最快的資料集——視訊預覽和語言資訊,我們能夠與合作伙伴保持一致,只保留最近的資料。我們不儲存非常短的預覽視訊播放資料,因為它們不能很好地說明會員對內容感興趣程度。此外,我們現在也儲存了初始語言首選項,然後只儲存後續播放的增量。這意味著對於大多數會員,我們只儲存一個語言首選項記錄,從而節省了大量儲存空間。我們降低了預覽視訊和語言首選項資料的 TTL,因而,與完整視訊資料相比,它們的過期策略會更激進。
在必要時,我們將應用“動態(live)”和壓縮技術,其中可配置數量的近期觀看記錄以未壓縮的形式儲存,其餘的記錄以壓縮的形式儲存在單獨的表中。對於儲存較舊資料的叢集,我們完全以壓縮形式儲存資料,在訪問時在較低的儲存成本和較高的計算成本之間做出權衡。
最後,我們使用較少的幾列把摘要檢視儲存在一個單獨的表中,而不是儲存完整視訊播放的所有細節。這個摘要檢視也被壓縮,以進一步優化儲存成本。
總體上,我們的新架構看起來像下面這樣:
如上所示,觀看資料是按型別進行分片的——有單獨的叢集用於完整播放資料、預覽播放資料和語言首選項資訊。對於完整播放資料,儲存是按年齡進行分片的。對於最近的觀看資料(過去幾天)、過去的觀看資料(幾天到幾年)和歷史檢視資料,都有單獨的叢集。最後,對於歷史觀看資料,只有摘要檢視,而沒有詳細記錄。
資料流
寫入
資料寫入到最近的叢集中。在輸入之前會應用過濾器,例如不儲存非常短的視訊預覽播放,或將所播放的字幕 / 配音與以前的首選項進行比較,只在與以前的行為相比存在變化時才儲存。
讀取
對最新資料的請求直接傳送到最近的叢集。當請求更多的資料時,並行讀取可以更有效地獲取資料。
最近幾天的觀看資料 :對於大多數需要幾天完整播放記錄的場景,只從“最近(Recent)”叢集中讀取資料。在叢集中並行讀取 LIVE 表和 COMPRESSED 表。繼續利用動態和壓縮資料集模式,當從 LIVE 讀取的記錄數量超出配置的閾值時,記錄會被彙總,並在壓縮後寫入 COMPRESSED 表,作為一個具有相同 row key 的新版本。
此外,如果需要語言首選項資訊,就並行讀取“語言首選項”叢集。類似地,如果需要預覽播放資訊,那麼就並行讀取“預覽播放”叢集中的 LIVE 表和 COMPRESSED 表。與完整觀看資料類似,如果 LIVE 表中的記錄數超過了配置的閾值,那麼這些記錄將作為具有相同 row key 的新版本彙總、壓縮並寫入 COMPRESSED 表中。
過去幾個月的完整播放資料需要並行讀取 “最近的”和“過去的”叢集。
彙總觀看資料通過並行讀取“最近”、“過去”和“歷史”叢集獲得。然後,將資料整合在一起,得到完整的彙總檢視。為了減少儲存大小和成本,“歷史”叢集中的彙總檢視不包含最近幾年會員觀看的更新資料,因此,需要通過彙總來自“最近”和“過去”叢集的觀看資料來進行增強。
資料流轉
對於完整播放記錄,不同年齡資料的叢集之間的資料移動是非同步進行的。在從“最近”叢集中讀取會員的觀看資料時,如果確定有比配置的天數更長的記錄,則會排隊等待將該會員的相關記錄從“最近”叢集移動到“過去”叢集。在執行任務時,相關記錄與“過去”叢集中 COMPRESSED 表中的現有記錄相組合。然後,將組合的記錄集壓縮並作為新版本儲存在 COMPRESSED 表中。一旦新版本寫入成功,以前的版本記錄將被刪除。
如果壓縮後的新版本記錄集的大小大於配置的閾值,則將記錄集分塊,並並行寫入這些塊。這些從一個叢集到另一個叢集的記錄傳輸是成批進行的,這樣,它們就不會在每次讀取時都觸發。
在從“過去”叢集讀取資料時,也完成了記錄到“歷史”叢集的移動。相關記錄會被重新處理,使用現有彙總記錄建立新的彙總記錄。然後,將它們壓縮並作為新版本寫入“歷史”叢集中的 COMPRESSED 表。新版本寫入成功後,刪除先前的版本記錄。
效能優化
與之前的架構類似,LIVE 記錄和 COMPRESSED 記錄儲存在不同的表中,並進行不同的調優以獲得更好的效能。由於 LIVE 表會頻繁更新,而且僅包含少量的觀看記錄,所以需要經常進行壓縮,並設定較小的 gc_grace_seconds ,以便減少 SSTable 的數量。為了提高資料一致性,會頻繁進行 讀修復 和“全列族修復( full column family repair )”。由於 COMPRESSED 表的更新頻率較低,所以通過低頻的手動全量壓縮足以減少 SSTable 的數量。在極少的更新執行期間檢查資料的一致性,從而避免進行讀修復和全列族修復。
快取層的變化
由於我們對 Cassandra 的大資料塊進行大量的並行讀取,使用快取層會帶來很大的好處。為了模擬後端儲存架構, EVCache 快取層架構也做了修改,如下圖所示。所有的快取都有接近 99% 的命中率,並且在減少對 Cassandra 層的讀請求數量方面非常有效。
快取和儲存架構之間的一個區別是,“彙總”快取叢集儲存完整播放觀看資料的壓縮彙總。在大約 99% 的快取命中率下,只有一小部分請求到達了 Cassandra 層,這一層會並行讀取 3 個表,把記錄拼接在一起,然後建立整個觀看記錄的彙總。
遷移:初步結果
這些變更已經完成了一半以上。能夠從按照資料型別進行分片的叢集中受益的場景已經遷移完畢。因此,雖然我們還不能分享完整的成果,但以下是我們獲得的初步結果和經驗教訓:
- 僅基於按資料型別分片的叢集就獲得了 Cassandra 操作特性(壓縮、GC 壓力和延遲)的巨大改進。
- 巨大的完整觀看資料 Cassandra 叢集動態餘量可以應對至少 5 倍的規模增長。
- 得益於更激進的資料壓縮和資料 TTL,節省了大量成本。
- 架構重構是向後相容的。現有的 API 將繼續有效,預計會有更好、更可預測的延遲。為訪問資料子集而新建立的 API 將帶來額外的延遲優勢,但需要修改客戶端。這使得獨立於客戶端推出伺服器端變更變得更加容易,並且可以根據客戶的使用頻寬在不同的時間使用不同的客戶端。
小結
在過去幾年裡,觀看資料儲存架構已經取得了長足的進步。我們逐步演變成使用一種動態和壓縮資料的模式,可以並行讀取觀看資料,並將這種模式重新用於團隊內其他的時間序列資料儲存上。最近,我們對儲存叢集進行了分片,以滿足不同場景的獨特需求,並對一些叢集使用了動態和壓縮資料模式。我們擴充套件了動態和壓縮資料的流轉模式,以便在按年齡分片的叢集之間移動資料。
設計這些可擴充套件的構建塊可以簡單而高效地擴充套件我們的儲存層。當我們以 5 倍增長規模為目標進行重新設計時,我們知道,Netflix 的產品體驗在持續發生變化和改進。我們正密切關注可能需要進一步演進的地方。
檢視英文原文: https://medium.com/netflix-techblog/scaling-time-series-data-storage-part-ii-d67939655586?br=ro&