讀時建模技術(shù)在異構(gòu)數(shù)據(jù)分析平臺的應(yīng)用
一、日志分析場景的需求和技術(shù)挑戰(zhàn)
1、日志分析
(1)什么是日志
廣義來講,日志記錄的是一個不可變的信息,它是一個事實,也就是在某個時點,發(fā)生了某一件事情。如果狹義地去理解,日志就是系統(tǒng)后臺的 log。通常,log 都會有一個時間戳,記錄了這個時間戳上發(fā)生的事,比如 server 啟動,或者是產(chǎn)生了錯誤等等。
(2)日志分析的價值
日志分析有著很大的價值。在 To C 場景中,比如淘寶、拼多多這樣的電商系統(tǒng),會記錄用戶的瀏覽行為,并推薦商品。這些推薦系統(tǒng)就是依賴于日志的。常見的內(nèi)容優(yōu)化、用戶畫像、AI 算法等也都依賴于日志。
To B 場景下,對于一個企業(yè)來說,分析日志主要有兩個目的,第一是希望利用這些數(shù)據(jù)去更好地賺錢(開源);第二是利用這些數(shù)據(jù)提高企業(yè)的運行效率,幫助企業(yè)去省錢(節(jié)流)。從開源的角度來講,比如近幾年非?;鸨牧鞒掏诰颍褪前哑髽I(yè)在生產(chǎn)中產(chǎn)生的各種日志數(shù)據(jù)收集起來,基于這些事實做統(tǒng)計或者機器學(xué)習(xí),幫企業(yè)進(jìn)行各種挖掘分析,找到一些規(guī)律,比如優(yōu)化制造工藝、提高流水線的生產(chǎn)效率、精簡流程、讓業(yè)務(wù)跑得更快等,這些都能夠幫助企業(yè)去更好地賺錢。從節(jié)流角度來講,比如 IT 運維,如果故障率低、故障響應(yīng)速度快,那么就可以為公司省很多錢,避免潛在損失。
(3)日志分析的技術(shù)挑戰(zhàn)
第一是日志的寫入速率快,存儲成本高。日志都是由機器產(chǎn)生的,機器生成的一大特點就是數(shù)據(jù)量非常大,這就要求系統(tǒng)在數(shù)據(jù)攝入的時候,速率必須要非常高,同時面對大量的文本數(shù)據(jù)存儲,壓縮比必須要做得比較好,才能夠節(jié)省存儲成本。
第二是日志格式很難統(tǒng)一,并且格式經(jīng)常變化。因為日志處理系統(tǒng)通常是存在于業(yè)務(wù)系統(tǒng)的下游,很難去規(guī)定讓所有業(yè)務(wù)系統(tǒng)都符合統(tǒng)一的日志的格式,尤其是在那些 IT 建設(shè)尚不成熟的公司。只有在一些 IT Infra 建得特別好的互聯(lián)網(wǎng)公司,做日志的團(tuán)隊才有可能去推動業(yè)務(wù)團(tuán)隊去統(tǒng)一日志格式。即便如此,日志里面的 schema 也是經(jīng)常會變化的。隨著系統(tǒng)迭代,會有新的功能和業(yè)務(wù),日志信息也會越來越多。如何能夠讓下游日志處理系統(tǒng)更好地去響應(yīng)這些日志格式、日志字段的變化,是一個非常大的挑戰(zhàn),在后文中將著重分享。
第三是批量查詢分析和即席交互式查詢分析(Ad Hoc)需求并存,數(shù)據(jù)進(jìn)入系統(tǒng)之后需要盡快可以查詢。比如在 IT 運維、網(wǎng)絡(luò)安全監(jiān)控等常見的日志處理場景中,當(dāng)要去排障或者尋找安全入侵的時候,或者要去做我們稱之為安全專家的 sweat hunting 的過程,都會有大量的即席交互查詢,所以會需要查詢?nèi)罩鞠到y(tǒng)的快速響應(yīng)。在做交互式查詢的時候,數(shù)據(jù)寫入系統(tǒng)之后,要保證能夠快速地查到。同時日志也會有很多跑批的要求,比如運維監(jiān)控,需要跑批的去做一些告警,可能是每分鐘、每 5 分鐘、每一個小時。所以查詢會是一個比較復(fù)雜的需求。
2、日志處理技術(shù)流派
接下來看一看日志處理的技術(shù)流派。讀時建模的英文叫做 Schema on read,可能有一些同學(xué)了解過數(shù)據(jù)湖或者 ELT 技術(shù),對 Schema on read 不太陌生。Schema on read 和 Schema on write 這兩個概念并不是一個特定的系統(tǒng)或者算法,可以稱之為技術(shù)流派。
(1)寫時建模(Scheme On Write)
上圖的右側(cè)(藍(lán)色)部分就是寫時建模(scheme on write)。常用的關(guān)系型數(shù)據(jù)庫,以及一些數(shù)據(jù)倉庫類的產(chǎn)品,都是寫時建模的工作模式。在用數(shù)據(jù)庫的時候,需要先定義數(shù)據(jù)的表結(jié)構(gòu),比如在上圖的例子里面,有一個 4 列的表,需要定義好每一列數(shù)據(jù)的屬性,比如 code 列是整數(shù)型、client 列是字符串類型、time 列是 datetime 類型。這些定義好之后,后續(xù)所有的數(shù)據(jù)進(jìn)入到系統(tǒng),必須要符合之前定義的 schema 標(biāo)準(zhǔn),否則就不能寫入了,這就是一個典型的 schema on write 的系統(tǒng)。
如果我們有這樣 3 種類型的日志,一個是 Nginx access log、一個是 Apache 的 access log、一個是 windows IIS 的 access log。大家可以看到這些 log 很相像,但格式又不太一樣。但是如果要把這些日志都采集到寫時建模的數(shù)據(jù)庫里面(藍(lán)色的這條通路),就要去做 ETL 了。針對三個不同的日志就需要維護(hù)三個不同的 ETL 任務(wù)。
(2)讀時建模(Schema On Read)
讀時建模的理念是先把原始數(shù)據(jù)存儲下來,即我們把這三份不同產(chǎn)品的原始的日志,直接存儲到我的系統(tǒng)里。比如這里有查詢 1、查詢 2 和查詢 3,大家會看到針對每一個查詢用的字段不一樣。我們會在查詢運行的時候去指定一些字段提取的規(guī)則。比如查詢 1,要用 method 字段、time 字段和 client 字段,要定義這三個字段提取的計算規(guī)則是什么,在查詢運行的時候,去把這些規(guī)則動態(tài)地應(yīng)用到所有的原始日志上面,從中提取出需要的信息。
這個過程有一個專業(yè)的術(shù)語,叫做字段提取,英文叫 field extraction。圖中跑 3 次查詢,橙色的箭頭就是一次 field extraction,這樣會生成一個預(yù)期的 schema 格式。再針對 schema 格式進(jìn)一步做比如統(tǒng)計分析、過濾、轉(zhuǎn)換或者 pivotal 透視等。右邊藍(lán)色的寫時建模是不需要去動態(tài)算 schema 的,因為 schema 已經(jīng)寫在這里了,所以每次查的時候,只要從存儲好的表里面查數(shù)據(jù)就行了。
讀時建模和寫時建模的共性是它們都是有 schema 的,只不過讀時建模的 schema 是動態(tài)的。讀時建模是用硬件的 CPU 算力去換取查詢的靈活性。假設(shè)要分析一個 user agent,如果使用讀時建模,那么只需要去定義好 user agent 字段怎么來的,field extraction 的規(guī)則是什么就可以了。但如果是寫時建模,就要多算一個 user agent 的字段,要做 schema 更改,修改數(shù)據(jù)庫的表結(jié)構(gòu)。其次,還要把所有的數(shù)據(jù)再定義一個 ETL 的任務(wù)去抽取 user agent 這個字段,通過跑批任務(wù)把數(shù)據(jù)重新填入到表中。從解決問題的角度來講都是 OK 的,但是如果經(jīng)常有 schema 變更,或者稱之為 schema 的 evolvement,那么用寫時建模代價是比較高的,因為每次調(diào)整后,還有歷史數(shù)據(jù)需要進(jìn)行重新回填等這樣很繁雜的操作。
任何一個技術(shù)都不可能是具有全面優(yōu)勢的,有好處,也會有缺陷。寫時建模最大的好處就是用空間去換時間,可以提前做很多優(yōu)化,比如可以針對字段去做一些索引,讓查詢跑得更快。甚至可以像現(xiàn)在的 MPP 數(shù)倉一樣提前做很多預(yù)計算、pre-aggregation 等加速后續(xù)的查詢。
二、讀時建模技術(shù)的特點
1、讀時建模和寫時建模的對比
大家其實不難發(fā)現(xiàn)這兩個流派的區(qū)別,如果我們要查詢靈活敏捷,就應(yīng)該選讀時建模。但是如果要求極致的性能和極致的快,而且查詢不太變化,那么就選擇寫時建模。
讀時建模有三個比較明顯的優(yōu)勢,第一個是查詢靈活敏捷;第二個優(yōu)勢是存儲空間比較小,因為寫時建模是用存儲空間去換查詢的時間,而讀時建模只存原始數(shù)據(jù),不會去建索引,也不會把抽取好的字段再次做存儲,所以能夠節(jié)省存儲的成本;第三個優(yōu)勢是寫入速度快,因為讀時建模只存原始數(shù)據(jù)不建索引,也不做很多預(yù)處理的工作,數(shù)據(jù)進(jìn)入系統(tǒng)的過程是比較輕量化的。對于寫時建模來說,數(shù)據(jù)要進(jìn)入系統(tǒng),需要做很多工作,首先要整理成合適的 schema,還要保證寫進(jìn)來的時候可能會針對性地去建一些索引、做預(yù)計算等工作。所以數(shù)據(jù)寫入的速度肯定是不如讀時建模快。
寫時建模也有自身優(yōu)勢,比如查詢速度很快。最典型的就是 BI 場景,由于 BI 分析的指標(biāo)都是固定的,查詢不會經(jīng)常變化,而且每天要看很多次,每次都希望能查詢很快。那么通過寫時建模去做好預(yù)計算,帶來的收益是非常大的。
2、讀時建模的優(yōu)勢場景
對比完讀時建模和寫時建模,讀時建模的理念是與日志日處理這個場景天然契合的。前文中提到日志處理的第一個問題是日志量大、存儲成本高,讀時建模就正好能發(fā)揮其優(yōu)勢。另外,日志格式很繁雜,不清楚接入的日志到底有多少種格式、多少個 pattern,讀時建模只要把日志先接入進(jìn)來,之后在使用中發(fā)現(xiàn)有新的 pattern 出現(xiàn)時,只需要定義新的字段提取規(guī)則,就能夠響應(yīng)各種各樣的數(shù)據(jù)格式變化需求。甚至已經(jīng)接入的日志格式有變化的時候,依舊可以通過調(diào)整讀時建模的字段提取規(guī)則,很好地去處理 schema evolvement 的情況。日志查詢的時候,有很多 Ad hoc 查詢的情況。這種查詢很多時候都是沒有出現(xiàn)在常規(guī)的業(yè)務(wù)報表查詢里面,經(jīng)常會利用一些想要用的動態(tài)字段來做過濾和加工。這種場景用讀時建模也是有天然的優(yōu)勢的,能夠節(jié)省很多的成本。
所以總結(jié)下來,讀時建模技術(shù)流派用相對廉價的硬件的 CPU、內(nèi)存這些算力,去換取了更快速的數(shù)據(jù)處理的需求落地時間,可以讓整個端到端的處理變得更加快速和便捷。
三、鴻鵠-免費的?站式異構(gòu)數(shù)據(jù)即時分析平臺
介紹完技術(shù)流派,再來回顧一下這兩個流派的日志處理產(chǎn)品。Splunk 是 schema on read 流派的開山鼻祖。但是 Splunk 整個架構(gòu)其實相對來講還是比較經(jīng)典的 MPP(Shared Nothing)架構(gòu),隨著云的 Infra 越來越成熟之后,無法滿足異構(gòu)數(shù)據(jù)處理的需求。有很長一段時間的產(chǎn)品都是以寫時建模為主,比如 ES、ClickHouse 等。到 2018 年以后,出現(xiàn)了 Grafana 的 Loki 這樣一個產(chǎn)品,它是偏向于讀時建模的。2020 年,炎凰數(shù)據(jù)推出了主打讀時建模的鴻鵠。
這里要明確一點,所有的產(chǎn)品在這里劃分的流派,只是它偏向于哪一個,不代表這個產(chǎn)品只能做這一個。比如炎凰數(shù)據(jù)平臺,雖然是主打 schema on read,但同時也有 schema on write 的能力。當(dāng)一個分析任務(wù)固化之后,schema 已經(jīng)確定了,很少改動,就可以把這些字段提取的邏輯固化下來,讓這些字段提取先預(yù)計算,并把預(yù)計算的結(jié)果先存好,用空間換時間。再比如 ClickHouse 的寫時建模很強大,它能夠讓查詢變得很快。但是 ClickHouse 最近的版本里面也提供了動態(tài)解析 JSON,即動態(tài)地去解析一個 JSON 的 column 字段,也是某種最簡單直白的 schema on read 的實現(xiàn)方法。所以總的來說,schema on read 和 schema on write 兩個手段都會需要用到。
四、鴻鵠 SQL 和鴻鵠的讀時建模引擎
1、讀時建模在炎凰的實踐
接下來就來介紹炎凰數(shù)據(jù)平臺——鴻鵠。它定位為一個一站式的異構(gòu)數(shù)據(jù)即時分析平臺。這里有三個關(guān)鍵詞:一站式、異構(gòu)和即時。
一站式指的是,整個炎凰數(shù)據(jù)平臺會像 ELK 那樣,有一個讀時建模的存儲計算的核心引擎。在這個之上有一些基礎(chǔ)的服務(wù),比如數(shù)據(jù)可視化的儀表盤服務(wù)、數(shù)據(jù)接入服務(wù)、權(quán)限管理服務(wù)、用戶認(rèn)證的告警管理服務(wù)等。它像是一個中間層的服務(wù),我們希望提供一個一站式的日志處理的開箱即用體驗,把系統(tǒng)裝好之后,就可以通過 UI 快速導(dǎo)入數(shù)據(jù),接入數(shù)據(jù)在 UI 上面就進(jìn)行查詢分析了,甚至是做一些可視化。一站式最大的好處就是降低用戶的運維成本。
第二個關(guān)鍵詞是異構(gòu),是鴻鵠很重要的一個屬性,也是讀時建模引擎來保證的。
第三個關(guān)鍵詞是即時分析,系統(tǒng)能夠很好地響應(yīng) Ad hoc 需求。并且鴻鵠上面有很多接口的擴展,我們從一開始設(shè)計系統(tǒng)的時候,就希望系統(tǒng)能夠盡可能地開放,所以我們會提供一些標(biāo)準(zhǔn)的 API,比如所有服務(wù)都有 REST API,還有一些 Java script SDK,能夠讓用戶非常方便地去把鴻鵠當(dāng)成一個底層的數(shù)據(jù)庫來使用。
2、鴻鵠中的數(shù)據(jù)存儲
下面介紹如何在鴻鵠當(dāng)中去設(shè)計讀時建模。要討論讀時建模,分為存儲和計算兩部分。
首先來介紹存儲。鴻鵠的數(shù)據(jù)存儲沒有表結(jié)構(gòu)的概念,也就是數(shù)據(jù)進(jìn)入到鴻鵠的時候,不需要定義有幾個字段,以及這些字段是整型還是字符串。所以總的來說,我們定義了數(shù)據(jù)集,大家可以把它理解為是一個數(shù)據(jù)的容器??梢园迅鞣N各樣不同格式的日志,簡單、快速地放到容器中。
我們將容器里存儲的對象抽象成了 event 事件這樣一個概念,每個日志就是一個事件,日志上包含一個時間戳,記錄了某個時點發(fā)生了什么事情。同時我們的存儲模型抽象上還做了一些元信息字段的定義,它標(biāo)識了日志是什么系統(tǒng)生成的、可能是什么格式等。
這里的時間戳字段是整數(shù)型,之所以不用存字符串形式是因為所有的查詢都會有一個時間窗口的概念,在日志分析里面時間窗是一個天然用來做過濾的條件。所以我們把時間戳提取出來,變成整數(shù),并且對時間戳的字段做一個索引,這樣能夠提供更快的查詢效率。
假設(shè)一開始數(shù)據(jù)集是空的,現(xiàn)在放了一條日志進(jìn)來,這條日志有五個字段或者列。接下來又來了兩條不同的日志,而且第二條日志除了有數(shù)據(jù)類型、主機和數(shù)據(jù)源的這幾個字段之外,還多了一個叫環(huán)境的字段,那么鴻鵠提供的可自定義擴展的元信息字段就能很好地幫你解決這個問題。后面有更多的元字段信息進(jìn)來的時候,鴻鵠平臺也是能夠支持存儲的,并且不需要在定義數(shù)據(jù)集的時候聲明。在鴻鵠平臺上,數(shù)據(jù)集的 schema 具有支持橫向擴展的特性。而且整個 schema 的變化,對用戶是無感的。因為你創(chuàng)建數(shù)據(jù)集和導(dǎo)入數(shù)據(jù)的時候,都不需要關(guān)心 schema 是什么。查詢的時候,比如有新的 environment 字段,就可以針對 environment 元信息字段去做過濾,這也是存儲引擎和計算引擎自動去保證的。對用戶來講,不需要做任何數(shù)據(jù)庫 DDL。
在存儲的邏輯模型清楚之后,再來介紹一下存儲中用到的一些技術(shù)的選型和大概的思路。
(1)按時間分片
首先面對大量的日志數(shù)據(jù),其實我們沒有辦法把所有的日志數(shù)據(jù)全存到一個文件,而是需要對數(shù)據(jù)去做分片。默認(rèn)的分片就是按照時間戳來分,因為所有的查詢都會要有時間戳過濾,這樣能夠達(dá)到比較好的查詢效率。
(2)每片數(shù)據(jù)采用列式存儲
列式存儲往往是和寫時建模系統(tǒng)一起配合使用的,為什么讀時建模也會有列式存儲呢?其實如果我們把數(shù)據(jù)集抽象成是只有這五個字段的一張表,那么每一個字段也都是按列存儲的。所以底層存的時候是把每一列都按照列式存儲下來,只不過每一個數(shù)據(jù)分片上面的列數(shù)可能不一樣。因為有 schema evolvement,所以相當(dāng)于每一個數(shù)據(jù)分片里的列式可能不同,但是每一列還是按照列式存儲。用列式存儲的初衷其實也很簡單,因為第一我們只提供分析型服務(wù),用列存能夠很好地去配合向量計算,做到數(shù)據(jù)處理的提速。這也是當(dāng)前 OLAP 系統(tǒng)的一個標(biāo)配。第二原因是使用列式存儲可以提高數(shù)據(jù)的壓縮比,數(shù)據(jù)壓縮比越高存儲成本越低。
為了支持 Ad hoc 查詢和一些快速的檢索查詢,系統(tǒng)默認(rèn)只做兩個索引。這里也是為了平衡存儲膨脹的開銷和查詢的效率所做的一個折中。因為索引做得越多查詢越快,但存儲成本也越高。但如果不建任何索引,查詢速度又會較低。所以我們綜合了日志場景中最常用的兩類查詢,創(chuàng)建了時間戳索引和倒排索引。
(3)時間戳索引
第一類時間戳索引是帶上時間范圍的,比如運維場景中,基本都是查過去 5 分鐘、半小時或者其它時間范圍內(nèi)的日志,所以我們會針對日志的時間做一個索引,每次查詢的時候,都會用到時間戳的索引來加速查詢。
(4)倒排索引
第二類是會比較輕量化的倒排索引(inverted index)。之所以叫輕量化是為了和 search engine 區(qū)分開來,搜索引擎不僅有倒排索引,還會有 scoring 和 ranking 的問題。但是在我們的系統(tǒng)里面不做 scoring 和 ranking,只是去做關(guān)鍵詞到日志的倒排。比如在上圖查詢包含了 evaluation 單詞的日志有哪些,這個時候會命中到橙色的第 1 條和第 2 條。如果查詢包含了 login 是哪一個,可以命中到第三條。在倒排索引上面,有一些簡單的與或邏輯,能夠讓查詢變得更加快速。比如要查既包含 evaluation 又包含 login,那么很快就可以回答出來是個空集,就不需要讀任何數(shù)據(jù)。之所以要做這樣的索引,是因為往后做讀時建模的從原始數(shù)據(jù)去提取字段的時候,都是要消耗 CPU 去計算,如果能在更早時候就排除掉一些完全沒有必要命中的數(shù)據(jù),就能夠節(jié)省掉這些不必要的 CPU 開銷,查詢也會非??焖?。
所以在我們的系統(tǒng)里,所有圍繞優(yōu)化的思路都會把數(shù)據(jù)的過濾盡可能地推到讀時建模、字段提取這件事情之前。比如包含關(guān)鍵字在有的時候可以推斷出來它可以隱含關(guān)鍵字過濾。比如要看某一個字段是不是等于 evaluation,首先也可以翻譯成先查詢包含 evaluation 關(guān)鍵詞的日志再去做過濾,這樣能夠盡可能更早地把這些數(shù)據(jù)過濾掉,平臺內(nèi)部會有很多的這樣的優(yōu)化手段。
3、鴻鵠里的計算引擎
接下來介紹計算部分。
(1)Schemaless SQL
采用了 SQL 接口來做計算。上圖中可以看到 From 子句后面是一個數(shù)據(jù)集,它能查詢出不同格式的日志。綠色框中是一個 key-value 結(jié)構(gòu)的日志、紅色框中是一個 JSON 結(jié)構(gòu)的日志。鴻鵠讀時建模引擎可以自動解析 key-value 和 JSON 格式的數(shù)據(jù),并且不需要做任何配置。
比如在上圖中,可以直接引用到 http.url 和 http.method,這些字段是從 JSON body(上一個圖)里面解析出來的,系統(tǒng)會自動幫你完成。同時這些 key-value 里面也會有解析出來的字段,就是我們這里拿到的 url 和 method。上圖中有兩個不同的 data type,一個是 audit 格式的審計日志,一個是 JSON 格式的 access log。自然而然會產(chǎn)生一個需求,url 和 http.url 應(yīng)該是同一個意思,我們希望它們合并到同一個列;method 和 http.method 也是同一個語義,也合并成同一個列。這在鴻鵠的讀時建模引擎中很簡單,它提供了大量的標(biāo)量函數(shù),做字段的轉(zhuǎn)換和變換。
比如上圖中這個簡單的例子,可以用 coalesce() 函數(shù)把 URL 字段和 HTTP 的 URL 字段歸一化成一個 URL 字段。大家會發(fā)現(xiàn),其實 SQL 表達(dá)的就是一個讀時建模,它建立的模型是把剛才 audit log 和 access log 中的 URL 字段和 method 字段整理成一個表的結(jié)構(gòu),一旦成表結(jié)構(gòu)之后,這個表就是一個所謂的模型。它們的理念其實特別相似,編程里面有個 Duck typing(鴨子類型)的問題,就是建完模型之后不必關(guān)心底層數(shù)據(jù)到底是從哪里來的,有 URL 就可以用 URL 這個字段。就像鴨子類型一樣,反正看起來像鴨子,就把它當(dāng)鴨子用就好了。就是這樣一個理念,只不過在 ETL 的世界里面,我們要先在數(shù)據(jù)庫里定義一個表結(jié)構(gòu),并把 SQL 的邏輯寫成 ETL 流水線,然后再把數(shù)據(jù)插入進(jìn)來。
(2)標(biāo)量函數(shù)+表函數(shù)
我們再來看一些更復(fù)雜有趣的事情。日志外層是 key-value 的結(jié)構(gòu),但是在 header 字段里面,其實是一個 JSON 的結(jié)構(gòu)。在分析日志的時候往往很難在第一次就定義好需要哪些字段。除了用 header 字段之外,隨著業(yè)務(wù)的演進(jìn)或者問題排查的深入之后,還要用 header 里面的字段。鴻鵠讀時建模引擎提供了標(biāo)量函數(shù)和表函數(shù)這樣的概念,幫助大家使用 SQL 來做字段的變換和提取。
(3)用 SQL 做字段提取
具體的做法很簡單,大家可以看到其實很簡單的一些 SQL 就是 parse_json 這樣的一個功能。parse_json 的核心就是把 JSON 的 header 字符串 flatten 開來,展開成我們要的字段。在展開之后,我們可以很容易地去對這些字段進(jìn)行過濾。因為一旦它形成字段之后,就跟數(shù)據(jù)庫里表的字段沒有任何區(qū)別了。
另外值得一提的是,第一行數(shù)據(jù)的 JSON 其實是沒有 accept、encoding、Referer 這些的,因為 JSON 字段相對比較少。但是在第二行的 JSON 里面字段是很多的,在用 parse_json() 函數(shù)展開之后,讀時建模引擎會自動去做兩個展開表的 Schema 統(tǒng)合,我們稱之為 union 的工作。而且會把這一行上不存在的字段變成 null 值,我們就可以很快地做出一張大寬表了,而做這個表的時候也不需要特別定義很多的表結(jié)構(gòu),這一切都是 schema 的 involvement,都由讀時建模引擎來處理。
(4)SQL函數(shù)
大量的函數(shù)在讀時建模時都要經(jīng)常被調(diào)用,所以這些函數(shù)的性能是非常重要的。為了保證這些函數(shù)具備比較好的性能,我們一方面會盡可能地用向量計算,還有一些函數(shù)會去做 JIT 加速,把它編譯成一些更快的可執(zhí)行字節(jié)碼,而不是用編譯執(zhí)行單元的方式來做。這些都是一些很細(xì)節(jié)的優(yōu)化。
(5)SQL CTE
還有一個概念是數(shù)據(jù)處理的流水線(pipeline)。很多小伙伴可能感覺 SQL 很難寫,因此我們在讀時建模引擎里面支持了一個叫 CTE 的語法,上圖中的 SQL 語句就是個典型的 CTE 語法。如果查詢復(fù)雜,比如三步四步,我們就把每一步寫在這個 common table expression 里面就可以了,會串行執(zhí)行,依次執(zhí)行第一步、第二步、第三步等等。
這樣書寫的一個好處是邏輯上代碼更可讀,第二是調(diào)試的時候也更簡單。
(6)復(fù)用建模邏輯
讀時建模的模型是一段 SQL,要將其固化和存儲下來也是一個必須要解決的問題。我們在 SQL 引擎里也有創(chuàng)建視圖這樣的功能,可以把一個模型的封裝邏輯寫在視圖中。這樣就可以把建模的邏輯和后續(xù)基于模型的分析的邏輯做一個比較好的解耦,當(dāng)建模的邏輯有變化時,只需要去調(diào)整視圖的邏輯即可,不需要去更改后續(xù)的分析邏輯。
(7)查詢加速
鴻鵠平臺也會去想辦法在寫時建模的理念下去做一些查詢加速,其中最典型的一個做法就是物化視圖。比如我們把 url、method 這樣的一個視圖變成物化視圖,這些 url 和 method 就會被實際存在物理磁盤上。下次再查詢的時候,就不需要再計算了,是用空間去換時間。
這里的物化視圖與一些數(shù)據(jù)庫的物化視圖有一點不一樣。我們的物化視圖自帶時序?qū)傩裕?dāng)發(fā)現(xiàn)有新的數(shù)據(jù)進(jìn)入 my_eventset 的時候就會去調(diào)用相應(yīng)的物化視圖邏輯去把最新的數(shù)據(jù)物化。這對于終端用戶是透明的,所以對于用戶來說只是定義了一個物化視圖的模型,后續(xù)會發(fā)現(xiàn)查詢這個模型的速度比普通視圖要更加快捷。