TiDB v6.0.0 (DMR) :快取表初試丨TiDB Book Rush

語言: CN / TW / HK

本文作者:啦啦啦啦啦,TiDB 老粉,目前就職於京東物流,社群資深使用者,asktug 主頁

一、背景

一般情況下使用 TiDB 單表大小為千萬級別以上在業務中效能最優,但是在實際業務中總是會存在小表。例如配置表對寫請求很少,而對讀請求的效能的要求更高。TiDB 作為一個分散式資料庫,大表的負載很容易利用分散式的特性分散到多臺機器上,但當表的資料量不大,訪問又特別頻繁的情況下,資料通常會集中在 TiKV 的一個 Region 上,形成讀熱點,更容易造成效能瓶頸。

TiDB v6.0.0(DMR) 版本推出了快取表的功能,第一次看到這個詞的時候讓我想到了 MySQL 的記憶體表。MySQL 記憶體表的表結構建立在磁碟上,資料存放在記憶體中。記憶體表的缺點很明顯。當 MySQL 啟動著的時候,表和資料都存在,當 MySQL 重啟後,表結構存在,資料消失。TiDB 的快取表不存在這個問題。從 asktug 論壇中看到很多小夥伴都很期待快取表的表現,個人也對它的效能很期待,因此在測試環境中實際看看快取表的效能如何。

二、快取表的使用場景

以下部分內容來自官方文件,詳情見 快取表

TiDB 快取表功能適用於以下特點的表: - 表的資料量不大 - 只讀表,或者幾乎很少修改 - 表的訪問很頻繁,期望有更好的讀效能

關於第一點官方文件中提到快取表的大小限制為包含索引在內的所有 key-value 記錄的總大小不能超過 64 MB。實際測試使用 Sysbench 生成下文中表結構的表從 20w 提高到 30w 資料量時無法將普通錶轉換為快取表,因此生產環境中實際使用快取表的場景應該最多不超過幾十萬級別的資料量。關於快取表對包含讀寫操作方面的效能,使用多種不同的讀寫請求比例進行了測試,相較普通表均沒有達到更好的效能表現。這是因為為了讀取資料的一致性,在快取表上執行修改操作後,租約時間內寫操作會被阻塞,最長可能出現 tidb_table_cache_lease 變數值時長的等待,會導致QPS降低。因此快取表更適合只讀表,或者幾乎很少修改的場景。

快取表把整張表的資料從 TiKV 載入到 TiDB Server 中,查詢時可以不通過訪問 TiKV 直接從 TiDB Server 的快取中讀取,節省了磁碟 IO 和網路頻寬。使用普通表查詢時,返回的資料量越多索引的效率可能越低,直到和全表掃描的代價接近優化器可能會直接選擇全表掃描。快取表本身資料都在 TiDB Server 的記憶體中,可以避免磁碟 IO,因此查詢效率也會更高。以配置表為例,當業務重啟的瞬間,全部業務連線一起載入配置,會造成較高的資料庫讀延遲。如果使用了快取表,讀請求可以直接從記憶體中讀取資料,可以有效降低讀延遲。在金融場景中,業務通常會同時涉及訂單表和匯率表。匯率表通常不大,表結構很少發生變化因此幾乎不會有 DDL,加上每天只更新一次,也非常適合使用快取表。其他業務場景例如銀行分行或者網點資訊表,物流行業的城市、倉號庫房號表,電商行業的地區、品類相關的字典表等等,對於這種很少新增記錄項的表都是快取表的典型使用場景。

三、測試環境

1.硬體配置及叢集拓撲規劃

使用 2 臺雲主機,硬體配置為 4C 16G 100G 普通 SSD 硬碟。 | Role | Host | Ports | | :---: | :---: | :---: | | alertmanager | 10.0.0.1 | 9093/9094 | | grafana | 10.0.0.1 | 3000 | | pd | 10.0.0.1 | 2379/2380 | | pd | 10.0.0.2 | 2379/2380 | | pd | 10.0.0.1 | 3379/3380 | | prometheus | 10.0.0.1 | 9090/12020 | | tidb | 10.0.0.1 | 4000/10080 | | tidb | 10.0.0.2 | 4000/10080 | | tikv | 10.0.0.1 | 20162/20182 | | tikv | 10.0.0.1 | 20160/20180 | | tikv | 10.0.0.2 | 20161/20181 |

2. 軟體配置 | 軟體名稱 | 軟體用途 | 版本 | | :---: | :---: | :---: | | CentOS | 作業系統 | 7.6 | | TiDB 叢集 | 開源分散式 NewSQL 資料庫 | v6.0.0 DMR | | Sysbench | 壓力測試工具 | 1.0.20 |

3.引數配置 ```sql server_configs: tidb: log.slow-threshold: 300 new_collations_enabled_on_first_bootstrap: true

tikv: readpool.coprocessor.use-unified-pool: true readpool.storage.use-unified-pool: false pd: replication.enable-placement-rules: true replication.location-labels: - host ``` 由於硬體條件受限,只有 2 臺普通效能的雲主機混合部署的叢集(實際上和單機部署也差不多了)。單機 CPU 核數較少且 TiDB Server 沒有做負載均衡所以併發無法調整太高。以下測試均使用一個 TiDB Server 節點進行壓測,因此不用特別關注本次測試的測試資料,可能會跟其他測試結果有所出入,不代表最佳效能實踐和部署,測試結果僅限參考。

四、效能測試

Sysbench 生成的表結構 sql CREATE TABLE sbtest1 ( id int(11) NOT NULL AUTO_INCREMENT, k int(11) NOT NULL DEFAULT '0', c char(120) NOT NULL DEFAULT '', pad char(60) NOT NULL DEFAULT '', PRIMARY KEY (id), KEY k_1 (k) ) ENGINE = InnoDB CHARSET = utf8mb4 COLLATE = utf8mb4_bin AUTO_INCREMENT = 1

讀效能測試

測試主要引數 oltp_point_select 主鍵查詢測試(點查,條件為唯一索引列)

主要 SQL 語句:

SELECT c FROM sbtest1 WHERE id=?

select_random_points 隨機多個查詢(主鍵列的 selete in 操作)

主要 SQL 語句:

SELECT id, k, c, pad FROM sbtest1 WHERE k IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

select_random_ranges 隨機範圍查詢(主鍵列的 selete between and 操作)

主要 SQL 語句:

SELECT count(k) FROM sbtest1 WHERE k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ? OR k BETWEEN ? AND ?

oltp_read_only 只讀操作(包含聚合、去重等)

主要 SQL 語句:

SELECT c FROM sbtest1 WHERE id=?

SELECT SUM(k) FROM sbtest1 WHERE id BETWEEN ? AND ?

SELECT c FROM sbtest1 WHERE id BETWEEN ? AND ? ORDER BY c

SELECT DISTINCT c FROM sbtest1 WHERE id BETWEEN ? AND ? ORDER BY c

Sysbench 測試命令示例 ```sql sysbench --mysql-host=10.0.0.1 --mysql-port=4000 --mysql-db=sbtest --mysql-user=root --time=600 \ --threads=8 --report-interval=10 --db-driver=mysql oltp_point_select --tables=1 --table-size=5000 run

sysbench --mysql-host=10.0.0.1 --mysql-port=4000 --mysql-db=sbtest --mysql-user=root --time=600 \ --threads=8 --report-interval=10 --db-driver=mysql oltp_read_only --tables=1 --table-size=5000 run

sysbench --mysql-host=10.0.0.1 --mysql-port=4000 --mysql-db=sbtest --mysql-user=root --time=600 \ --threads=8 --report-interval=10 --db-driver=mysql select_random_points --tables=1 --table-size=5000 run

sysbench --mysql-host=10.0.0.1 --mysql-port=4000 --mysql-db=sbtest --mysql-user=root --time=600 \ --threads=8 --report-interval=10 --db-driver=mysql select_random_ranges --tables=1 --table-size=5000 run ```

一、使用普通表

1.單表資料量 5000,測試 QPS | threads/request type | oltp_point_select | oltp_read_only | select_random_points | select_random_ranges | | --- | --- | --- | --- | --- | | 8 | 2214 | 1985 | 3190 | 2263 | | 16 | 3199 | 2414 | 3412 | 2491 | | 32 | 4454 | 2867 | 3898 | 2743 | | 64 | 5792 | 3712 | 4321 | 2981 | | 128 | 7639 | 4964 | 4474 | 2965 |

2.單表資料量 50000,測試 QPS |threads/request type| oltp_point_select| oltp_read_only |select_random_points |select_random_ranges| | --- | --- | --- | --- | --- | |8 |4874 |2808| 2841 |2207| |16 |5042 |3429 |3172 |2448| |32| 6754 |4290|3405 |2651| |64| 8989 |5282 |3831 |2818| |128| 12565 |6470 |3996| 2811|

二、使用快取表

1.單表資料量 5000,測試 QPS |threads/request type| oltp_point_select |oltp_read_only |select_random_points |select_random_ranges| | --- | --- | --- | --- | --- | |8 |15780 |10811| 5666 |2716| |16 |23296 |11399 |6417| 2948| |32 |28038 |11313 |6907 |3050| |64 |32924| 11377| 7217 |3200| |128 |33962| 11413 |7199 |3232|

2.單表資料量 50000,測試 QPS

|threads/request type| oltp_point_select| oltp_read_only |select_random_points |select_random_ranges| | --- | --- | --- | --- | --- | |8 |15910| 16540 |5359| 2646| |16 |21945| 17022| 5999 |2915| |32 |25614 |17356 |6355 |3065| |64 |31782 |17410 |6690 |3088| |128 |35009 |17584 |6713 |3161|

三、效能對比

讀寫混合性能測試

測試主要場景引數 oltp_read_write 表示混合讀寫。

point_selects(每個事務裡點查的數量)

delete_inserts(每個事務裡插入/刪除組合的數量)

主要 SQL 語句:

INSERT INTO sbtest1 (id, k, c, pad) VALUES (?, ?, ?, ?)

DELETE FROM sbtest1 WHERE id=?

SELECT c FROM sbtest1 WHERE id=?

本次測試通過單個事務中請求型別的數量 --delete_inserts 固定為 10 且調整 --point_selects 引數的值來模擬不同讀寫比例下的效能差異,其餘請求引數使用預設值,具體命令可參考下面 Sysbench 測試命令示例。

Sysbench 測試命令示例 sql sysbench --mysql-host=10.0.0.1 --mysql-port=4000 --mysql-db=sbtest --mysql-user=root --time=600 --threads=8 --report-interval=10 --db-driver=mysql oltp_read_write --tables=1 --table-size=5000 --point_selects=10 --non_index_updates=0 --delete_inserts=10 --index_updates=0 run

一.使用普通表

1.單表資料量 5000,測試 QPS |threads/--point_selects |10| 40 |160 |640| | --- | --- | --- | --- | --- | |8 |869 |2289 |3852| 5090| |16 |1014| 2139 |4354 |6094| |32 |1075 |2205| 5089| 6944| |64 |605| 1861| 5160| 8395| |128 |877| 2127 |4332 |9257|

2.單表資料量 50000,測試 QPS

|threads/--point_selects| 10 |40| 160| 640| | --- | --- | --- | --- | --- | |8| 1107 |2144| 3312| 4439| |16| 1108 |2103 |3738| 5702| |32 |1055 |2228 |4325| 6770| |64| 1062 |1397|5367 |8209| |128 |981| 1838 |7235| 17472|

二、使用快取表

1.單表資料量 5000,測試 QPS

|threads/--point_selects |10 |40 |160| 640| | --- | --- | --- | --- | --- | |8| 711 |1322 |2123| 2787| |16 |361| 665 |1274| 2870| |32 |400| 627 |1394 |2997|| |64| 323 |804 |1853 |4100| |128| 372 |680| 1847| 4704|

2.單表資料量 50000,測試 QPS

|threads/--point_selects| 10| 40 |160| 640| | --- | --- | --- | --- | --- | |8 |974 |2726| 3716| 1804| |16| 787| 1366| 1736 |2176| |32| 673| 1231| 2338 |4627| |64 |572| 1384| 3120 |7755| |128| 557 |1104 |2907| 7486|

三、效能對比

五、遇到的問題

  • 嘗試將 30w 資料的表改為快取表時報錯 ERROR 8242 (HY000): 'table too large' is unsupported on cache tables

目前 TiDB 對於每張快取表的大小限制為 64 MB,因此太大的表無法快取在記憶體中。另外,快取表無法執行普通的 DDL 語句。若要對快取表執行 DDL 語句,需要先使用 ALTER TABLE xxx NOCACHE 語句去掉快取屬性,將快取表設回普通表後,才能對其執行其他 DDL 語句。

  • 測試過程中快取表效能出現了不穩定的情況,有些時候快取表反而比普通表讀取效能差,使用 trace 語句(TRACE SELECT * FROM sbtest1;)檢視發現返回結果中出現了 regionRequest.SendReqCtx,說明 TiDB 尚未將所有資料載入到記憶體中,多次嘗試均未載入完成。把 tidb_table_cache_lease 調整為 10 後沒有出現該問題。

在 asktug 中向研發大佬提出了這個問題得到了解答。根據 http://github.com/pingcap/tidb/issues/33167 中的描述,當機器負載較重時,load table 需要 3s 以上 ,但是預設的 tidb_table_cache_lease 是 3s, 表示載入的資料是立即過時的,因此需要重新載入,並且該過程永遠重複。導致了浪費了大量的 CPU 資源並且降低了 QPS。目前可以將 tidb_table_cache_lease 的值調大來解決,該問題在 master 分支中已經解決,後續版本應該不會出現。

根據測試結果,寫入較為頻繁的情況下快取表的效能是比較差的。在包含寫請求的測試中,快取表相較於普通表的效能幾乎都大幅下降。

在 lease 過期之前,無法對資料執行修改操作。為了保證資料一致性,修改操作必須等待 lease 過期,所以會出現寫入延遲。例如 1tidb_table_cache_lease1 為 10 時,寫入可能會出現較大的延遲。因此寫入比較頻繁或者對寫入延遲要求很高的業務不建議使用快取表。

六、測試總結

讀效能

單表 5000,快取表相比普通表提升的百分比

|threads/request type| oltp_point_select| oltp_read_only |select_random_points| select_random_ranges| | --- | --- | --- | --- | --- | |8| 612.73%| 444.63%| 77.61%| 20.01%| |16| 628.22%| 372.20%| 88.01%| 18.34%| |32| 529.50% |294.59% |77.19% |10.38%| |64 |468.43%| 206.49% |67.02% |7.34%| |128| 344.58% |129.91% |60.90%| 9.00%|

單表 50000,快取表相比普通表提升的百分比

|threads/request type |oltp_point_select |oltp_read_only |select_random_points| select_random_ranges| | --- | --- | --- | --- | --- | |8 |226.42% |489.03% |88.63%| 19.89%| |16 |335.24%| 396.41% |89.12% |19.07%| |32| 279.24% |304.56%| 86.63% |15.61%| |64| 253.56%| 229.60%| 74.62%| 9.58%| |128 |178.62% |171.77%| 67.99%| 12.45%|

讀寫混合

單表 5000,快取表相比普通表提升的百分比(負增長符合預期)

|threads/--point_selects |10 |40| 160| 640| | --- | --- | --- | --- | --- | |8| -35.77%| -42.24%| -44.88%| -45.24%| |16 |-64.39%| -68.91%| -70.73 |-52.90%| |32| -62.79%| -71.56% |-72.60%|-56.84%| |64 |-46.61%| -42.44% |-64.08% |-50.05%| |128|-57.58%| -68.03%| -57.36% |-49.18%|

單表 50000,快取表相比普通表提升的百分比(負增長符合預期)

|threads/--point_selects| 10 |40| 160| 640| | --- | --- | --- | --- | --- | |8 |-12.01%| 27.14%| 12.19% |-59.36%| |16| -28.97% |-35.04% |-53.55% |-61.83%| |32 |-36.20% |-44.74% |-45.94% |-31.65%| |64 |-46.13% |-0.93% |-41.86% |-5.53%| |128| -43.21% |-39.93%| -59.82%| -57.15%|

結果顯示,相比於普通表,快取表在 oltp_point_select、oltp_read_only、select_random_points、select_random_ranges 幾種只讀的場景下效能有非常大的提升,但在包含寫請求的測試中無法提供更好的效能。它的機制決定了使用場景目前僅限於表的資料量不大的只讀表,或者幾乎很少修改的小表。綜上,雖然快取表目前的使用場景相對比較單一,但是在合適的場景下確實是一個解決了業務痛點的好功能,也期待在後續的版本中能有更高的穩定性和更優秀的效能表現。