MaxCompute 半結構化數據思考與創新

雲佈道師

本文將介紹 MaxCompute 在半結構化數據方面的一些思考與創新,介紹會圍繞下面四點展開:1.半結構化數據簡析 2.傳統方案優劣對比 3.MaxCompute 半結構化數據解決方案 4.收益分析

半結構化數據簡析

首先來介紹一下什麼是半結構化數據。

半結構化數據是相對結構化數據和非結構化數據而言的,所以先來看一下什麼是結構化數據和非結構化數據。

結構化數據的概念大傢都比較熟悉。傳統的關系型數據庫是用表的方式對數據進行組織,表的內部定義瞭字段的數量、類型,以及各種各樣字段的屬性信息,這些定義本身就包含瞭豐富的信息。因為在結構化數據的場景中,字段屬性被事先嚴格地定義,所以數據庫也好,各種數據引擎、存儲引擎都可以通過這些定義獲得的信息,有針對性的對數據進行處理。這些處理包括但不限於建立索引,對數據進行排序,對數據進行列存化,也包括向量化執行等等,從而達到一個降低存儲成本,提升訪問效率的目的。通常來說,結構化數據可以達到非常好的數據讀寫性能,它的存儲效率、壓縮效率也可以做得非常好,但是它的靈活性會受到很大的限制。在很多的數據庫或者數據倉庫當中,如果想改變數據表的結構,通常來說是一個非常高風險的操作,可能會帶來很多額外的數據管理成本。

相比之下,非結構化數據本身也是一個很容易理解的概念,我們日常生活中會接觸到的,比如說視頻、音頻、圖片、文章等等,都可以算作是一個比較典型的非結構化數據。非結構化數據裡面很一個很重要的特征就是沒有一個清晰的統一的協議對這些數據內部的結構進行約束,數據本身的內容也沒有統一的規律。非結構化數據具備的優勢就是幾乎是無限的數據靈活性。當然這種無限的靈活性本身也是會有代價的,就是由於沒有辦法事先對數據本身的結構進行解析,因此很在大部分場景裡面,數據引擎沒有辦法對數據結構進行有效的信息提取,所以一般來說非結構化數據的存儲效率和訪問性能都是比較差的。

說清楚瞭結構化數據和非結構化數據,我們再回過頭來看看什麼是半結構化數據,半結構化數據最重要的一個特征就是數據內部一般來說會包含數據本身的一個結構信息,我們會說它是一個自包含的數據結構。通過事先的一個約定的協議,我們可以很容易地對數據結構這個數據進行解析和數據內容的提取。

相比於傳統的結構化數據,半結構化數據並沒有受到來自外部的、來自數據倉庫或者數據庫的這種表級別的強約束。因此一般來說,半結構化數據會更加的靈活,它可以更好地根據具體的用戶場景,用戶需求進行動態的變化。也正是因為這個特征,通常來說半結構化數據會有多層嵌套的結構,比如說 Json,Xml,都是很典型的半結構化數據。從非結構化數據到半結構化數據,最後再到結構化數據,數據的靈活性是在不斷下降的,但是數據的存儲效率、訪問性能也在不斷地提升。半結構化數據在某種程度上可以說是兼顧瞭兩種類型的優點,一方面它具有比較好的靈活性,另外一方面通過協議本身的結構化的約束,也為高效率的訪問和解析提供瞭幫助。

半結構化數據是一種通用的數據傳輸和存儲結構,被廣泛地使用在日志分析、IoT 設備的信息采集、移動設備的事件上報,以及自動駕駛等多樣化的數據場景中。這也體現瞭半結構化數據的一個很大的特點,就是它的通用性非常好。同時由於半結構化數據本身的靈活性,它們可以在大部分數據場景下面承載和傳遞豐富的原始數據的信息。由於其解析協議非常簡單,也能夠支持我們快速地對數據進行解析和訪問。所以一般來說,半結構化數據本身就是一個非常豐富的數據信息的載體。另一方面,比如 Json、Xml,具有非常豐富和完善的數據生態,我們可以很方便地在各種語言、各種框架、各種平臺上面對這些數據進行解析、生產和消費。多平臺的通用性也讓半結構化數據這種協議成為瞭事實上的一種數據的通信標準,可以廣泛地接受和使用。從數據倉庫處理的角度來說,半結構化數據本身的這種靈活性,也會給上遊的業務部門以及中間的數據中臺和下遊的數據消費決策部門的獨立運行,提供一個很好的緩沖。在一個比較大的公司或者團隊中,上遊是產生數據的業務部門,中間是負責數據處理維護的數據倉庫的數據中臺部門,還有下遊負責決策和消費的決策部門,通常是獨立運行的,有著截然不同的運行模式和目標。半結構化數據以其靈活性和易解析的特性可以很好地幫助各個部門進行獨立的業務演進,避免由於上遊部門頻繁的業務迭代帶來高昂的跨部門溝通以及數據維護的成本。

傳統方案優劣對比

在傳統的數據倉庫中,半結構化數據解決方案分為 schema on read 和 schema on write 兩種形式。本質上來講的就是數據引擎在數據讀寫的哪一個環節對數據結構進行解析。schema on read,顧名思義就是在數據導入的過程中不對數據做任何解析和處理,直接將數據進行存儲,然後隻有在數據訪問的時候會根據用戶具體的請求,依賴引擎的動態解析能力對數據進行解析。由於在數據寫入的過程中我們沒有對數據本身做出任何約束,因此 schema on read 的方案一般來說會提供比較好的數據靈活性。和 schema on read 相反,schema on write 方案就是我們在數據寫入的時候,就需要根據事先定義好的結構對數據進行解析,將這種半結構化數據的結構轉換成傳統的結構化數據,然後再導入到數據倉庫當中。相比於 schema on read,schema on write 的靈活性較差,但是能夠提供更好的存儲和訪問性能。

當用戶將數據寫入到數據倉庫中,因為沒有對數據進行任何解析,直接是以字符串的方式導入到數據倉庫的,因此是以一種行式的數據結構進行存儲的。當用戶要對這個數據進行查找和解析,比如上圖的案例,用戶希望統計年紀在 18 歲以上的用戶數量的時候,需要先提取年紀在 18 歲以上這個特征,由於在數據寫入的時候沒有提前對數據進行拆分,所以需要對整個數據進行全表掃描,拿到瞭所有的數據之後進行解壓解碼,然後獲得具體的 JSON 數據結構,再進一步地根據用戶的需求對這個 JSON 結構進行處理,最終獲得年齡字段。在整個執行鏈路當中,一方面數據的存儲開銷非常大,另外一方面整個查詢效率由於需要 full scan,還需要花費額外的 CPU 進行解壓,同時對這個 JSON 的數據結構進行解析,所以它的數據解析效率,訪問性能都是非常差的。

同樣的一個查詢請求,在 schema on write 的場景裡面,我們可以隻對用戶的年齡字段進行讀取,然後直接進行數據解壓解碼,獲得完整的數據結構,再進行數據的解析和查詢,它的存儲效率和查詢效率都會更高。

但是 schema on write 的方案也不是完美無缺的。一般來說,采用 schema on write 方案的時候,會假定上遊業務部門不會對這些字段進行頻繁的改動,整個數據結構處於一個相對穩定的狀態。如果上遊的業務部門處於一種快速迭代、快速適應的階段,那麼可能會不斷地有增加字段、修改字段的需求。

在上遊業務快速迭代的情況下,如果仍然選擇使用 schema on write 方案,下遊的數據中臺或者數據維護部門就需要不斷地根據上遊業務部門的改動,對數表的結構進行適配,不斷地、頻繁地執行表字段的增加或者修改,這將耗費巨大的業務維護成本。因此我們考慮有沒有可能在允許上遊業務部門頻繁迭代、自由迭代的情況下,既獲得比較好的查詢效率和比較低的存儲成本,又盡可能地降低數據倉庫或者數據維護部門的數據維護成本。

這也就是我們提出來的數據倉庫半結構化數據場景的一個核心的需求。希望能夠同時兼顧數據查詢的高性能、數據存儲的低成本以及數據演進的靈活性。結合 schema on write 和 schema on read 的優勢,一方面在數據寫入的過程中進行數據結構的提取和轉換,同時也支持對數據讀取過程中動態的自適應的訪問,從而達到一個降低存儲成本和保持靈活性的效果。

MaxCompute 半結構化數據解決方案

MaxCompute 是一個適用於數據分析場景的企業級的雲服務數倉,以 serverless 的架構提供快速的全托管的在線的數據倉庫服務。它消除瞭傳統數據平臺在擴展性和彈性方面的限制,能夠最小化用戶運維投入,可以使用戶以較低的成本高效地分析和處理海量數據。隨著當前數據收集手段的不斷豐富,各個行業數據的大量積累,數據規模已經增長到瞭傳統的數據庫或者軟件行業無法承載的 PB 甚至 EB 的級別。MaxCompute 提供瞭離線和流式的數據接入,能夠支持超大規模的數據計算和查詢加速能力,可以為用戶提供包括面向多種計算場景的數據倉庫解決方案以及分析建模的服務。MaxCompute 還提供完善的數據導入方案和分佈式計算的模型。用戶不需要關心具體的分佈式計算和維護細節,就可以完成大數據分析。通常來說 MaxCompute 適用於100GB 以上存儲規模的計算需求量,最大可以到 EB 級別。MaxCompute 在阿裡巴巴集團內部得到瞭大規模的應用,適用於大型互聯網企業的數據倉庫和 BI 分析,網站的日志分析,電子商務場景的交易分析,以及用戶特征和興趣的挖掘。

上圖展示瞭 MaxCompute 半結構化數據的一個具體場景。用戶會將前端的業務數據和業務日志,通過實時或者分批次的方式導入數據倉庫,然後與業務數據進行結合。從用戶的訴求來看,他們希望能夠最大限度地減少入倉過程中數據轉換的鏈路,並提升數據導入的實時性。另一方面數據中臺也會對數據進行定時的監控,保證數據質量,同時進行定時的報警觸發。在數據的下遊,多個不同的業務部門會對數據有不同的解析需求,用戶會通過交互式分析、加速查詢的方式生成可視化的數據報表。

在該場景中,上遊的業務部門和中間的數據中臺,還有下遊的數據消費決策部門有著各自比較獨立的數據需求,在這裡半結構化數據就可以成為連接和緩沖各部門不同訴求的一個非常自然的選擇。用戶在引入半結構化數據的同時,一方面可以允許上遊業務根據自身的需求獨立演進,快速迭代,中臺的數據維護成本也可以降到最低,同時下遊各個不同消費部門各自的數據消費需求也可以得到很好的滿足。

用戶在將半結構化數據導入數據倉庫的過程當中,發現在一個相對較短的周期,比如幾個小時、幾天甚至幾周這樣一個周期內,用戶的數據結構基本上來說是保持穩定的,也就是說在一個較短的時間內,用戶的字段的類型和數量是幾乎保持不變的。因此在短周期而且空間相鄰的數據當中,有機會去提取這些相對穩定的公共數據結構,然後將這些半結構化數據通過列存化的方式來降低存儲成本並提升存查詢效率。

在長周期的業務迭代中,比如幾周甚至幾個月的迭代周期裡面,業務部門可能會根據具體的業務場景進行相對緩慢平滑的業務迭代,可以通過引擎自身帶有的這種動態自適應的能力,去適應和發現長周期當中字段類型或者字段數量的變化,從而達到一個比較綜合的半結構化數據的解決方案。

在如上的數據中可以通過對這個數據的掃描提取出來一個所有數據都具備的公共的數據類型,它中間會有四個字段,每個字段會有一個明確的類型,然後通過這個提取的類型,可以將原始的用戶數據以及收集到的這個類型,同樣地輸入給數據轉換器,數據轉換器就可以將這個數據進行很好的列存化。實現在數據導入數據倉庫的過程中,就對數據進行動態的解析,完成數據列存化的過程。

MaxCompute 底層采用的是 AliORC 列存來進行數據的存儲。AliORC 是阿裡雲自己研制的一個高性能的基於開源 Apache ORC 的數據格式,它能夠天然地很好地對這種嵌套結構進行支持,能夠在數據結構、數據文件的文件格式層面就很好地去保存不同節點之間的相互映射以及嵌套的信息。當要對某一個比較深的節點進行探查或者裁剪的時候,可以很自然地將這種 JSON 的路徑和這種嵌套結構的節點進行一一映射,然後來做出一個很好的、很自然而高效率的列裁剪。

如上述例子,可以首先通過一個前面提到的 schema 提取的工作,將它提取成一個具有嵌套特征的數據結構,最終將它轉化成一個基於 AliORC 的列存結構,將每列的數據進行連續的存放,甚至是嵌套類型內部的子節點,也可以把它進行列存化,實現連續存放從而獲得更好的壓縮性能以及更好的查詢性能。

理想情況下用戶的所有數據都具有比較好的穩定性和一致性,但是由於半結構化數據本身自有的這種靈活性的特點,很多場景下面臟數據是難以完全避免的。前面的例子假設所有的用戶字段提供的數據類型都是非常幹凈,非常統一的,但在事實的生產環境中,很有可能會由於代碼的 bug,或是數據傳輸過程中的錯誤,導致數據的類型並不完全一致。比如上圖中標紅的第三個 age 字段,前面兩個 JSON 數據中 age 字段都是整型,但是在第三行數據當中,age 字段突然變成瞭一個字符串類型。在這種場景下面,沒有辦法很好地進行一個統一的公共類型的提取,因為整型和字符串類型很多時候並不是一個互相兼容的類型。在這種情況下,我們會將這種數據保存成一種內部的二進制數據結構,在這種二進制的結構當中,不僅會保存這個字段具體的數據信息,也會保存它的數據類型的信息。在這個例子裡面將前兩行數據的 age 字段,同時記錄瞭它的數據類型,也就是它的整型信息,也記錄瞭它的具體的值的信息。然後在第 3 行數據當中,記錄的它是一個 string 類型,這樣就達到瞭盡可能完整地保存用戶數據的目的。因為從平臺的角度來說,平臺很難判斷用戶的這個類數據類型的變化到底是出於業務類型的考量、業務自然的演進,還是一個由 bug 導致的錯誤,所以從數據平臺的角度,還是需要能夠盡可能完整地保存用戶信息。另一方面,這種使用獨立的二進制的方式來保存信息的方式也盡可能地保證瞭數據列存的效率。能夠最大限度地保證不同的字段仍然是通過列存的方式進行存儲的。在數據訪問的時候也盡可能地針對不同字段進行列裁剪。而且某一個列當中出現瞭臟數據,並不會對其它列的數據類型和數據存儲訪問效率造成影響。因此最大限度地將臟數據類型和普通數據類型進行瞭隔離。

另外一種比較棘手的場景就是稀疏數據類型的處理。在一些場景中,每一行用戶數據中可能都會存在一些字段,這些字段出現的頻率很低。如果使用前面這種公共類型提取的方案,將這些出現頻率很低的字段仍然提取為一個獨立的列,就會導致底層存儲格式上列的數量無限膨脹,列存本身的效率就會變得非常低。因此在數據進行這種類型提取的過程當中,也需要對字段的頻率進行統計。對頻率較低的字段進行一個統一的歸納處理,將它們放到一個統一的特殊字段當中。在數據訪問的過程當中,如果用戶查找到瞭一些在列存化字段當中不存在的列,那麼就會在特殊字段當中進行查找。通過這種處理,我們希望能夠最終取得一個兼顧效率和靈活性的平衡。

接下來看一看數據引擎是怎麼在具體的查詢過程中進行自適應的查找的。前面提到,用戶數據的結構在一個較長的周期裡面是可能會不斷地演進和變化的,因此在實際存儲的過程中,不同的列存文件,其數據可能會受到用戶業務長期演進的影響,因此不同的列存文件實際存儲的用戶數據的 schema 可能是不完全一致的。比如前面提到的這個 SQL 查詢的例子,在這個查詢過程中,用戶要查詢年齡這一列,然後將它 cast 成 int 類型之後,去查詢所有大於 18 歲的用戶的數量。在這樣一個場景中,會先對這個 SQL 查詢進行一個解析,然後把它做成一個 logical plan。在這個 plan 當中會有多個不同的算子,在最上遊的這種聚合算子或者查詢算子,它們都是一個強類型的算子,會期望輸入的數據是一個整型。但是在底層的 table scan 讀上來的時候,它其實是一個動態數據類型,並沒有辦法知道這個時候讀上來的實際是一個什麼類型,因此在中間需要增加一個動態數據轉換的能力,將數據讀上來的任何類型,通過一種 best effort 的方式轉化成需要的數據類型,再去進行下一步的數據處理。如圖中所演示的,會首先對這個數據文件進行列裁剪,讀取出來 age 這個年齡字段。但是大傢可以看到,在這個樣例當中,不同的數據文件,讀出來的 age 字段的類型是不一樣的,有的文件裡面可能讀出來是字符串類型,有的文件讀出來是整型,有的文件讀出來是 binary 的二進制類型。因此數據引擎就需要根據實際讀上來的類型動態判斷要不要在中間增加一個數據轉換的算子,統一將數據轉換成 int 類型,之後再交由上遊的 filter 算子進行數據的處理,從而實現一個自適應的數據處理的能力。

收益分析

最後來看一下數據查詢方面的收益。對比瞭前面提到的三種方案,一種是完全使用 JSON 字符串的方式,一種是 JSON 列存化的方式,另外一種就是原生的列存的方式。大傢可以看到無論是數據在做 table scan 過程中讀取的數據量,還是整個數據的查詢時間,查詢性能都會有接近一個數量級的提升。

相比於原生的列存,Json 列存化的方案仍然存在一些提升的空間。在分析之後發現這裡面存在的空間主要的原因還是在 Json 列存化進行數據解析的過程當中,沒有辦法完全地將日期類型等等很好地轉換成對應的原生的列存類型,這也是下一步的工作當中需要改進的方向。

最後對 MaxCompute 這個半結構化數據的列存化方案進行一個總結。首先,MaxCompute 半結構化列存方案是開箱即用的,不需要用戶側做任何改造。它可以最大限度地保證用戶隻要進行正常的數據導入,就可以享受到半結構化列存方案帶來的紅利,然後最大限度地降低用戶側改造帶來的額外的數據維護成本。另外,通過在寫入的時候對數據類型進行動態解析,能夠最大限度地去利用數據結構之間的相似性,提取相鄰數據之間的公共結構,對數據列存化。同時也對臟數據或者稀疏數據等場景進行瞭兜底,保證用戶在各種場景下的半截刻畫數據都可以盡可能地享受到的列存化方案帶來的優勢。同時通過這種數據的列成化以及數據引擎動態的訪問能力,能夠最大限度地提升數據的查詢效率,達到接近於原生列存的查詢效率。最後,通過使用內部的列式存儲,能夠最大限度地降低存儲成本,從而達到降低存儲成本的目的。

問答環節

Q:設置瞭列的類型就可以直接使用嗎?

A:是的,阿裡雲 MaxCompute 在直接提供瞭一個叫做 JSON 的數據類型,用戶隻要設置瞭這個列的數據類型之後,就可以直接享受到提供瞭這個列式半結構化、列存化的這麼一個帶來的這種紅利和優勢,在用戶在導入的過程中,或者用戶在數據查詢的過程中,都不需要進行任何額外的數據維護和操作。

Q:數據在列存的過程中怎麼維護?

A:JSON 數據的我們在存儲的過程當中並不會直接管理這個 JSON 內部的數據結構,就是說在數據的源數據存儲過程中,並不會直接去理解說這個數據當中到底有多少結構,因為用戶的結構可能是非常復雜,也會不斷地演進的。因此隻是在數據存儲的過程中,在文件級別會保留 Json 的這個數據結構。然後會依賴數據結,數據引擎在訪問過程中的這麼一個動態的能力去對 JSON 的內部結構進行提取。

Q:阿裡雲 MaCompute 支持私有化部署嗎?

A:支持的。

赞(0)