天天看點

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

網絡互聯裝置的增長帶來了大量易于通路的時間序列資料。越來越多的公司對挖掘這些資料感興趣,進而擷取了有價值的資訊并做出了相應的資料決策。

近幾年技術的進步提高了收集,存儲和分析時間序列資料的效率,同時也刺激了人們對這些資料的消費欲望。然而,這種時間序列的爆炸式增長,可能會破壞大多數初始時間序列資料的體系結構。

Netflix作為一家以資料為驅導的公司,對這些挑戰并不陌生,多年來緻力于尋找如何管理日益增長的資料。我們将分享Netflix如何通過多次擴充來解決時間序列資料的存儲架構問題。

時間序列資料——會員觀看記錄

Netflix會員每天觀看超過1.4億小時的内容。而每個會員在點選标題時會産生幾個資料點,這些資料點将被作為觀看記錄進行存儲。Netflix通過分析這些觀看資料,為每位會員提供了實時準确的标簽和個性化推薦服務,如這些文章中所述:

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

如何判斷你在觀看一個節目?

https://medium.com/netflix-techblog/netflixs-viewing-data-how-we-know-where-you-are-in-house-of-cards-608dd61077da

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

幫助你繼續觀看Netflix的節目。

https://medium.com/netflix-techblog/to-be-continued-helping-you-find-shows-to-continue-watching-on-7c0d8ee4dab6

從以下3個次元累積曆史觀看記錄:

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

随着時間的推移,每個會員的更多觀看記錄資料将被存儲。

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

随着會員數量的增長,更多會員的觀看記錄資料會被存儲。

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

随着會員每月觀看時間的累積,每個會員的更多觀看記錄将被存儲。

過去十年的發展,Netflix已經在全球擁有1億名會員,其觀看記錄的資料亦是大幅增加。在本篇部落格中,我們将重點讨論如何應對存儲觀看曆史資料帶來的巨大挑戰。

從簡單的開始

觀看記錄的第一版原生雲存儲架構使用Cassandra的理由如下:

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

Cassandra對時間序列資料模組化提供了很好的支援,其中每行都有動态的列數。

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

觀看記錄資料的讀寫速度比約為9:1。由于Cassandra的寫入效率非常高,是以Cassandra非常适合頻繁寫入操作的工作。

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

根據CAP定理,團隊更傾向于最終的一緻性。Cassandra支援通過調整一緻性進行權衡。

在最初的方法中,每個成員的觀看曆史記錄都存儲在Cassandra中,并使用行鍵存儲在一行中:CustomerId。這種水準分區的方式能夠随着會員數量的增長而有效擴充,并且使得浏覽會員的整個觀看記錄的常見用例變得簡單、高效。

然而随着會員數量的增加,更重要的是,每個會員的流量越來越多,行數和整體資料量也越來越多。随着時間的推移,這導緻了昂貴的操作成本,對于讀寫具有海量觀看記錄的會員資料而言性能較差。

下圖說明了初始資料模型的讀寫流程:

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

圖1:單個圖表資料模型

寫流程

當會員點選播放時,一條觀看記錄将作為新的列插入。點選暫停或停止後,則該觀看記錄被更新。可見對于單列的寫入是迅速和高效的。

讀流程

通過整行讀取來檢索一個會員的所有觀看記錄:當每個會員的記錄數很少時,讀取效率很高。但是随着一個會員點選更多标題産生更多的觀看記錄。此時讀取具有大量列的行資料會給Cassandra帶來額外的壓力,并造成一定的讀取延遲。

通過時間範圍查詢讀取會員資料的時間片:将導緻了與上面的性能不一緻,這取決于在指定的時間範圍内檢視記錄的數量。

通過分頁整行讀取大量觀看記錄:這對于Cassandra來說是好的,因為它并不需要等待所有的資料傳回就可以加載。同時也避免了用戶端逾時。然而,随着觀看記錄數量的增加,整行讀取的總延遲增加了。

放緩原因

讓我們來看看Cassandra的一些内部實作,以了解為什麼我們最初簡單設計的性能緩慢。随着資料的增長,SSTable的數量相應增加。

由于隻有最近的資料在記憶體中,是以在很多情況下,必須同時讀取memtables和SSTable才能檢索觀看記錄。這樣就造成了讀取延遲。同樣,随着資料量的增加,壓縮需要更多的IO和時間。由于行越來越寬,讀修複和全列修複是以變得更加緩慢。

緩存層

雖說Cassandra在觀看記錄資料寫入方面表現很好,但仍有必要改進讀取延遲。為了優化讀取延遲,需要以犧牲寫入路徑上的工作量為代價,我們通過在Cassandra存儲之前增加記憶體分片緩存層(EVCache)來實作。

緩存是一種簡單的鍵值對存儲,鍵是CustomerId,值是觀看記錄資料的壓縮二進制表示。每次寫入Cassandra都會發生額外的緩存查找,并在緩存命中時将新資料與現有值合并。

讀取觀看記錄首先由緩存提供服務。在高速緩存未命中時,再從Cassandra讀取條目,壓縮并插入高速緩存。

多年來随着緩存層的增加,這種單一的Cassandra表格存儲方法表現良好。基于CustomerId的分區在Cassandra叢集中可擴充性亦較好。

直到2012年,觀看記錄Cassandra叢集成為Netflix最大的Cassandra叢集之一。為進一步擴充,團隊決定将叢集規模擴大一倍。

這就意味着Netflix要冒險進入使用Cassandra的未知領域。與此同時,伴随着Netflix業務的快速增長,包括不斷增加的國際會員數和即将投入的原創内容。

重新設計:實時和壓縮存儲方法

顯然,需要采取不同的方法進行擴充來應對未來5年的預期增長。團隊分析了資料特征和使用模式,重新設計了觀看記錄存儲方式并實作了兩個主要目标:

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

較小的存儲空間

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

每個會員的觀看記錄增長與讀寫性能保持一緻

對于每個會員,觀看記錄資料被分成兩個集合:

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

實時或近期觀看記錄(LiveVH):頻繁更新的最近觀看記錄數量較少。這樣的資料以非壓縮形式存儲,如上面簡單的設計中所述。

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

壓縮或存檔觀看曆史記錄(CompressedVH):大量較早的觀看記錄很少更新。 這樣的資料将被壓縮以減少存儲空間。壓縮的觀看曆史記錄存儲在每行鍵的單個列中。

LiveVH和CompressedVH存儲在不同的表格中,并通過不同的調整以獲得更好的性能。由于LiveVH的頻繁更新和擁有少量的觀看記錄,是以壓縮需頻繁進行,且保證gc_grace_seconds足夠小以減少SSTables數量和資料大小。

隻讀修複和全列修複頻繁進行保證資料的一緻性。由于對CompressedVH的更新很少,是以手動和不頻繁的全面壓縮足以減少SSTables的數量。在不頻繁更新期間檢查資料的一緻性。這樣做消除了讀修複以及全列維修的需要。

使用與前面所述相同的方法将新觀看記錄寫入LiveVH。

為了從新設計中獲益,觀看曆史記錄的API已更新,可以選擇讀取最近的或完整的資料:

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

最近觀看記錄:對于大多數的用例,隻需從LiveVH中讀取資料,通過限制資料大小降低延遲。

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

完整的觀看記錄:作為LiveVH和CompressedVH的并行讀取實作。由于資料壓縮和CompressedVH的列較少,是以通過讀取較少的資料就可以顯著加速讀取。

CompressedVH更新流程

當從LiveVH中讀取觀看曆史記錄時,如果記錄數量超過可配置的門檻值,那麼最近的觀看記錄就被彙總一次,壓縮并通過背景任務存儲在CompressedVH中。然後使用行鍵(行關鍵字):CustomerId将資料存儲在新行中。新的彙總是版本化的,寫入後會再次檢查查資料的一緻性。隻有在驗證與新版本資料一緻後,舊版本的資料才會被删除。為簡單起見,在彙總過程中沒有加鎖,Cassandra負責解決極少的重複寫入操作(即最後一個寫入操作獲勝)。

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

圖2:實時和壓縮的資料模型

如上圖所示,CompressedVH中彙總的行也存儲中繼資料資訊,如最新版本号,對象大小和塊資訊(稍後更多)。版本列存儲對最新版本的彙總資料進行引用,以便CustomerId的讀取始終隻傳回最新的彙總資料。

彙總起來的資料存儲在一個單一的列中,以減少壓縮壓力。為了最大限度地減少頻繁觀看模式的會員的彙總頻率,最後幾天檢視曆史記錄的值将在彙總後儲存在LiveVH中,其餘部分在彙總期間與CompressedVH中的記錄合并。

通過Chunking進行擴充

對于大多數會員來說,将其整個觀看記錄存儲在單行壓縮資料中将在讀取流程中提升性能。對于一小部分具有大量觀看記錄的會員,由于第一種體系結構中描述的類似原因,從單行中讀取CompressedVH速度緩慢。不常見用例需要在讀寫延遲上設一個上限,才不會對常見用例造成讀寫延遲。

為了解決這個問題,如果資料大小大于可配置的門檻值,我們将彙總起來的壓縮資料分成多個塊。這些塊存儲在不同的Cassandra節點上。即使對于非常大的觀看記錄資料,對這些塊的并行讀取和寫入也最多隻能達到讀取和寫入延遲上限。

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

圖3:自動縮放通過組塊

如圖3所示,根據可配置的塊大小,彙總起來的壓縮資料被分成多個塊。所有塊都通過行鍵:CustomerId $ Version $ ChunkNumber并行寫入不同的行。在成功寫入分塊資料之後,中繼資料通過行鍵:CustomerId寫入到自己的行。

對于大量觀看記錄資料的彙總,上述方法将寫入延遲限制為兩種寫入。在這種情況下,中繼資料行具有一個空資料列,以便能夠快速讀取中繼資料。

為了使常見用例(壓縮觀看記錄小于可配置門檻值)被快速讀取,将中繼資料與同一行中的觀看記錄組合以消除中繼資料查找流程,如圖2所示。

通過關鍵字CustomerId首次讀取中繼資料行。對于常見用例,塊數為1,中繼資料行也具有最新版本彙總起來的壓縮觀看記錄。對于不常見的用例,有多個壓縮的觀看記錄資料塊。使用版本号和塊數等中繼資料資訊生成塊的不同行密鑰,并且并行讀取所有塊。上述方法将讀取延遲限制為兩種讀取。

<b>緩存層更改</b>

記憶體緩存層的增強是為了支援對大型條目進行分塊。對于具有大量觀看記錄的會員,無法将整個壓縮的觀看曆史記錄放入單個EVCache條目中。與CompressedVH模型類似,每個大的觀看曆史高速緩存條目被分成多個塊,并且中繼資料與第一塊一起被存儲。

結果

利用并行,壓縮和改進的資料模型,實作了所有目标:

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

通過壓縮縮小存儲空間。

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

通過分塊和并行的讀/寫操作保證讀/寫一緻性。常見用例的延遲受限于一次讀操作和一次寫操作,以及不常見用例的延遲受限于兩次讀操作和兩次寫操作。

業界 | 每天1.4億小時觀看時長,Netflix怎樣存儲這些時間序列資料?

圖4:結果

資料大小減少了約6倍,花費在Cassandra維護上的系統時間減少了約13倍,平均讀取延遲減少了約5倍,平均寫入延遲減少了約1.5倍。更重要的是,它為團隊提供了可擴充的架構和空間,可以适應Netflix觀看記錄資料的快速增長。

原文釋出時間為:2018-03-13

本文作者:文摘菌

繼續閱讀