更多技術交流、求職機會、試用福利,歡迎關注位元組跳動資料平臺微信公眾號,回復【1】進入官方交流群
需求背景
資料探查上線之前,資料驗證都是通過寫 SQL 方式進行查詢的,從撰寫 SQL,到決議運行出結果,不僅時間長,還會反復消耗計算資源,探查上線后,只需要一次探查,就可以得到整張表的探查報告,但后續我們還發現了一些問題,主要有三點:
-
無法看到探查的資料明細以及關聯的行詳情,無法對資料進行預處理操作,
-
探查還是需要資源調度,等待時長平均分鐘級,
-
與質量監控沒有打通,探查資料的后續走向不明確,
針對這些問題,我們進一步開發了動態探查需求,解決的問題如下:
-
基于大資料預覽的探查,支持對資料進行函式級別的預處理,
-
探查結果秒級更新,實時回應,
-
與資料監控打通,探索 SQL 的生成模式,
本文主要介紹動態探查的應用場景和相關的技術實作,
應用場景
探查主要應用在元資料管理,資料研發,數倉的開發以及資料治理,可為對資料質量有需求的場景提供資料質量的發現和識別能力,目標用戶除了研發同學,也包含不是以 SQL 研發為主的群體,比如演算法建模和資料挖掘等領域,
探查可以有效的打通三個倍訓:
-
元資料管理 -> 探查 -> 資料預覽探查(庫表的質量報告)
-
資料監控 <-> 資料探查
-
動態探查 -> SQL -> 資料開發 -> 除錯 -> 探查報告(質量分析)
名詞解釋
-
全量探查:基于庫表的全量探查,后端引擎執行,展示探查后列的統計分布結果,
-
動態探查:基于抽樣的部分資料探查,展示欄位明細,可以使用操作對資料進行預處理,并實時動態的展示統計分布結果,資料獲取后的程序都由前端執行,
兩者的對比示意圖
技術實作
除了資料的抽樣部分在后端做,其他的都是前端實作的,包括大資料展示,探查計算,卡片聯動,操作堆疊互動,以及未來要做的函式編輯器以及 SQL 生成,
技術架構
-
抽樣能力:對資料進行基于質量分布特征的抽取,目前做的是隨機抽樣,后續嘗試基于特征來抽樣,
-
資料展現:大容量的資料載體,支持對資料處理的實時展現,前端目前是基于虛擬滾動 Table 做的,后續打算遷移到 canvas table 上,
-
前端探查:實時探查,可視化展現資料分布,突出質量指標,資料處理能力:函式處理能力(GroupBy..)
-
操作堆疊:需要對資料操作進行管理和回溯基于 immutable 和操作流實作操作堆疊,
-
編輯器:提供完整函式的功能,需要:詞法決議,智能提醒,語法高亮,基于編輯器實作函式的功能,antlr4 實作詞法決議,配合 monaco editor 實作一些智能提醒和語法高亮,
-
生成 SQL:將可視化的互動式操作轉換成可執行的 SQL,
目前 sql generator 有以下幾種方式:
-
基于鏈式呼叫生成
-
基于標簽模板生成
-
基于 AST(抽象語法樹)去做
關鍵技術及實作
大資料渲染
由于動態探查場景下前端需要支持最大 5000 條資料的展示和互動,所以在渲染這塊存在比較大的壓力,主要集中在探查卡片和資料預覽兩個部分,
探查卡片包含了特定列的部分關鍵資訊匯總,比如 0 值、Null 值、列舉值等,如下圖紅框部分:
圖片探查卡片部分由于存在較多定制化內容,所以采用了虛擬串列方案進行渲染,支持收起狀態和展開狀態:
圖片資料預覽部分展示的是探查的全部資料集合,可以快速查看原始資料的詳細內容,由于內容同質化比較高,所以資料預覽采用的是基于團隊內部維護的 canvas 版本 Table 方案進行渲染,如下圖紅框部分:
卡片聯動
由于卡片和資料預覽列的寬度差異較大,并且上下兩部分滑動是獨立的,造成在選擇查看某個具體列的時候,上下對齊位置會比較麻煩,為了解決這個問題,這塊增加了自動定位功能,演示效果如下:
這部分需要解決的問題有兩個:卡片中間點坐標計算和自動定位邏輯,
中間點坐標計算邏輯如下:
// 計算卡片中點坐標 index是卡片序號,adsorbSider表示是否吸邊
getCardCenter(index: number, adsorbSider?: boolean) {
...
// 獲取卡片資訊
const cardBox: IBaseBox = this.cardList[index];
// 獲取列資訊
const colBox: IBaseBox = this.colList[index];
const clientWidth = getClientWidth();
if(adsorbSider) {
// 吸邊處理
if(cardBox.offset < this.cardScroll) {
return cardBox.offset;
}
if(cardBox.offset + cardBox.width - this.cardScroll > clientWidth) {
return cardBox.offset + cardBox.width - clientWidth;
}
return this.cardScroll;
}
return getTargetPosition(colBox, this.tableScroll, cardBox);
}
// 獲取滾動目標位置
// originBox: 滾動起始物件
// originScroll: 滾動起始左側scroll
// targetBox: 滾動結束物件
const getTargetPosition = (originBox: IBaseBox, originScroll: number, targetBox: IBaseBox) => {
const clientWidth = getClientWidth();
if(!originBox || !targetBox) return 0;
let offsetLeftSider = Math.max(originBox?.offset - originScroll, 0);
if(offsetLeftSider + targetBox.width >= clientWidth) {
if(targetBox.offset + targetBox.width > clientWidth) {
// 此處容易出現吸邊
return targetBox.offset + targetBox.width - clientWidth;
} else {
return 0;
}
}
const scroll = targetBox?.offset - offsetLeftSider + (targetBox.width - originBox.width) / 2;
return Math.max(
Math.min(targetBox.offset, scroll),
0
);
}
獲取到中點坐標后,自動定位需要符合如下規則:
-
選中卡片后,表格要自動滾動定位到下方居中對齊,無法滿足對齊標準的,盡量靠近選中卡片位置,
-
選中表格列后,卡片要自動滾動定位到上方居中對齊,無法滿足對齊標準的,盡量靠近選中表格位置,
-
搜索選中列后,卡片和表格要自動滿足上面兩個規則,并滾動到可視區域內,
規則中有幾種邊界情況,參考下圖:
居中對齊是對于卡片和列寬在 scroll 距離允許情況下的理想對齊方式,貼邊對齊是針對卡片在起始和結束位置 scroll 不足以滿足居中對齊要求時候的對齊方式,除此之外還有一種是卡片的寬度遠大于列寬,并且不是起始或者結束位置的時候所采取的對齊方式,如下如卡片 B 因為無法滾動,卡片 A 的寬度又占據了底部第二列的一部分,所以此時卡片 B 只能高亮和底部的列進行對齊,
操作堆疊
動態探查支持了對于探查結果的基礎分析能力,比如列洗掉、過濾、排序等,如下圖紅框部分:
圖片用戶對于探查結果的每一次操作都會被記作一次操作,多次操作串聯起來形成操作堆疊,可以自由的修改或者刪減操作堆疊里的操作,并實時查看最新結果,以過濾操作演示效果如下:
圖片操作堆疊部分需要處理的問題主要有以下幾點:
-
如何管理多種操作進行串行計算
這里把所有操作都抽象成了Input + Logic = Ouput的結構,Input 是輸入引數,此處可以是指某一列的資料、上一步操作的結果或者其他計算值,Logic 是操作的具體邏輯,負責根據 Input 轉換生成 Output,Output 可以作為最終結果進行渲染,也可以再次進入下一環節參與計算,拿列洗掉操作舉個栗子,下面是大體代碼實作:
class ColDelOpt {
run = (params: IOptEngineMetaInfo) => {
// 操作Input部分
const {
columns = [],
dataSourceMap = {}
} = params;
const {
fields = []
} = this.params;
// 操作Logic部分
const nextColumns = columns.filter((item) => !fields.includes(item.name));
// 操作的Output
return {
columns: nextColumns,
dataSourceMap
}
}
}
可以看到 ColDelOpt 內部有一個 run 方法,該方法支持傳入一個包含了列資訊 columns 和資料集 dataSourceMap 的 params 物件,此處 params 即被抽象的外部輸入引數 Input,run 方法內部的邏輯部分即被抽象的 Logic 部分,最后方法回傳值包含了最新的 columns 和 dataSourceMap,即為 Output 部分,基于這種結構,用戶所有的操作都可以被初始化成不同的 Opt 實體,由操作引擎統一呼叫實體的 run 方法,并傳入所需的引數,最終得到計算結果,
-
某個操作被修改后如何進行二次計算
操作堆疊的計算是由計算引擎來完成的,引擎負責根據外部事件,來自動執行現有操作的資料處理作業,引擎執行流程和大體代碼如下:
// 操作引擎
class OptEngine {
// 操作串列
private optList: IOptEngineItem[] = [];
// 原始資料
private metaData: IOptEngineMetaInfo = {
columns: [],
dataSourceMap: {},
};
// 執行算子
optRun = () => {
let {
columns = [],
dataSourceMap = {}
} = this.metaData;
if(!this.optList.length) return {
columns,
dataSourceMap
};
for(let index = 0; index < this.optList.length; index++) {
// 讀取操作算子
const optItem = this.optList[index];
let startTime = performance.now();
try {
// 執行算子計算
const result = optItem.run({
columns,
dataSourceMap
});
// 更新算子結果
columns = result.columns || [];
dataSourceMap = result.dataSourceMap || {};
} catch(e) {
// 報錯后直接直接回傳
return {
columns,
dataSourceMap,
// 裝填報錯資訊
errorInfo: {
key: optItem.key || '',
message: e.message
}
}
}
}
return {
columns,
dataSourceMap,
}
}
autoRun = (
metaInfo: IOptEngineMetaInfo,
optList: IOptItem[],
callback: (params: IAutoRunResult) => void
) => {
// 裝填資料
this.setupMetaData(metaInfo);
// 裝填操作堆疊
this.setupOptList(optList.map((item) => {
// 行過濾
if(item.type === OPT_TYPE.FILTER) {
return new FilterOpt({
key: item.key,
params: item.params
})
}
// 其余型別操作
...
// 默認原值回傳
return new IdentityOpt({
key: item.key,
})
}));
// 執行操作計算
const result = this.optRun();
// 回傳資料
return {
// 計算列
columns: result.columns,
// 執行結果
dataSource: Object.entries(result.dataSourceMap).map(([key, value]) => ({
field: key,
value
})),
// 操作堆疊執行例外資訊
errorInfo: result.errorInfo
};
}
}
應用實踐
以一個小例子來演示下動態探查的使用,前端開發程序中,有一個真實的場景,我們為了排查一個豎屏顯示幕的 bug(1080*1920),想找到關聯的用戶,看其分布情況,就可以很方便的用動態探查去尋找,
后續計劃
關注動態探查的操作豐富性以及之后的資料走向,比如離線資料匯出,和生成 SQL 等,技術方向上主要放在以下幾個方面:
-
更多的探查型別和圖表支持動態探查目前支持空值,列舉值,零值,資料統計等基礎的探查功能,未來會計劃支持包括 map,json,time,sql 陳述句等型別的識別和探查,同時提供更豐富的圖表支持,
-
操作堆疊的編輯器體驗動態探查目前還是以類 Excel 的操作為主,未來主要提供編輯器級別的操作體驗,可以提供 HSQL 支持的大部分函式,包括支持多表 join 功能,
-
操作流程的 SQL 生成動態探查目前的 SQL 能力還未建設完成,會在未來結合編輯器級別的操作,并支持多表,配合詞法決議功能,提供更精準的生成 SQL 能力,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/499674.html
標籤:大數據
上一篇:索引的樹結構
下一篇:重新學習資料庫(1)
