中高頻多因子庫儲存最佳實踐

語言: CN / TW / HK

 1. 概述

因子挖掘是量化交易的基礎。隨著量化交易競爭的加劇,量化投資團隊需要處理大量因子。在許多情況下,因子資料量甚至會遠遠超過高頻的行情資料量。以 5,000 只股票 10,000 個因子為例,一年的 10 分鐘線資料量為 2.3TB,1分鐘線資料量為 23TB,3秒線資料量為 460 TB。如此量級的資料就對因子儲存方案提出了很高的要求。

本文將基於中高頻多因子儲存場景,結合實際資料案例來設計 DolphinDB 儲存方案,並對比不同儲存模式下的效能,給出最佳儲存模式的建議。

1.1 中高頻多因子儲存面臨的挑戰

在資料高頻次和因子高數量的雙重疊加之下,資料量將輕易達到 TB 級別,那麼中高頻多因子的儲存方案就必須同時面對以下問題:

  • 龐大的資料量

因子計算通常有4個維度包括股票、因子、頻率和時間。國內股票總個數按5,000來算。因子個數一般機構大約為1,000起,多的甚至有10,000個因子。時間頻率最高的是每3秒鐘生成一次資料,頻率低的也有10分鐘一次,也就是說,一隻股票一個因子一天會生成24到4,800條 tick 資料。

寬表儲存模式資料量統計:

股票(只) 因子(個) 頻率 時間 資料量(GB)
5,000 10,000 10 分鐘線 4 年 9,014
5,000 1,000 1 分鐘線 2 年 4,513
5,000 1,000 3 秒線 1 年 45,129

窄表儲存模式資料量統計:

股票(只) 因子(個) 頻率 時間 資料量(GB)
5,000 10,000 10 分鐘線 4 年 27,037
5,000 1,000 1 分鐘線 2 年 13,518
5,000 1,000 3 秒線 1 年 134,183

面對如此龐大的資料量,如何保證高效的資料寫入和資料壓縮是因子庫儲存的一大挑戰,如果不能支援並充分發揮多塊磁碟的 I/O,寫入耗時將達數小時以上。

  • 因子庫動態變化

因子庫經常會發生變化,往往需要新增因子、修改因子定義,或加入新的股票等。面對 TB 級的因子資料,單個因子的新增、修改、刪除耗時應該保證在秒級才能確保量化投研的效率。

對於上述問題,我們在設計方案時,除了儘可能優化每一項操作的效能,更重要的是每項效能不能出現明顯短板,否則在資料操作量級大幅上升後,會大幅度降低整體的生產效率。

  • 多因子對齊輸出

金融行業的資料分析通常需要資料支援面板格式。對於讀取隨機標的(A 股市場目前約5,000只股票)、隨機多個因子(10,000個因子中隨機查詢1,000個因子)的場景,需要在操作時能夠儘可能高速並精準讀取資料,減少無效 I/O ,並以需要的方式(通常是因子面板模式)將資料讀取出來。

1.2 中高頻多因子儲存場景需求

總體來看,中高頻多因子的儲存方案需要滿足以下幾點:

  • 保證寫入高效性;
  • 支援高效的因子庫運維(新增因子及因子資料的修改、刪除);
  • 支援高效、靈活的讀取方式,且以最適合金融計算的方式輸出。

2. DolphinDB 的儲存特性

面對中高頻多因子儲存場景,我們將 DolphinDB 的下述儲存特性組合使用,提供了靈活的儲存解決方案。

2.1 分割槽儲存

資料分割槽儲存,針對因子資料可以採用“時間+因子/標的”的方式組合分割槽來達成因子資料的靈活分割槽儲存。DolphinDB 對於不同分割槽資料可以多執行緒並行操作。

2.2 分割槽內分組排序儲存

DolphinDB 的 TSDB 引擎提供排序鍵設定,對分割槽內的資料完成分塊索引。這樣的設定有助於在隨機讀時更精準的定位資料。例如,在“2022年2月-factor1”的分割槽內部,資料可以按照 SortColumn-SortKey 欄位 SecurityID 分組儲存,在每個 SecurityID 組內,再按照時間欄位 TradeTime 排序,如下圖所示。

藉助以上特性,可以靈活地設計儲存方案,以應對中高頻多因子場景下的不同需求。

下文中,我們以一個“10分鐘級100000因子”的例子,為大家測試在不同儲存模式下,資料的寫入、查詢和運維等方面的效能,並通過分析結果,為大家提供一箇中高頻多因子庫儲存的最佳實踐。

3. 十分鐘級一萬因子儲存場景解決方案

3.1 儲存方案設計

在儲存因子資料時,使用者可以選擇窄表和寬表兩種模式。

窄表模式一般有4列:時間戳、股票程式碼、因子編號以及因子值,如下圖所示。在需要面板資料的場景中,窄表模式的資料可使用 DolphinDB 的 pivot 功能轉換為面板資料。面對金融場景時序資料中大量因子需要對齊轉置的情形,可以根據時間、股票程式碼和因子對資料表重新排序,將時間和股票程式碼作為行,因子作為列進行計算輸出,並且非常高效。

寬表模式中,一般每個因子存為一列,如下圖所示。寬表模式下的面板資料可以直接用於量化交易中的程式計算,符合金融場景的資料輸出需求,但在測試案例一三種運維操作的測試資料對比中,我們會看到,因子資料的新增和修改場景下寬表耗時較高。

DolphinDB 中同時支援寬表和窄表的兩種模式資料儲存。結合 DolphinDB 的儲存特性,我們設計以下兩種儲存方案來比對10分鐘級10,000因子場景儲存效能:

方案 1:窄表模式

  • TradeTime 按月值分割槽 + FactorName 值分割槽
  • 排序欄位: SecurityID + TradeTime

把時間分割槽調整到月,對因子分割槽調整到每個因子單獨分割槽,並對每個分割槽內的資料按照 SecurityID 分組,組內按照 TradeTime 排序。這樣的好處是每個分割槽資料大小適合,在資料檢索時,既可以按照時間和因子名進行分割槽剪枝幹,又可以按照股票 ID 近一步的精確定位資料,滿足在隨意組合因子、標的場景下精準地讀取資料。

方案 2:寬表模式

  • TradeTime 按月值分割槽 + SecurityID 值分割槽
  • 排序欄位: SecurityID + TradeTime

在 SecurityID 上進行分割槽剪枝,因子維度上通過選擇不同的列來進行資料篩選。

下文將在固態硬碟(SSD)和機械硬碟(HDD)這兩種不同的硬體配置下,對寬表和窄表的儲存效能分別進行測試。

3.2 資料準備

我們通過模擬隨機生成5,000只股票10分鐘級10,000個因子的資料,並分別採用窄表和寬表兩種方式來儲存。

隨機生成因子名稱和股票程式碼的函式定義如下:

def createFactorNamesAndSymbolNamse(num_factors,num_symbols){
	factor_names = lpad(string(1..num_factors),6,"f00000")
	symbols_preliminary = lpad(string(1..num_symbols),6,"000000")+"."
	areas = rand(["SZ","SH"],num_symbols)
	symbols = symbols_preliminary + areas
	return factor_names,symbols
}

生成欄位及欄位型別的函式定義如下:

def createColnameAndColtype(mode,factor_names){
	if(mode == "single"){
		return ["tradetime","symbol","factorname","value"],[DATETIME,SYMBOL,SYMBOL,DOUBLE]
	}else{
		col_names = ["tradetime","symbol"].append!(factor_names)
		col_types = [DATETIME,SYMBOL].append!(take(DOUBLE,factor_names.size()))
		return col_names,col_types
	}
}

3.3. 測試案例一:HDD 儲存

伺服器配置:

  • CPU:64核
  • 記憶體:512G
  • 磁碟:9塊 HDD 硬碟
  • 資料庫設定:單機叢集三資料節點

因子寫入

對於5,000只股票10,000個因子,我們首先測試寫入2022.1.1至2022.1.31內的10 鍾級資料,可以執行如下程式碼定義不同儲存模式下的因子寫入函式:

// 窄表模式寫入某個時間範圍資料
def writeSingleModelData(dbname,tbname,start_date,end_date,symbols,factor_names){
	total_time_range = getTimeList(start_date,end_date)
	nodes = exec value from pnodeRun(getNodeAlias)
	for(j in 0..(total_time_range.size()-1)){
		for(i in 0..(factor_names.size()-1)){
			rpc(nodes[i%(nodes.size())],submitJob,"singleModel"+j+"and"+i,dbname,singleModelPartitionData,dbname,tbname,total_time_range[j],symbols,factor_names,factor_names[i])
		}
	}
}

// 寬表模式寫入某個時間範圍資料
def writeWideModelData(dbname,tbname,start_date,end_date,symbols,factor_names){
	total_time_range = getTimeList(start_date,end_date)
	nodes = exec value from pnodeRun(getNodeAlias)
	for(j in 0..(total_time_range.size()-1)){
		for(i in 0..(symbols.size()-1)){
			rpc(nodes[i%(nodes.size())],submitJob,"wideModel"+j+"and"+i,dbname,wideModelPartitionData,dbname,tbname,total_time_range[j],factor_names,symbols[i])
		}
	}
}

可以看到,寬表模式在資料寫入速度上優於窄表模式,硬碟佔用空間上略優於窄表模式。這是因為窄表模式下的資料冗餘度高,實際資料量比較大。另外需要說明一點,實驗中因子值使用的是隨機的浮點數,幾乎沒有重複,壓縮比較低,實際場景中的壓縮比會更高。

儲存方案 寫入天數 每天行數 總行數 每行位元組 資料原始大小 (GB) 落盤大小 (GB) 寫入耗時 (s) 壓縮比 磁碟 I/O(MB/s)
寬表 21 120,000 2,520,000 80,012 190 166 301 1.1 648
寬表 21 120,000 2,520,000 80,012 190 166 301 1.1 648

因子查詢

查詢21天全市場5,000只標的的1,000個因子資料,窄表的查詢會將資料轉換成與寬表一樣的面板資料輸出。定義因子查詢函式的核心程式碼如下:

// 窄表模式查詢隨機1000因子
def querySingleModel(dbname,tbname,start_time,end_time,aim_factor){
	return select value from loadTable(dbname,tbname) where tradetime>=start_time and tradetime<= end_time and  factorname in aim_factor pivot by tradetime,symbol,factorname
}

// 寬表模式查詢隨機1000因子
def queryWideModel(dbname,tbname,start_time,end_time,aim_factor){
	ll = aim_factor[0]
	for(i in 1..(aim_factor.size()-1)){
		ll = ll+","+aim_factor[i]
	}
	script = "select tradetime,symbol,"+ll+"from loadTable("+'"'+dbname+'"'+","+'"'+tbname+'"'+")" + "where tradetime>="+start_time+"and tradetime<="+end_time
	tt = parseExpr(script).eval()
	return tt
}

資料以寬表模式儲存在機械硬碟中時,對10,000個因子隨機查詢1,000個因子的初次查詢速度慢一些;查詢前1,000個因子則速度較快。這是因為在機械硬碟下進行多列的隨機檢索會比較慢。

即便如此,與寬表模式的最快情況相比,窄表模式下經過 pivot by 轉為面板資料後的查詢速度也要更快。這是因為雖然寬表的所有列已經按照面板模式準備好,但是寬表的資料都是在同一個分割槽,讀取時是單執行緒進行。而窄表模式資料存在於很多因子分割槽 ,讀取資料和拼接面板資料時是很多個 CPU 同時工作,這是個分散式多執行緒操作,所以窄表模式在查詢面板資料時耗時更少。

儲存方案 因子選擇 資料大小 (GB) 冷查詢耗時 (s) 熱查詢耗時 (s)
窄表 隨機 1,000 18.8 80 52
寬表 隨機 1,000 18.8
寬表 窄表 18.8 425 49

資料運維

因子資料的運維包括新增因子、更新因子、刪除因子。

  • 新增因子

在新增因子的場景,窄表模式可以使用 append! 插入新的因子資料;而寬表模式需要先進行 addColumn 操作,然後通過 update 操作更新因子列資料。在 DolphinDB 當前的設計下,更新寬表模式中某一列因子,需要將分割槽資料全部重寫,耗時較長

假設此處需要新增第 f10002 號因子在2022.1.1至2022.1.31時間範圍內的資料,不同儲存模式下的新增因子指令碼如下所示:

//窄表模式新增1個因子
def singleModelAddNewFactor(dbname,tbname,start_date,end_date,symbols,factor_names,new_factor){
	time_list = getTimeList(start_date,end_date).flatten()
	num_row = symbols.size()*time_list.size()
	col_names,col_types = createColnameAndColtype("single",factor_names)
	t = table(num_row:num_row,col_names,col_types)
	t["tradetime"] = stretch(time_list,num_row)
	t["symbol"] = take(symbols,num_row)
	t["factorname"] = take(new_factor,num_row)
	t["value"] = rand(100.0,num_row)
	pt = loadTable(dbname,tbname)
	pt.append!(t)	
}

//寬表模型新增一個因子
def wideModelAddNewFactor(dbname,tbname,start_date,end_date,symbols,new_factor,parallel = true){   //parallel=true表示並行,=false表示序列
	pt = loadTable(dbname,tbname)
	addColumn(pt,[new_factor],[DOUBLE])
	time_list = getTimeList(start_date,end_date)
	start_time_list,end_time_list = [],[] 
	for(i in 0..(time_list.size()-1)){
		start_time_list.append!(time_list[i][0])
		idx = time_list[i].size()-1
		end_time_list.append!(time_list[i][idx])
	}
	if(!parallel){
		for(i in 0..(start_time_list.size()-1)){
			for(j in 0..(symbols.size()-1)){
				wideModelSinglePartitionUpdate(dbname,tbname,start_time_list[i],end_time_list[i],new_factor,symbols[j])
			}
		}
	}else{
		for(i in 0..(start_time_list.size()-1)){
			ploop(wideModelSinglePartitionUpdate{dbname,tbname,start_time_list[i],end_time_list[i],new_factor,},symbols)
		}
	}
}
  • 更新因子

量化投研中,重新計算因子資料是常見的場景。根據窄表模式下的分割槽規則,對指定因子資料更新時,可以精確定位到因子所在分割槽,並進行修改,所以耗時在秒級;而寬表模式的更新方式如上節所述原因,耗時較長。

假定此處需要更新第 f00555 號因子在2022.1.1至2022.1.31時間範圍內的因子值資料,不同儲存模式下的指令碼如下所示:

//窄表模式更新1個因子
def singleModelUpdateFactor(dbname,tbname,start_date,end_date,update_factor,parallel = false){   //parallel=true表示並行更新
	time_list = getTimeList(start_date,end_date)
	start_time_list,end_time_list = [],[] 
	for(i in 0..(time_list.size()-1)){
		start_time_list.append!(time_list[i][0])
		idx = time_list[i].size()-1
		end_time_list.append!(time_list[i][idx])
	}
	if(!parallel){
		for(i in 0..(start_time_list.size()-1)){
			singleModelSinglePartitionUpdate(dbname,tbname,start_time_list[i],end_time_list[i],update_factor)
		}		
	}else{
		ploop(singleModelSinglePartitionUpdate{dbname,tbname,,,update_factor},start_time_list,end_time_list)
	}
}

//寬表模型更新1個因子
def wideModelUpdateFactor(dbname,tbname,start_date,end_date,update_factor,symbols,parallel = true){  //parallel=true表示並行更新,=false表示序列
	time_list = getTimeList(start_date,end_date)
	start_time_list,end_time_list = [],[] 
	for(i in 0..(time_list.size()-1)){
		start_time_list.append!(time_list[i][0])
		idx = time_list[i].size()-1
		end_time_list.append!(time_list[i][idx])
	}
	if(!parallel){
		for(i in 0..(start_time_list.size()-1)){
			for(j in 0..(symbols.size()-1)){
				wideModelSinglePartitionUpdate(dbname,tbname,start_time_list[i],end_time_list[i],update_factor,symbols[j])	
			}
		}
	}else{
		for(i in 0..(start_time_list.size()-1)){
			ploop(wideModelSinglePartitionUpdate{dbname,tbname,start_time_list[i],end_time_list[i],update_factor,},symbols)
		}
	}
}
  • 刪除因子

刪除因子雖然不是必須的,但可以釋放儲存空間,以及提供其他便利。當前窄表模型的分割槽方案在刪除指定因子時耗時在秒級,指令碼如下所示,TSDB 引擎下的寬表模式目前不支援刪除因子列。

// 單值模型刪除一個因子
def singleModelDeleteFactor(dbname,tbname,start_date,end_date,delete_factor){
	pt = loadTable(dbname,tbname)
	time_list = getTimeList(start_date,end_date).flatten()
	start_time,end_time = time_list[0],time_list[time_list.size()-1]
	delete  from pt where tradetime >= start_time and tradetime <= end_time and factorname = delete_factor
}

三種運維操作下的測試資料如下表所示,可以看到在10分鐘級10,000個因子資料場景下,窄表模式在因子資料查詢和因子資料運維方面全面優於寬表模式,只是在資料寫入速度和儲存空間要遜於寬表模式。綜合考慮各個方面,使用窄表模式儲存因子資料是更好的選擇。

資料運維操作 窄表 (s) 寬表 (s)
新增 1 因子 1.2 534
更新 1 因子 1.1 541
刪除 1 因子 0.8 N/A

3.4. 測試案例二:SSD 儲存

上一節中,我們使用機械硬碟比對了一個月因子資料場景下寬表和窄表的效能。在實際生產時,為了提高效率,我們往往選擇 SSD 硬碟來儲存因子資料。本小節我們就選擇 SSD 硬碟來進行測試,進行6個月因子資料場景下寬表和窄表的效能比對。同樣從因子寫入、因子查詢和資料運維三個方面進行測試。

伺服器配置:

  • CPU 48 核
  • 記憶體:512G
  • 磁碟:4 塊 SSD 硬碟
  • 資料庫設定:單機叢集二資料節點

因子寫入

通過多工並行方式寫入6個月5,000只標的10分鐘級10,000 子資料,定義因子寫入函式的程式碼與案例一中因子寫入一致,完整指令碼可參考附件。從結果我們可以看到,寬表模式在寫入速度和儲存空間上效能佔優。

儲存方案 寫入天數 每天行數 總行數 每行位元組 資料原始大小 (GB) 落盤大小 (GB) 寫入耗時 (s) 壓縮比 磁碟 I/O(MB/s)
方案 1 - 窄表 129 1,200,000,000 154,800,000,000 24 2,873 1,118 2,194 2.6 1,338
方案 2 - 寬表 129 120,000 15,480,000 80,012 1,143 1,030 1,115 1.1 1,049

因子查詢

接下來我們隨機查詢5,000只標的下1,000個因子在1, 3, 6個月內的資料,核心查詢程式碼與案例一中一致,完整指令碼可參考附件。

測試結果如下所示,其中冷查詢表示在無資料快取的情況下進行查詢,熱查詢表示有資料快取的情況下進行查詢 。可以看到,在使用 SSD 磁碟的情況下,窄表模式的查詢耗時同樣低於寬表模式。此外隨著查詢資料量增長,查詢耗時是線性增長的,不會因為查詢資料量的大增而出現查詢耗時大幅增加的情況。

儲存方案 查詢資料大小 (GB) 查詢資料月份 冷查詢耗時 (s) 熱查詢耗時 (s)
寬表 18.8 1 59 43
寬表 18.8 3 59 43
窄表 57.3 3 103 90
寬表 57.3 3 171 115
窄表 115.5 6 201 173
寬表 115.5 6 363 274

資料運維

同樣,我們從新增、更新和刪除因子三個角度,測試1, 3, 6個月的資料運維效能,核心程式碼與案例一中一致,完整指令碼可參考附件。在這個環節窄表模式同樣遠遠優於寬表模式,且資料運維的各項操作,耗時同樣隨操作影響資料量線性增長。

資料運維操作 操作因子資料月份 窄表 (s) 寬表 (s)
新增 1 因子 1 1.0 185
新增 1 因子 3 2.9 502
新增 1 因子 6 5.8 1,016
更新 1 因子 1 1.1 162
更新 1 因子 3 3.1 477
更新 1 因子 6 6.2 948
刪除 1 因子 1 0.8 N/A
刪除 1 因子 3 1.1 N/A
刪除 1 因子 6 1.2 N/A

吞吐量

在案例一中我們提到,窄表模式下查詢面板資料時,任務會以分散式多執行緒的方式處理,這將消耗較多的 CPU 資源。

為了驗證在多執行緒併發查詢因子資料時,查詢效能是否會因為 CPU 資源競爭大幅下降,我們進行了併發查詢測試。

我們採取併發8個執行緒查詢一個月全市場5,000只股票、隨機10,000個因子資料,每個查詢資料大小為 19GB,測試結果如下表。

儲存方案 總耗時(s)
窄表 247
寬表 242

通過比對我們可以看到,在8個併發查詢的場景,窄表模式的查詢確實有一定下降,但總耗時仍然可以達到和寬表模式基本相同的水準。

本節在 SSD 硬碟上測試了6個月的因子資料場景。使用合理設計的窄表模式儲存 TB 級別的因子資料,在資料寫入、多因子隨機查詢、因子資料運維等各方面均有穩定表現。在8 程併發情況下查詢速度與寬表模式相當。此外,查詢、因子運維耗時能夠保證線性增長,不會因為海量資料不斷增加而出現查詢、因子運維耗時大幅增加情況,這樣的特性是資料庫可以支援海量資料的重要保證。

3.5. 中高頻多因子庫儲存的最佳實踐:SSD vs HDD

前兩節中我們分別在 HDD 和 SSD 兩種硬碟環境下進行了寬表和窄表的儲存效能比對。但是在其他系統資源一樣的情況下,HDD 和 SSD 的差別是否很大,也是我們比較關心的一件事情。本小節我們將在下表所示的兩種硬體配置環境下,對比測試不同儲存模式的效能,為大家提供中高頻多因子庫儲存的最佳實踐建議。

伺服器配置項 SSD HDD
CPU 64 核 64 核
記憶體 512G 512G
磁碟 3 塊 SSD 硬碟(1.5GB/s 吞吐量) 9 塊 HDD 硬碟(1.4GB/s 吞吐量)
資料庫設定 單機叢集三資料節點 單機叢集三資料節點

因子寫入

寫入一個月5,000只標的10鍾級10,000因子資料,定義因子寫入函式的程式碼與案例一中一致,完整指令碼可參考附件。在磁碟總 I/O 相近的情況,SSD 硬碟的寫入速度略優於 HDD 硬碟。

儲存方案 儲存配置 資料原始大小 (GB) 落盤大小 (GB) 壓縮比 磁碟 IO(MB/s) 寫入耗時 (s)
SSD 窄表 479 186 2.6 1,068 459
SSD 寬表 191 166 1.1 757 257
HDD 窄表 479 186 2.6 937 523
HDD 寬表 191 166 1.1 648 301

因子查詢

分別進行查詢一個月全市場5,000只股票 隨機1,000個因子資料,查詢資料量大小 19G,核心查詢程式碼與案例一中一致,完整指令碼可參考附件。從下表中可以看到,使用 HDD 硬碟的冷查詢和熱查詢會相差大一些。而無論 SSD 硬碟還是 HDD 硬碟,窄表模式的分散式多執行緒查詢都可以保持更加穩定的效能。

儲存方案 儲存配置 冷查詢耗時 (s) 熱查詢耗時 (s)
SSD 窄表 64 61
SSD 寬表 162 55
HDD 寬表 425 49
HDD 窄表 80 52

資料運維

在運維場景下,窄表模式的落盤後資料量為 186GB ,寬表為 166GB,新增、更新和刪除因子的核心程式碼與案例一中一致,完整指令碼可參考附件。測試結果如下所示,可以看到無論是 SSD 還是 HHD ,窄表模式的運維操作耗時都非常低,基本都是秒級。而寬表模式雖然在 SDD 硬碟下耗時要少於 HDD 硬碟,但總體耗時仍然非常高。

儲存方案 資料運維操作 窄表 (s) 寬表 (s)
SSD 新增 1 因子 1.1 330
SSD 更新 1 因子 0.9 316
SSD 刪除 1 因子 0.5 N/A
HDD 新增 1 因子 1.2 534
HDD 更新 1 因子 1.1 541
HDD 刪除 1 因子 0.8 N/A

從上述結果我們可以看到。在同等條件下,選擇 SSD 磁碟在寫入場景、冷查詢場景效能都要好於 HDD 磁碟。而在因子運維場景,窄表模式耗時比較短,相差並不明顯。寬表模式則有較大差異。也就是說在因子資料使用的各個場景採用 SSD 磁碟都可以獲得更好的效能。故在因子資料儲存上,我們推薦使用 SSD 磁碟來進行因子資料儲存。

總結

本教程通過一個“10分鐘級10,000因子資料”的例子,測試了因子在不同儲存模式(寬表、窄表)和硬碟選擇(HDD, SSD)下的寫入、新增、更新、刪除,以及多執行緒併發查詢時的效能。

通過測試結果可知:在10分鐘級10,000因子場景下,採取按月 Value 分割槽 + 因子名 Value 分割槽,以及 SortColumn 為 SecurityID+TradeTime 設定下的窄表模式對資料進行儲存,為最佳解決方案。

雖然在併發查詢場景下,窄表模式的查詢操作會因為分散式處理導致的 CPU 資源競爭而增加耗時,但仍可以保證與寬表模式的查詢耗時差距不超過5%。

同時,該儲存方案無論在 HDD 硬碟或 SSD 硬碟中,都能保持穩定的效能,查詢場景效能優於寬表模式,因子運維場景下優勢更加明顯,各項指標均達到秒級,僅在資料寫入環節的效能略遜於寬表模式。

因此,在中高頻多因子庫的儲存方案選擇中,我們更推薦使用者採用合理設計儲存模型的窄表模式。