據(jù)庫超表架構(gòu)落地的一次實戰(zhàn)復(fù)盤)
一、金倉時序數(shù)據(jù)庫超表架構(gòu)難在哪先撂個結(jié)論手工分表這套辦法毛病不在“分”上在于分完之后所有的活兒都得你自己干。做過時序數(shù)據(jù)的人應(yīng)該都懂。按月建表嘛xxx_202501、xxx_202502一路往下排。寫入靠應(yīng)用層拼表名做路由查跨月的數(shù)據(jù)就 UNION ALL 一串。量小的時候是真沒毛病甚至可以說還挺優(yōu)雅。可數(shù)據(jù)一上來麻煩就開始一個接一個往外蹦。最先繃不住的往往是分區(qū)規(guī)則。它散落在代碼里啊建表靠定時任務(wù)路由邏輯埋在應(yīng)用里頭。新同事不知道有這套約定改查詢忘了帶月份路由一條 SQL 直接掃當(dāng)月大表。這種事幾乎每個團隊都出過出一次記一輩子。然后是跨月查詢。UNION ALL 完再聚合你自己琢磨琢磨各表上的索引不就白建了嘛。月份跨得越多越慢沒有例外。冷熱數(shù)據(jù)還混著放。三個月前的明細(xì)除了出報表那天誰碰它啊可它偏偏和今天的熱數(shù)據(jù)躺同一塊盤上磁盤告警三天兩頭來報到。刪又不敢刪鬼知道哪張報表哪天要用。到期刪舊數(shù)據(jù)這事兒靠人惦記忘了就堆著堆著堆著就成了誰也不敢動的歷史包袱。想擴容就剩換更貴的服務(wù)器一條道賬單蹭蹭漲。這些麻煩攤開來看根子其實就一個問題分區(qū)這件事到底歸誰管手工分表時代它歸應(yīng)用管所以樁樁件件都得人扛。金倉的超表Hypertable治的就是這個。它把分區(qū)的活兒整個下沉到數(shù)據(jù)庫里頭去了。數(shù)據(jù)進來按時間自動切成一個個數(shù)據(jù)塊Chunk每塊只管一段時間的再疊個空間維度比如按點位 ID 哈希把高并發(fā)寫入攤開。應(yīng)用這邊看到啥從頭到尾就一張monitor_point_dataINSERT 該咋寫咋寫。查半年的數(shù)據(jù)也是一條普普通通的 SQL跟你當(dāng)前時間區(qū)間沒關(guān)系的塊數(shù)據(jù)庫自己就不碰了。兩種模式擺一塊兒對比下對比項手工分表金倉超表分區(qū)誰管應(yīng)用建表定時任務(wù)規(guī)則散在代碼里數(shù)據(jù)庫自己按時間空間切 Chunk跨時段查詢多表 UNION ALL索引廢掉一條普通 SQL沒關(guān)的塊直接不碰寫入路由應(yīng)用按時間拼表名不存在這個環(huán)節(jié)直接插老數(shù)據(jù)治理手工 DROP忘了就堆著到期自動按塊刪以后擴容堆硬件燒錢可以往分布式超表走SQL 兼容-標(biāo)準(zhǔn) SQLBI 工具直連靠 SQL 吃飯的團隊最后一行搞不好才是最香的。報表工具、BI 看板、備份腳本、監(jiān)控探針、權(quán)限體系全能接著用不用為時序能力單獨搭一條工具鏈。人就那么幾個的小團隊這點比啥跑分都實在。不過丑話說前頭超表只是把復(fù)雜度從應(yīng)用層挪到了數(shù)據(jù)庫層它可沒消失。塊間隔、壓縮、保留策略、連續(xù)聚合這套新機制各有各的坑坑跟坑之間還會互相影響。下面按落地的順序一個一個說。目錄一、金倉時序數(shù)據(jù)庫超表架構(gòu)難在哪二、建表三、第一個坑塊間隔四、壓縮和保留五、第二個坑刷新窗口和保留策略會互相咬六、幾句經(jīng)驗二、建表先建張普通表就你平時寫的那種一點特殊語法都沒有CREATETABLEmonitor_point_data(timeTIMESTAMPTZNOTNULL,point_idINTEGERNOTNULL,metricTEXTNOTNULL,valueDOUBLEPRECISION,qualitySMALLINT);-- 轉(zhuǎn)為超表時間列做主分區(qū)point_id 哈希做空間分區(qū)SELECTcreate_hypertable(monitor_point_data,time,partitioning_columnpoint_id,number_partitions8,chunk_time_intervalINTERVAL1 day);一個函數(shù)調(diào)用完事兒。時間軸按chunk_time_interval自動滾新塊空間軸按點位 ID 哈希成 8 個分區(qū)。應(yīng)用側(cè)要做的改造就是把原來拼表名那段代碼刪掉。有個約束得提前講省得對著報錯發(fā)半天呆唯一索引必須帶上分區(qū)列。想拿point_id這種單列做唯一約束數(shù)據(jù)庫直接給你彈回來報錯還老長一段。道理不復(fù)雜每個 Chunk 各自建索引數(shù)據(jù)庫沒法跨塊替你保證唯一性所以唯一索引里必須把時間列捎上。三、第一個坑塊間隔塊間隔這個參數(shù)直覺上特別容易犯一個錯覺得塊切得越小查詢越快上來就設(shè)個 1 小時。直說了吧會翻車。塊切太細(xì)后臺建新塊的頻率就飛起寫入高峰還會莫名其妙冒出鎖等待。為啥建新塊要拿的鎖比往已有塊里插數(shù)據(jù)拿的鎖時間長。一堆事務(wù)擠在同一時刻搶著開新塊可不就互相頂死了嘛。那到底設(shè)多大手冊里有參考值按日寫入量給的每天寫 2GB、內(nèi)存 64GB 的機器7 天一塊正合適一天寫到 10GB縮到 1 天一塊。所以原則就一句話先抄手冊的作業(yè)再按自己的量微調(diào)。直覺在這兒不值錢。運行中想調(diào)也有接口但藏著個語義坑-- 注意只對之后新建的塊生效已經(jīng)建好的塊不動SELECTset_chunk_time_interval(monitor_point_data,INTERVAL24 hours);-- 看看當(dāng)前分區(qū)配置長啥樣SELECTcolumn_name,num_partitions,time_intervalFROMtimescaledb_information.dimensionsWHEREhypertable_namemonitor_point_data;改間隔只管新塊舊塊一個不碰。也就是說塊間隔設(shè)大了想改小舊塊是救不回來的。要么干等它自己滾出保留期要么老老實實遷數(shù)據(jù)。這參數(shù)務(wù)必上線前定死。四、壓縮和保留先問一句一個月之前的明細(xì)除了出報表那天還有誰碰它沒人碰。時序數(shù)據(jù)的訪問模式就是這么有規(guī)律最近的數(shù)據(jù)天天查老數(shù)據(jù)出了報表就沒人搭理了。壓縮策略照著這個規(guī)律配就行近幾天原樣擱著更老的塊自動壓成列存。ALTERTABLEmonitor_point_dataSET(timescaledb.compress,timescaledb.compress_segmentbypoint_id,timescaledb.compress_orderbytime DESC);-- 超過 7 天的塊自動壓縮SELECTadd_compression_policy(monitor_point_data,compress_afterINTERVAL7 days);-- 原始明細(xì)保留 180 天到期自動刪塊SELECTadd_retention_policy(monitor_point_data,drop_afterINTERVAL180 days);配置里兩個參數(shù)說下作用。compress_segmentby是按哪列分組壓縮時序場景一般挑設(shè)備或者點位 ID。compress_orderby是組內(nèi)按啥排通常就填時間列。這倆選對了壓縮比和查詢效率都跟著受益實測壓縮比 4:1 上下跟官方口徑基本對得上。順帶提個不大但挺陰的細(xì)節(jié)壓縮塊上沒法直接加帶默認(rèn)值的列要加得先解壓。嫌解壓麻煩也有變通加個可空列再 UPDATE 把值補上就這么繞過去。五、第二個坑刷新窗口和保留策略會互相咬報表聚合慢靠連續(xù)聚合治。思路不玄乎把小時級的聚合結(jié)果提前物化成一張?zhí)厥獾某砗笈_按策略增量刷新查詢直接拿現(xiàn)成的不碰明細(xì)。CREATEMATERIALIZEDVIEWpoint_data_hourlyWITH(timescaledb.continuous)ASSELECTpoint_id,time_bucket(INTERVAL1 hour,time)ASbucket,avg(value)ASavg_val,max(value)ASmax_val,min(value)ASmin_valFROMmonitor_point_dataGROUPBYpoint_id,bucketWITHNODATA;SELECTadd_continuous_aggregate_policy(point_data_hourly,start_offsetINTERVAL3 days,end_offsetINTERVAL1 hour,schedule_intervalINTERVAL30 minutes);下面這個坑我個人認(rèn)為是整套方案里最容易翻車的必須單獨拎出來說。很多人配這倆策略的時候是分開想的保留策略拍一個數(shù)刷新窗口拍另一個數(shù)各管各的看著多合理啊。但它們實際上會互相咬。你保留 30 天刷新窗口也伸到 30 天前會咋樣聚合刷新跑到那個時間段一看源數(shù)據(jù)讓保留策略給刪了那它就把物化好的結(jié)果也順手刪了。注意這整個過程沒有報錯。沒告警沒異常日志就是報表上的數(shù)字悄無聲息地沒了。直到哪天有人盯著一條空曲線問數(shù)據(jù)咋回事你才知道壞了。這種靜默失敗排查起來最磨人。正確姿勢就一條保留周期要遠(yuǎn)大于刷新窗口。照上面例子那樣保留 180 天、刷新窗口只伸到 3 天前讓刷新永遠(yuǎn)落在還有原始數(shù)據(jù)的時段里這坑就踩不著。手冊里對這個組合是有明確警告的別問我為啥知道得這么清楚。日常查趨勢還是普通 SQL配個時間桶函數(shù)要多細(xì)有多細(xì)SELECTtime_bucket(5 minutes,time)ASfive_min,avg(value)ASavg_valFROMmonitor_point_dataWHEREpoint_id1024ANDtimenow()-INTERVAL2 hoursGROUPBYfive_minORDERBYfive_min;六、幾句經(jīng)驗回頭看超表落地這事技術(shù)上真不難難的是心態(tài)。把原來攥在自己手里那點“分表智慧”整個交還給數(shù)據(jù)庫交出去那幾天是真沒底老琢磨它真能替我管好幾條經(jīng)驗擱這兒。塊間隔先抄作業(yè)再微調(diào)按日寫入量對照手冊來。這參數(shù)改小只對新塊生效設(shè)大了想縮沒有回頭路。唯一索引必須帶分區(qū)列單列唯一約束直接報錯把時間列加上就好。保留策略和刷新窗口必須一起設(shè)計。這條得再說一遍因為這倆配岔了連報錯都沒有數(shù)據(jù)就這么沒了。壓縮塊上別直接加帶默認(rèn)值的列先解壓或者可空列加 UPDATE 繞過去。還有個容易忽略的超表的價值不止“快”那一下。應(yīng)用代碼干凈了團隊里沒人再需要搞懂那套分表路由的約定。這種工程上的松快時間拉長了看比查詢快幾秒值錢。往后單機寫入真到了天花板還有分布式超表這條路寫入再橫向攤一層架構(gòu)不用推倒重來。先寫到這等真跑起來了再補后話。