摘要:本文章將從使用者角度介紹HStore概念以及使用,
本文分享自華為云社區《GaussDB(DWS)HStore表講解》,作者:大威天龍:- ,
HStore表簡介
面對實時入庫和實時查詢要求越來越高的趨勢,已有的列存盤無法支持并發更新入庫,行存查詢性能無法做到實時回傳且空間壓縮表現不佳,GaussDB(DWS)基于列存盤格式設計和實作了全新的HStore表,同時提供高效的并發插入、更新入庫,以及高性能實時查詢,本文章將從使用者角度介紹HStore概念以及使用,
HStore表的背景
為什么要有HStore表呢?在具體講解HStore表之前,我們先來回顧一下GaussDB(DWS)中幾種已有的表型別:
行存表(row-store)
最基礎的表型別,顧名思義,資料按行存盤,在實際的物理塊中,資料的將按下列圖示的方式存盤:
優勢很明顯,點查場景下,直接就能索引到行存某行元組的位置,點查性能好,資料庫中的系統表就是行存表,對于用戶的一些對點查性能要求高或者頻繁更新的小表,都推薦用行存表,
列存表(column-store)
AP場景下,常常需要對某列進行批量查詢來做分析業務,這時候采用行存的話就會把所有列都讀出來產生冗余IO, 同時AP場景下的表資料量往往很大,行存表壓縮暫未商用,使用行存表也會導致占用空間過大,
GaussDB(DWS)中的列存表就是針對這種場景實作的,列存表資料的實際存盤示意圖如下:
列存表將每列的資料批量存盤成一個CU(Compress Unit), 能帶來了很好的空間壓縮與批量查詢性能提升,對于一些涉及多表關聯的分析類復雜查詢、資料不經常更新的表,推薦使用列存表,
列存帶Delta表
對于列存表,如果業務是頻繁的小批量插入,那么將產生大量的小CU(單個CU里只有幾百條甚至幾條資料), 每個列的CU都是有壓縮代價的,小CU過多將嚴重影響列存表的查詢性能,
列存的Delta表就是針對這種場景實作的,讓小批量插入的資料先存盤到行存delta表,滿6w后由后臺autovacuum異步merge到主表CU,
需要注意的是列存帶Delta表只解決小批量入庫產生的小CU問題,不解決同一個CU上的并發更新問題
HStore表
前面提到,雖然列存老Delta表解決了小批量入庫產生的小CU問題,但是沒有解決同一個CU上的并發更新產生的鎖沖突問題,
而實時入庫的場景下,需要將insert+upsert+update操作實時并發入庫,資料來源于上游的其他資料庫或者應用,同時要求入庫后的資料要能及時查詢,且對于查詢的效率要求很高,
目前的列存表由于鎖沖突的原因無法支持并發upsert/update入庫,導致這些有需要的局點只能使用行存表,但是行存表因為格式的天然劣勢,在AP查詢場景下一方面性能較慢,另一方面由于壓縮差導致占用了大量的磁盤空間,對用戶產生額外成本,
GaussDB(DWS)中的HStore表, 在使用列存盤格式盡量降低磁盤占用的同時,支持高并發的更新操作入庫以及高性能的查詢效率,面向對于實時入庫和實時查詢有較強訴求的場景,同時擁有處理傳統TP場景的事務能力,
HStore表的示意圖如下:
GaussDB(DWS) 中幾種表型別的對比
HStore的Delta表
HStore表的實作主要依靠一張新設計的delta表以及記憶體并發控制機制,這里簡單講一下delta表的實作以及簡單的觀察delta表,
HStore的Delta表主要用于存放入庫產生的Insert/Delete/Update操作,小批量Insert的資料會先進入Delta形成一條型別是I(Insert)的記錄;洗掉會往Delta表插入一條型別是D(Delete)的記錄;更新操作(Upsert與Update)會拆分成Delete + Insert,會插入一條型別X(表示由更新產生的洗掉)的記錄以及一條型別I的記錄;
(型別是U(Update)的記錄由輕量化Update產生,不過當前輕量化更新默認關閉,所以不用管,)
可以看到,入庫時的Upsert/Update/Delete都會轉換成相應型別的記錄插入的HStore的Delta表中,再結合記憶體并發控制機制,就能保證同一個CU上更新于洗掉操作不會阻塞,同時,由于小批量的插入只會在Delta表上形成一條記錄,相比與列存老Delta的直接存盤資料,能減少IO占用,提高MERGE效率,
HStore的Delta表 與 列存老Delta表的對比
HStore的視圖與函式
當前HStore表提供了視圖,可以用來觀察Delta表的給型別元組數量以及Delta的膨脹情況,
select * from pgxc_get_hstore_delta_info('tableName');
同時也提供了函式可以對Delta表做輕量清理以及全量清理,
-- 輕量Merge滿6萬的I記錄以及CU上的洗掉資訊,持有四級鎖不阻塞業務增刪改查,但空間不會還給作業系統, select hstore_light_merge('tableName'); -- 全量Merge所有記錄,然后truncate清空Delta表返還空間給系統,不過持有八級鎖會阻塞業務, select hstore_full_merge('tableName');
這里做一個簡單的觀察實驗:
1.往HStore表上批量插入一百條資料,能看到生成了一條型別是I的記錄(n_i_tup 為1)
gaussdb=# create table data(a int primary key, b int); NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "data_pkey" for table "data" CREATE TABLE gaussdb=# insert into data values(generate_series(1,100),1); INSERT 0 100 gaussdb=# create table hs(a int primary key, b int)with(orientation=column, enable_hstore=on); NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "hs_pkey" for table "hs" CREATE TABLE gaussdb=# insert into hs select * from data; INSERT 0 100 gaussdb=# select * from pgxc_get_hstore_delta_info('hs'); --觀察hstore表的delta表上的各型別資料 node_name | part_name | live_tup | n_i_type | n_d_type | n_x_type | n_u_type | n_m_type | data_size -----------+---------------------+----------+----------+----------+----------+----------+----------+----------- dn_1 | non partition table | 1 | 1 | 0 | 0 | 0 | 0 | 8192 (1 row)
2.執行hstore_full_merge后能觀察到Delta表上沒有元組(live_tup為0),并且Delta表的空間大小data_size是0.
gaussdb=# select hstore_full_merge('hs'); hstore_full_merge ------------------- 1 (1 row) gaussdb=# select * from pgxc_get_hstore_delta_info('hs'); --觀察hstore表的delta表上的各型別資料 node_name | part_name | live_tup | n_i_type | n_d_type | n_x_type | n_u_type | n_m_type | data_size -----------+---------------------+----------+----------+----------+----------+----------+----------+----------- dn_1 | non partition table | 0 | 0 | 0 | 0 | 0 | 0 | 0 (1 row)
3.執行洗掉,能觀察到Delta表上有一條型別是D的記錄(n_d_tup為1),
gaussdb=# delete hs where a = 1; DELETE 1 gaussdb=# select * from pgxc_get_hstore_delta_info('hs'); --觀察hstore表的delta表上的各型別資料 node_name | part_name | live_tup | n_i_type | n_d_type | n_x_type | n_u_type | n_m_type | data_size -----------+---------------------+----------+----------+----------+----------+----------+----------+----------- dn_1 | non partition table | 1 | 0 | 1 | 0 | 0 | 0 | 8192 (1 row)
其它的操作這里不再一一嘗試,感興趣的讀者可以自己下來試一下,
HStore表的簡單使用實驗
準備作業
當需要使用HStore表時,需要同步修改以下幾個清理相關的引數默認值,否則會導致HStore表性能嚴重劣化,推薦的引數修改配置是:autovacuum_max_workers_hstore=3,autovacuum_max_workers=6,autovacuum=true,
并發更新實驗
在列存表上插入一批資料后,開啟兩個會話,
1.會話1洗掉某一條資料,然后不結束事務:
gaussdb=# create table col(a int , b int)with(orientation=column); CREATE TABLE gaussdb=# insert into col select * from data; INSERT 0 100 gaussdb=# begin; BEGIN gaussdb=# delete col where a = 1; DELETE 1
2.會話2洗掉另一條資料,能看到會話2等待會話1,
gaussdb=# begin; BEGIN gaussdb=# delete col where a = 2;
會話1提交后會話2才能繼續執行,這就復現了列存的CU鎖問題:
3. 使用HStore表重復上面實驗,能觀察到會話2直接執行成功,不會鎖等待,
gaussdb=# begin; BEGIN gaussdb=# delete hs where a = 2; DELETE 1
壓縮效率實驗
1.構建一張有三百萬資料的資料表data
gaussdb=# create table data( a int, b bigint, c varchar(10), d varchar(10)); CREATE TABLE gaussdb=# insert into data values(generate_series(1,100),1,'asdfasdf','gergqer'); INSERT 0 100 gaussdb=# insert into data select * from data; INSERT 0 100 gaussdb=# insert into data select * from data; INSERT 0 200 ---回圈插入,直到資料量達到三百萬 gaussdb=# insert into data select * from data; INSERT 0 1638400 gaussdb=# select count(*) from data; count --------- 3276800 (1 row)
2.批量匯入到行存表,觀察大小為223MB
gaussdb=# create table row (like data including all); CREATE TABLE gaussdb=# insert into row select * from data; INSERT 0 3276800 gaussdb=# select pg_size_pretty(pg_relation_size('row')); pg_size_pretty ---------------- 223 MB (1 row)
3.批量匯入到列存表,觀察大小為3.5MB
gaussdb=# create table hs(a int, b bigint, c varchar(10),d varchar(10))with(orientation= column, enable_hstore=on); CREATE TABLE gaussdb=# insert into hs select * from data; INSERT 0 3276800 gaussdb=# select pg_size_pretty(pg_relation_size('hs')); pg_size_pretty ---------------- 3568 KB (1 row)
4.總結
這個表結構比較簡單,資料也都是重復資料,所以HStore表的壓縮效果很好,一般情況下HStore表相比行存能有3-5倍的壓縮,
批量查詢性能實驗
還是使用上面建的表,這里簡單驗證一下批量查詢
1.查詢行存表的第四列,耗時在4s左右
gaussdb=# explain analyze select d from data; explain analye QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------- id | operation | A-time | A-rows | E-rows | Peak Memory | E-memory | A-width | E-width | E-costs ----+------------------------------+----------------------+---------+---------+--------------+----------+---------+---------+---------- 1 | -> Streaming (type: GATHER) | 4337.881 | 3276800 | 3276800 | 32KB | | | 8 | 61891.00 2 | -> Seq Scan on data | [1571.995, 1571.995] | 3276800 | 3276800 | [32KB, 32KB] | 1MB | | 8 | 61266.00
2.查詢HStore表的第四列,耗時300毫秒左右
gaussdb=# explain analyze select d from hs; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------- id | operation | A-time | A-rows | E-rows | Peak Memory | E-memory | A-width | E-width | E-costs ----+----------------------------------------+--------------------+---------+---------+----------------+----------+---------+---------+---------- 1 | -> Row Adapter | 335.280 | 3276800 | 3276800 | 24KB | | | 8 | 15561.80 2 | -> Vector Streaming (type: GATHER) | 111.492 | 3276800 | 3276800 | 96KB | | | 8 | 15561.80 3 | -> CStore Scan on hs | [111.116, 111.116] | 3276800 | 3276800 | [254KB, 254KB] | 1MB | | 8 | 14936.80
3.總結
這里只驗證了批量查詢場景,該場景下列存以及HStore表相比行存都有很好的查詢性能,但在索引點查詢場景下,列存是比不上行存的,這里不再做詳細對比,
HStore表注意事項
1.引數設定
HStore依賴后臺常駐執行緒對HStore表進行MERGE清理操作,才能保證查詢性能與壓縮效率,所以使用HStore表務必設定相關GUC,推薦的配置如下:
autovacuum_max_workers_hstore=3 autovacuum_max_workers=6 autovacuum=true
2.并發同一行:
當前HStore并發更新同一行仍然是不支持的,其中同一行上并發update/delete操作會先等鎖然后報錯,同一行上的并發upsert操作會先等鎖然后繼續執行,由于等待開銷也是會影響業務的入庫性能,甚至可能產生死鎖,所以需要在入庫時保證不會并發更新到同一行或者同一個key,
3.索引相關
索引會占用額外的空間,同時帶來的點查性能提升有限,所以HStore表只建議在需要做Upsert或者有點查(這里指唯一性與接近唯一的點查)的訴求下創建一個主鍵或者btree索引,
4.MERGE相關
由于HStore表依賴后臺autovacuum來將操作MERGE到主表,所以入庫速度不能超過MERGE速度,否則會導致delta表的膨脹,可以通過控制入庫的并發來控制入庫速度,同時由于Delta表本身的空間復用受oldestXmin的影響,如果有老事務存在可能會導致Delta空間復用不及時而產生膨脹,
5.UPSERT性能
HStore表雖然相比普通列存,并發upsert入庫性能得到了很大提升,但相比行存還是有差距,大概只有行存的1/3,所以在不追求壓縮率以及批量查詢性能、只追求單點查詢性能的場景下,還是推薦行存表入庫,
點擊關注,第一時間了解華為云新鮮技術~
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/555222.html
標籤:大數據
下一篇:返回列表
