## 前言
POLARDB作為阿里雲下一代關系型雲數據庫,自去年9月份公測以來,收到了不少客戶的重點關注,今年5月份商業化后,許多大客戶開始陸續遷移業務到POLARDB上,但是由於POLARDB的很多默認行為與RDS MySQL兼容版不一樣,導致很多用戶有諸多使用上的困惑,本來總結了幾點,給大家答疑解惑。另外,本文提到的參數,在新版本上,用戶都可以通過控制台修改,如果沒有,可以聯系售后服務修改。本文適合讀者:阿里雲售后服務,POLARDB用戶,POLARDB內核開發者,需要有基本的數據庫知識,最好對MySQL源碼有部分了解。
## 磁盤空間問題
RDS MySQL在購買的時候需要指定購買的磁盤大小,最大為3TB。如果空間不夠,需要升級磁盤空間。具體來說,如果實例所在的物理機磁盤空間充足,這個升級磁盤的任務很快就可以完成,但是如果空間不足,就需要在其他物理機上重建實例,大實例需要幾天的時間。為了解決這個問題,POLARDB底層使用存儲集群的方式,做到磁盤動態擴容,且磁盤擴容過程對用戶無感知,具體來說,默認磁盤空間分配為規格內存的10倍,當使用了70%,系統就會自動擴容一部分空間,而且擴容不需要停止實例。
有了這種機制,POLARDB的存儲可以做到按照使用量來收費,真正做到使用多少就收多少錢,計費周期是一小時。同時,由於存儲按量收費,導致許多用戶對存儲的使用量非常敏感,在我們的控制台上,有五種空間統計,分別是磁盤空間使用量,數據空間使用量,日志空間使用量,臨時空間使用量和系統文件空間使用量。
磁盤空間使用量是后四者之和,數據空間使用量包括用戶創建的所有庫,mysql庫,test庫,performance_schema庫,日志空間使用量包括redolog,undolog,ibdata1,ib_checkpoint(存儲checkpoint信息),innodb_repl.info(存儲切換信息,物理復制信息等),臨時空間使用量包括socket文件,pid文件,臨時表(大查詢排序用),審計日志文件,系統文件空間使用量包括錯誤日志,慢日志,general日志以及主庫信息(用於構建復制關系)。雖然有四部分空間使用量,但大多數主要被數據空間和日志空間占用,數據空間比較好理解,主要就是表空間聚集索引和二級索引的占用量,但是這個日志空間很多用戶不是很了解,常常提上來的問題是,為什么我的日志空間占了100多個G,而數據空間就幾個G,這里簡單解釋一下。
日志空間使用量,如上所述,有很多組成部分。redolog,主要用來構建物理復制,同時也可以被當做增量日志來支持還原到時間點/克隆實例的任務,類似原生的binlog,文件名按順序遞增,主節點產生日志,只讀節點/災備節點應用日志,同時后台管控任務會定時上傳redolog(只要發現新的就立即上傳)並且定時刪除(目前一小時觸發一次刪除任務),具體大小與DML總量有關。undolog,主要用來構建數據歷史版本以支持MVCC機制和回滾機制,不同於RDS MySQL的undolog都在ibdata1文件中,POLARDB的undolog大部分是以獨立表空間/文件存在,具體大小與用戶使用習慣有關。ibdata1,主要存儲系統元數據信息等系統信息,具體大小與用戶表數量有關,但是一般不會太大。ib_checkpoint,這個是POLARDB特有的,用於存儲checkpoint信息,大小固定。innodb_repl.info也是POLARDB獨有的,存儲物理復制相關的信息以及切換信息,一般不會太大。由此可見,日志空間使用量雖然也有很多組成部分,但主要是被redolog日志和undolog日志占用。
### redolog日志占用
redolog日志,由於對數據的修改都會記錄redolog,所以對數據修改的越快,redolog產生的速度就會越快,而且上傳OSS(保留下來做增量日志)的速度有限,所以在實例導數據階段,會導致redolog堆積,當導入完成后,redolog會慢慢上傳完然后刪除,這樣空間就會降下來,但是不會完全降為0。具體原因需要介紹一下:目前所有規格,redolog大小都為1G,被刪除的redolog不會馬上被刪除,而是放入一個緩沖池(rename成一個臨時文件),當需要新的redolog時候,先看看緩沖池里面還有沒有可用的文件,如果有直接rename成目標文件,如果沒有再創建,這個優化主要是為了減少創建新文件時的io對系統的抖動,緩沖池的大小由參數`loose_innodb_polar_log_file_max_reuse`控制,默認是8,如果用戶想減少緩存池的文件個數,就可以減少這個參數從而減少日志空間占用量,但是在壓力大的情況下,性能可能會出現周期性的小幅波動。所以當寫入大量數據后,即使redolog都被上傳,默認也有8G的空間用作緩存。注意,調整這個參數后,緩沖池不會立刻被清空,隨着dml被執行,才會慢慢減少,如果需要立即清空,建議聯系售后服務。
另外,POLARDB會提前創建好下一個需要寫的redolog日志(每個日志都是固定的1G,即使沒有被寫過),主要目的是當當前的redolog被寫完后,能快速的切換到下一個,因此,也會占用額外1G空間。此外,后台定時刪除任務目前是一個小時清理一次(還有優化的空間),但是不會清理到最后一個日志,會保留一個日志,主要用來做按時間點還原任務。
接下來,舉個經典的例子,方便理解上述的策略:
```
mysql> show polar logs;
+-----------------+----------------+-------------+
| Log_name | Start_lsn | Log_version |
+-----------------+----------------+-------------+
| ib_logfile41008 | 19089701633024 | 100 |
| ib_logfile41009 | 19090775372800 | 100 |
+-----------------+----------------+-------------+
2 rows in set (0.00 sec)
mysql> show polar status\G
......
-----------------
Log File Info
-----------------
2 active ib_logfiles
The oldest log file number: 41008, start_lsn: 19089701633024
The newest log file number: 41009, start_lsn: 19090775372800
Log purge up to file number: 41008
8 free files for reallocation
Lastest(Doing) checkpoint at lsn 19091025469814(ib_logfile41009, offset 250099062)
......
```
`show polar logs`這條命令可以查看系統中的redolog日志,上個例子中,ib_logfile41008這文件已經被寫完,但是這個日志需要被保留用來支持按照時間點還原和克隆實例任務,ib_logfile41009是最后一個redolog,表示目前正在寫的redolog。
`show polar status\G`可以顯示POLARDB很多內部信息,這里只截取了redolog相關的一部分,前四行就是字面的意思,不具體解釋了。第五行表示緩沖池目前有8個redolog。
另外,上文提到過,POLARDB會提前創建一個redolog用以快速的切換,名字一般是最后一個文件編號加一,所以是ib_logfile41010。
結合這些信息,就可以推斷出,目前系統中redolog占用量為11G = 8G(緩沖池中的)+1G(保留的ib_logfile41008)+1G(正在被寫的ib_logfile41009)+1G(提前創建的ib_logfile41010)。
另外,透露一個好消息,我們內部正在調研redolog日志不收費的可行性,如果通過驗證,這部分占用的空間將不會收取用戶費用。
### undolog日志占用
講完了redolog日志,接下里講講undolog日志。上文說過在POLARDB中undolog大部分是以獨立表空間存在的,也就是說是獨立的文件,而不是聚集在ibdata1文件中。目前分了8個文件,文件名為undo001-undo008,每個文件默認初始大小為10M,會隨着使用增大,在某些不推薦的用法下,會導致undolog空間增長很快。這里簡單舉個例子,可以使undolog撐的很大:使用`START TRANSACTION WITH consistent snapshot`開啟一個事務,注意要在RR隔離級別下,然后開啟另外一個連接,對庫中的表進行高頻率的更新,可以使用sysbench輔助,很快,就會發現undolog膨脹。從數據庫內核的角度來講,就是由於一個很老的readview,導致需要把很多的歷史版本都保留下來,從而導致undolog膨脹。在線上,往往是一個大查詢或者一個長時間不提交的事務導致undolog膨脹。undolog膨脹后,即使所有事務都結束后,也不會自動縮小,需要使用下文的方法進行在線truncate。
目前,用戶還不能直接查看undolog的占用量,后續我們會在information_schema加上,方便用戶查看,但是可以通過間接的方法:如果控制台上顯示日志占用量很大,但是redolog占用量很小,那么一般就是undolog了,因為其他幾個都占用很小的空間,幾乎可以忽略不計。
如果發現undolog占用量比較大,POLARDB也有辦法清理。原理是,等undolog所對應的事務都結束后,把清理開關打開,如果發現大小超過執行大小的undo tablespace,就會在purge線程中進行undo的truncate。盡量在業務低峰期進行,並且保證沒有大事務長事務。具體操作方法就兩步,首先調整`innodb_max_undo_log_size`大小,這個參數表示當每個undo tablespace大於這個值時候,后續會把它縮小,重新調整為10M。接着,打開truncate開關`innodb_undo_log_truncate`,這樣,后台線程就會把所有大於`innodb_max_undo_log_size`設置的undo tablespace調整為10M。注意,這里要保證沒有大事務長事務,因為后台線程會等待undo tablespace中所有事務都提交后,才會下發命令,同時也要保證`innodb_undo_logs`大於等於2。另外,不建議這個功能長期開着,如果在控制台發現日志占用量減少了,建議關閉truncate功能,因為其有可能在您業務高峰期運行,導致數據庫延遲。
## DDL與大事務問題
如果有一個大事務或者長事務長時間未提交,由於其長期持有MDL讀鎖,這個會帶來很多問題。在RDS MySQL上,如果后續對這張表又有DDL操作,那么這個操作會被這個大事務給堵住。在POLARDB上,這個問題更加嚴重,簡單的說,如果只讀實例上有一個大事務或者長期未提交的事務,會影響主實例上的DDL,導致其超時失敗。糾其本質的原因,是因為POLARDB基於共享存儲的架構,因此在對表結構變更前,必須保證所有的讀操作(包括主實例上的讀和只讀實例上的讀)結束。
具體解釋一下POLARDB上DDL的過程。在DDL的不同階段,當需要對表進行結構變更前,主實例自己獲取MDL鎖后,會寫一條redolog日志,只讀實例解析到這個日志后,會嘗試獲取同一個表上的MDL鎖,如果失敗,會反饋給主實例。主實例會等待所有只讀實例同步到最新的復制位點,即所有實例都解析到這條加鎖日志,主實例同時判斷是否有實例加鎖失敗,如果沒有,DDL就成功,否則失敗回滾。
這里涉及到兩個時間,一個是主實例等待所有只讀實例同步的超時時間,這個由參數`loose_innodb_primary_abort_ddl_wait_replica_timeout`控制,默認是一個小時。另外一個是只讀實例嘗試加MDL鎖的超時時間,由參數`loose_replica_lock_wait_timeout`控制,默認是50秒。可以調整這兩個參數來提前結束回滾DDL,通過返回的錯誤信息,來判斷是否有事務沒結束。
`loose_innodb_primary_abort_ddl_wait_replica_timeout`建議比`loose_replica_lock_wait_timeout `大。
舉個實際例子方便理解:
用戶可以通過命令`show processlist`中的State列觀察,如果發現`Wait for syncing with replicas`字樣,那么表示這條DDL目前處在等待只讀節點同步的階段。如果超過`loose_innodb_primary_abort_ddl_wait_replica_timeout`設置的時間,那么主節點會返回錯誤:
```
ERROR HY000: Rollback the statement as connected replica(s) delay too far away. You can kick out the slowest replica or increase variable 'innodb_abort_ddl_wait_replica_timeout'
```
如果沒有超時,那么主節點會檢查是否所有只讀節點都成功獲取MDL鎖了,如果失敗,那么主節點依然會返回錯誤:
```
ERROR HY000: Fail to get MDL on replica during DDL synchronize
```
如果主實例返回第二個錯誤,那么建議用戶檢查一下主實例以及所有只讀實例上是否有未結束的大查詢或者長時間未提交的事務。
這里順便介紹一下大事務長事務的防范手段。參數`loose_max_statement_time`可以控制大查詢的最大執行時間,超過這個時間后,會把查詢kill掉。參數`loose_rds_strict_trx_idle_timeout`可以控制空閑事務的最長存活時間,當一個事務空閑狀態超過這個值時候,會主動把這個連接斷掉,從而結束事務,注意,這個參數應該比`wait_timeout/interactive_timeout`小,否則無效。
## 查詢緩存問題
在MySQL低版本,查詢緩存(Query Cache)能提高查詢的性能,尤其是更新少的情況下,但是由於其本身也容易成為性能瓶頸,所以在最新的MySQL中此特性已經被移除。POLARDB目前的版本兼容MySQL 5.6,所以用戶依然可以使用查詢緩存,但是我們還是建議不使用,因為我們在引擎存儲層做了很多優化,即使不用查詢緩存依然有很好的性能。
由於POLARDB使用了物理復制,不同於binlog的邏輯復制,查詢緩存在只讀實例上的失效,依然需要通過redolog來保證,即當某條查詢緩存失效的時候,需要通過redolog來通知所有只讀節點,讓他們把對應的查詢記錄也失效掉,否則通過只讀節點會讀到歷史版本的數據。
當查詢緩存失效時,會寫redolog通知所有只讀節點,這個機制默認是關閉的,通過參數`loose_innodb_primary_qcache_invalid_log`來控制。
綜上所示,如果在只讀節點上開啟了查詢緩存(只要有一個開啟),那么必須在主節點上開啟`loose_innodb_primary_qcache_invalid_log`,否則只讀節點會讀到歷史版本的數據。考慮到HA切換會切換到任意一個只讀節點,因此建議如果開啟了查詢緩存,在所有只讀節點上也把`loose_innodb_primary_qcache_invalid_log`開啟。
## 讀寫分離問題
POLARDB自帶一個只讀實例,增減只讀實例非常快速,所以用戶非常適合使用讀寫分離的功能,但是從目前用戶的反饋來看,如果在插入數據后立刻查詢,很容易查詢到之前舊版的數據,為了解決這個問題,我們給出兩種解法。一種是通過POLARDB數據庫內核的強同步保證主實例和只讀節點數據一致,另外一種是通過數據庫前面的PROXY層來解決。下面簡單介紹一下。
POLARDB集群基於物理復制構建,目前復制除了支持常規的異步復制(默認),半同步復制之外,還有強同步復制,即當事務提交時,只有當指定的只讀實例應用完redolog日志后,主實例才給用戶返回成功。這樣即使后續的讀請求發送到了只讀節點,也能保證讀到最新的數據。但是這個配置會導致性能大幅度下降,只有默認異步復制的三分之一左右,在使用之前請做詳細的測試。簡單說一下配置過程:
首先需要在主實例上設置:設置`loose_innodb_primary_sync_slave`為3,目的是告訴主實例,它連接的只讀實例會有強同步的需求。接着在需要強同步的只讀實例上把參數`loose_slave_trans_sync_level `設置為2,注意這個參數需要重啟實例。另外,先設置主實例,再設置只讀實例的順序不能亂。設置成功后,在主實例上執行`show polar replicas;`(這個命令可以查看所有的只讀實例),在`sync_level`這一列,可以發現由默認的0變成了2,這就表示強同步開啟成功了。如果需要關閉強同步,在主實例上設置`loose_innodb_primary_sync_slave`為0,只讀節點上設置`loose_slave_trans_sync_level `設置為0即可,注意設置的順序依然不能亂。此外,如果強同步的只讀實例在`loose_innodb_primary_sync_slave_timeout`后還沒返回,強同步復制退化為異步復制,還可以通過`loose_innodb_primary_sync_slave`參數控制當只讀節點掉線時是否立刻退化為異步復制。
另外一種解決辦法是通過PROXY來解決。主實例每次做完更新就會把當前的日志位點發給PROXY,同時PROXY也會定期去輪詢最大的日志位點,當PROXY需要把后續的查詢發到只讀實例上時,首先會判斷只讀實例是否應用到了最新的位點,如果不是,就把請求轉發到主實例。這個策略操作的單位是連接,即通過這種方法能保證同一個連接中讀到的一定是最新的數據。這種方法雖然會導致主庫的壓力變大,但是其對性能影響較小,是一種推薦的方法。如果用戶需要使用,聯系售后做一次小版本升級,即可開放這個功能。
## BINLOG問題
POLARDB使用基於redolog的物理復制來構建復制關系,不依賴BINLOG,因此BINLOG默認是關閉的,但是許多用戶需要使用BINLOG將數據同步到第三方數據倉庫以方便做復雜的數據分析,所以有很多開啟BINLOG的需求。用戶可以開啟BINLOG,但是與RDS不同,后台不但不會有任務定時上傳備份BINLOG,而且也不會有定期刪除BINGLOG的任務,完全需要用戶自己控制何時刪除,否則會導致BINLOG堆積,從而造成更多的存儲成本。因為POLARDB不依賴BINLOG復制,我們不清楚用戶已經消費了多少日志(有可能用戶的SLAVE端使用了類似復制延遲的技術),因此,需要用戶自己決定何時清除日志。
用戶可以通過主實例(考慮到HA切換,最好把所有只讀實例也打開)參數`loose_polar_log_bin`打開BINLOG(需要重啟),BINLOG就會自動存儲在日志目錄下,空間統計在日志空間使用量里面。可以通過常規的`show master logs`查看BINLOG。每個BINLOG的大小,BINLOG cache的大小,BINLOG格式等參數都可以通過控制台調整。這里注意下,由於POLARDB使用的是自研的存儲系統,`sync_binlog`參數無效,因此沒有開放。
如果用戶需要刪除無用的BINLOG,目前有一種方法:通過調節參數`loose_expire_logs_hours`來控制BINLOG自動刪除的時間,這個參數表示自BINLOG創建后多久系統自動將它刪除,單位是小時。注意,刪除前請務必確保BINLOG已經無效,誤刪除后將無法恢復(BINLOG是否需要后台上傳OSS來作為備份,這個需求我們會參考用戶的反饋來決定是否支持)。
目前,開啟BINLOG有一個限制:當底層存儲系統升級的時,開啟BINLOG的實例不可服務時間目前是分鍾級別的,不開啟BINLOG的實例是秒級別的。所以,如果用戶對實例可用性要求比較高,可以等我們優化后再開啟BINLOG。
## 限制問題
POLARDB由於使用了自研的文件系統和自研的塊設備存儲系統,因此在一些限制上與RDS MySQL有所不同。
由於文件系統是集成在數據庫里面的,即數據庫與文件系統共用一個進程,所以文件系統會占用一部分的規格內存。另外不同規格的文件個數也有上限。目前存儲最大支持到10000GB。
此外,文件名,即數據庫中的表名和庫名都不能超過63個字符,實際使用的時候最好控制在55個字符以下,因為還有.frm,.ibd后綴,中間過程臨時表等。詳細的說明見[這里](https://help.aliyun.com/document_detail/72671.html?spm=5176.10695662.1996646101.searchclickresult.38b34a84FpSWtb)
## 並發連接問題
數據庫最佳性能的線程數一般是CPU核數的2-3倍,線程數太少,不容易發揮出多線程的優勢,線程數太多,又容易導致上下文切換過多以及鎖爭搶嚴重的問題。不幸的是,很多用戶往往會創建很多並發連接,導致數據庫CPU打滿,性能低下。
為了解決頻繁創建釋放連接的問題,即高頻短連接問題,可以調大`thread_cache_size`,從而減少頻繁創建連接的開銷。另外,也建議用戶使用客戶端連接池來代替高頻短連接的方案。
為了解決高並發連接的問題,可以使用Thread Pool功能。在Thread Pool模式下,用戶連接和處理線程不再是一一對應關系。處理線程的數量是一個可控的數量,不會隨着用戶連接數的增多而大幅增加,這樣可以減少高並發場景下線程上下文切換的消耗。
用戶可以通過調整參數`loose_thread_handling`為`pool-of-threads`來打開Thread Pool功能。同時,建議調整參數`thread_pool_size`為實例CPU核數,其他參數保持默認即可。
Thread Pool比較適合短小的查詢和更新,大事務大查詢會降低其效果。用戶需要依據業務模型來斟酌。另外需要注意一點,Thread Pool不會提高性能,但是其能穩定高並發場景下的性能。
## 總結
本文簡單介紹了POLARDB常見的幾種問題,大多數來源於用戶真實的反饋。我們也在不斷的探索更多的功能以及更好的交互。如果在使用POLARDB中遇到疑惑,不要猶豫請立刻聯系我們,我們會給您最滿意的答復,謝謝對POLARDB的支持。