天天看點

OceanBase資料庫實踐入門——性能測試建議

概述

本文主要分享針對想壓測OceanBase時需要了解的一些技術原理。這些建議可以幫助使用者對OceanBase做一些調優,再結合測試程式快速找到适合業務的最佳性能。由于OceanBase自身參數很多、部署形态也比較靈活,這裡并沒有給出具體步驟。

資料庫讀寫特點

壓測的本質就是對一個會話的邏輯設計很高的并發。首先需要了解單個會話在資料庫内部的讀寫邏輯。比如說,業務會話1對資料庫發起一個DML SQL,第一次修改某筆記錄,資料庫會怎麼做呢?

為了便于了解OB的行為,我們先看看ORACLE是怎麼做的。後面有對比才可以加深了解。

ORACLE 讀寫特點

ORACLE會話第一次修改一行記錄,如果該記錄所在塊(8K大小)不在記憶體(Buffer Cache)裡時會先從磁盤檔案裡讀入到記憶體裡。這個稱為一次實體讀,為了性能考慮,ORACLE一次會連續讀取相鄰的多個塊。然後就直接在該塊上修改,修改之前會先記錄REDO和UNDO(包括UNDO的REDO)。然後這個資料塊就是髒塊(Dirty Block)。假設事務沒有送出,其他會話又來讀取這個記錄,由于隔離級别是讀已送出(READ COMMITTED),ORACLE會在記憶體裡克隆目前資料塊到新的位置,新塊包含了最新的未送出資料。然後ORACLE在新塊上逆向應用UNDO連結清單中的記錄,将資料塊復原到讀需要的那個版本(SCN),然後才能讀。這個也稱為一次一緻性讀(Consistency Read),這個新塊也稱為CR塊。

OceanBase資料庫實踐入門——性能測試建議

即使是修改一條記錄一個字段的幾個位元組,整個塊(8K大小)都會是髒塊。随着業務持續寫入,大量髒塊會消耗資料庫記憶體。是以ORACLE會有多重機制刷髒塊到磁盤資料檔案上。在事務日志切換的時候也會觸發刷髒塊操作。如果業務壓力測試ORACLE,大量的寫導緻事務日志切換很頻繁,對應的刷髒操作可能相對慢了,就會阻塞日志切換,也就阻塞了業務寫入。這就是ORACLE的特點。解決辦法就是加大事務日志檔案,增加事務日志成員或者用更快的磁盤存放事務日志和資料檔案。

ORACLE裡一個表就是一個Segment(如果有大對象列還會有獨立的Segment,這個先忽略),Segment由多個不一定連續的extent組成,extent由連續的Block(每個大小預設8K)組成,extent缺點是可能會在後期由于頻繁删除和插入産生空間碎片。

OceanBase 讀寫特點

OceanBase會話第一次修改一行記錄,如果該記錄所在塊(64K大小)不在記憶體(Block Cache)裡時也會先從磁盤檔案裡讀入到記憶體裡。這個稱為一次實體讀。然後要修改時跟ORACLE做法不同的是,OceanBase會新申請一小塊記憶體用于存放修改的内容,并且連結到前面Block Cache裡該行記錄所在塊的那筆記錄下。如果修改多次,每次修改都跟前面修改以連結清單形式關聯。同樣在修改之前也要先在記憶體裡記錄REDO。每次修改都會記錄一個内部版本号,記錄的每個版本就是一個增量。其他會話讀取的時候會先從Block Cache中該記錄最早讀入的那個版本(稱為基線版本)開始讀,然後疊加應用後面的增量版本直到合适的版本(類似ORACLE中SCN概念)。(随着版本演進,這裡細節邏輯可能會有變化。)

OB的這個讀方式簡單說就是從最早的版本讀起,逐漸應用增量(類似REDO,但跟REDO日志無關)。而ORACLE一緻性讀是從最新的版本讀起,逐漸復原(應用UNDO)。在OB裡,沒有UNDO。當版本鍊路很長時,OB的讀性能會略下降,是以OB也有個checkpoint線程定時将記錄的多個版本合并為少數幾個版本。這個合并稱為小合并(minor compaction)。此外,OB在記憶體裡針對行記錄還有緩存,

OceanBase資料庫實踐入門——性能測試建議

從上面過程還可以看出,每次修改幾個位元組,在記憶體裡的變髒的塊隻有增量版本所在的塊(預設寫滿才會重新申請記憶體),基線資料塊是一直不變化。是以OB裡髒塊産生的速度非常小,髒塊就可以在記憶體裡儲存更久的時間。實際上OB的設計就是髒塊預設不刷盤。那如果機器挂了,會不會丢資料呢?

OB跟ORACLE一樣,修改資料塊之前會先記錄REDO,在事務送出的時候,REDO要先寫到磁盤上(REDO同時還會發送往其他兩個副本節點,這個先忽略)。有REDO在,就不怕丢資料。此外,增量部分每天還是會落盤一次。在落盤之前,記憶體中的基線資料和相關的增量資料會在記憶體裡進行一次合并(稱Merge),最終以SSTable的格式寫回到磁盤。如果說記憶體裡塊内部産生碎片,在合并的那一刻,這個碎片空間基本被消弭掉了。是以說OB的資料檔案空間碎片很小,不需要做碎片整理。同時OB的這個設計也極大降低了LSM的寫放大問題。

當業務壓測寫OB時,髒塊的量也會增長,最終達到增量記憶體限制,這時候業務就無法寫入,需要OB做合并釋放記憶體。OB的合并比較耗IO、CPU(有參數可以控制合并力度),并且也不會等到記憶體用盡才合并,實際會設定一個門檻值。同時為了規避合并,設計了一個轉儲機制。當增量記憶體使用率超過門檻值後,就開啟轉儲。轉儲就是直接把增量記憶體寫到磁盤上(不合并)。轉儲對性能的影響很小,可以高峰期發生,并且可以轉儲多次(參數配置)。

OceanBase資料庫實踐入門——性能測試建議

OB增量記憶體就類似一個水池,業務寫是進水管在放水, 轉儲和大合并是出水管。水位就是目前增量記憶體使用率。當進水的速度快于出水,池子可能就會滿。這時候業務寫入就會報記憶體不足的錯誤。

這就是OB讀寫的特點,解決方法就是加大OB記憶體、或者允許OB自動對業務寫入速度限流。

OceanBase部署建議

OB 在commit的時候redo落盤會寫磁盤。讀資料的時候記憶體未命中的時候會有實體讀,轉儲和大合并的時候落盤會有密集型寫IO。這些都依賴磁盤讀寫性能。是以建議磁盤都是SSD盤,并且建議日志盤和資料盤使用獨立的檔案系統。如果是NVME接口的閃存卡或者大容量SSD盤,那日志盤和資料盤放在一起也可以。不要使用LVM對NVME接口的大容量SSD做劃分,那樣瓶頸可能會在LVM自身。

OB的增量通常都在記憶體裡,記憶體不足的時候會有轉儲,可以轉儲多次。盡管如此,建議測試機器的記憶體不要太小,防止頻繁的增量轉儲。通常建議192G記憶體以上。

OB叢集的節點數至少要有三個。如果是功能了解,在單機上起3個OB程序模拟三節點是可以的,但是如果是性能測試,那建議還是使用三台同等規格的實體機比較合适。機器規格不一緻時,最小能力的機器可能會制約整個叢集的性能。 OceanBase叢集的手動部署請參考《OceanBase資料庫實踐入門——手動搭建OceanBase叢集》。在部署好OceanBase之後,建議先簡單了解一下OceanBase的使用方法,詳情請參考文章《OceanBase資料庫實踐入門——常用操作SQL》。

如果要驗證OB的彈性縮容、水準擴充能力,建議至少要6節點(部署形态2-2-2)。并且測試租戶(執行個體)的每個Zone裡的資源單元數量至少也要為2個,才可以發揮多機能力。這是因為OB是多租戶設計,對資源的管理比較類似雲資料庫思想,是以裡面設計有點精妙,詳情請參見《揭秘OceanBase的彈性伸縮和負載均衡原理》。

下面是一個租戶的測試租戶資源初始化建議

  • 登入sys租戶
create resource unit S1, max_cpu=2, max_memory='10G', min_memory='10G', max_iops=10000, min_iops=1000, max_session_num=1000000, max_disk_size=536870912;
create resource unit S2, max_cpu=4, max_memory='20G', min_memory='20G', max_iops=20000, min_iops=5000, max_session_num=1000000, max_disk_size=1073741824;
create resource unit S3, max_cpu=8, max_memory='40G', min_memory='40G', max_iops=50000, min_iops=10000, max_session_num=1000000, max_disk_size=2147483648;
select * from __all_unit_config;

create resource pool pool_demo unit = 'S2', unit_num = 2;
select * from __all_resource_pool order by resource_pool_id desc ;

create tenant t_obdemo resource_pool_list=('pool_demo');
alter tenant t_obdemo set variables ob_tcp_invited_nodes='%';           

請注意上面的unit_num=2這個很關鍵。如果unit_num=1,OB會認為這個租戶是個小租戶,後面負載均衡處理時會有個預設規則。

  • 登入業務租戶
create database sbtest;
grant all privileges on sbtest.* to sbuser@'%' identified by 'sbtest';           

sysbench壓測建議

因為測試場景跟業務有關,這裡就以常見的sysbench場景舉例分析

sysbench工具可以建幾個結構相同的表,然後執行純讀、純寫、讀寫混合。其中讀又分根據主鍵或者二級索引查詢,等值查詢、IN查詢或範圍查詢幾種。詳細的可以檢視官方介紹。

如果用sysbench壓測OB,建立很多表是一種方法。另外一種方法就是建立分區表。OceanBase是分布式資料庫,資料遷移和高可用的最小粒度是分區,分區是資料表的子集。分區表有多個分區,非分區表隻有一個分區。分區表的拆分細節是業務可以定義的。OceanBase可以将多個分區分布到不同節點,也可能不會分布到多個節點。這個取決于OB叢集和租戶的規劃設計。是以對sysbench建立的表(分區),在性能分析時要分析分區具體的位置。

建表準備

下面是sysbench裡分區表示例,修改 oltp_common.lua:

query = string.format([[
CREATE TABLE sbtest%d(
  id %s,
  k INTEGER DEFAULT '0' NOT NULL,
  c CHAR(120) DEFAULT '' NOT NULL,
  pad CHAR(60) DEFAULT '' NOT NULL,
  %s (id,k)
) partition by hash(k) partitions %s %s %s]],
      table_num, id_def, id_index_def, part_num, engine_def, extra_table_options)           

需要注意用分區表後,主鍵列和唯一索引列需要包含分區鍵。分區表的索引有本地(LOCAL)索引和全局索引兩種。本地索引的存儲跟資料是在一起的,全局索引的存儲是獨立的。全局索引是為了應對查詢條件不是分區鍵的場景,沒有全局索引時,會掃描所有分區的本地索引。這兩種索引的性能優劣沒有定論,以實際業務場景測試為準。此外,傳統資料庫索引對修改操作會有負面影響,分布式資料庫的全局索引對修改操作的影響可能更大,因為一個簡單的DML語句都會因為要同步修改全局索引而産生分布式事務。而本地索引就沒有分布式事務問題。是以對全局索引的使用場景要謹慎評估。這個特性不是OB特有,隻要是分布資料庫都會面臨這個問題。

資料分布均衡

本節是說明OB資料分布背後的原理和方法。

OceanBase是分布式資料庫,它通過每個機器上的observer程序将多個機器的資源能力聚合成一個大的資源池,然後再為每個業務配置設定不同資源規格的租戶(執行個體)。是以每個業務租戶(執行個體)的資源能力都隻是整個叢集能力的子集。業務并不一定能使用到全部機器資源。

OceanBase裡的資料都有三份,細到每個分區有三個副本,角色上有1個leader副本2個follower副本。預設隻有leader副本提供讀寫服務,leader副本所在的節點才有可能提供服務,有負載。OceanBase調整各個節點負載的方法是通過調整内部leader副本的位置實作的。這個調整可以讓OceanBase自動做,也可以手動控制。

OceanBase自動負載均衡是參數enable_rebalance控制,預設值為True。可以檢視确認。修改用alter system語句。

alter system set enable_rebalance=True;
show parameters like 'enable_rebalance';           
OceanBase資料庫實踐入門——性能測試建議

檢視實際表的分區leader副本位置

##檢視分區分布
SELECT t5.tenant_id,t5.tenant_name,t3.database_name, t4.tablegroup_name, t2.table_name, t1.partition_id, concat(t1.svr_ip,':',t1.svr_port) observer, t1.role, t1.data_size,t1.row_count
from `gv$partition` t1 join `__all_table` t2 on (t1.tenant_id=t2.tenant_id and t1.table_id=t2.table_id)
    join `__all_database` t3 on (t2.tenant_id=t3.tenant_id and t2.database_id=t3.database_id)
    left join `__all_tablegroup` t4 on (t1.tenant_id=t4.tenant_id and t1.tablegroup_id=t4.tablegroup_id)
    join `__all_tenant` t5 on (t1.tenant_id=t5.tenant_id)
where t5.tenant_id = 1020 and role=1 and database_name in ('sysbenchtest')
order by t5.tenant_id,t3.database_id,t1.tablegroup_id,t1.partition_id, t1.role;           

sysbench指令參數

sysbench的機制是壓測過程中如果有錯誤就會報錯退出,是以需要針對一些常見的錯誤進行忽略處理,這樣sysbench會話可以重試繼續運作。比如說主鍵或者唯一鍵沖突、事務被殺等等。

下面的運作指令供參考

  • 初始化
./sysbench --test=./oltp_read_only.lua --mysql-host=***.***.82.173 --mysql-port=4001 --mysql-db=test --mysql-user="sbuser"  --mysql-password=sbtest --tables=16 --table_size=100000000 --threads=32 --time=300 --report-interval=5 --db-driver=mysql --db-ps-mode=disable --skip-trx=on --mysql-ignore-errors=6002,6004,4012,2013,4016 prepare           
  • 純讀
./sysbench --test=./oltp_read_only.lua --mysql-host=***.***.82.173 --mysql-port=4001 --mysql-db=test --mysql-user="sbuser"  --mysql-password=sbtest --tables=16 --table_size=100000000 --threads=96 --time=600  --db-driver=mysql --db-ps-mode=disable --skip-trx=on --mysql-ignore-errors=6002,6004,4012,2013,4016 --secondary=on run           
  • 純寫
./sysbench --test=./oltp_write_only.lua --mysql-host=***.***.82.173 --mysql-port=4001 --mysql-db=test --mysql-user="sbuser"  --mysql-password=sbtest --tables=16 --table_size=100000000 --threads=32 --time=600 --report-interval=5 --db-driver=mysql --db-ps-mode=disable --skip-trx=on --mysql-ignore-errors=6002,6004,4012,2013,4016,1062 run           
  • 讀寫混合
./sysbench --test=./oltp_read_write.lua --mysql-host=***.***.82.173 --mysql-port=4001 --mysql-db=test --mysql-user="sbuser"  --mysql-password=sbtest --tables=16 --table_size=100000000 --threads=32 --time=600 --report-interval=5 --db-driver=mysql --db-ps-mode=disable --skip-trx=on --mysql-ignore-errors=6002,6004,4012,2013,4016,1062 run           

測試觀察

在純寫或者讀寫測試中,注意觀察增量增量記憶體使用進度。如果寫入速度比轉儲和合并速度還快,那會碰到記憶體不足寫入失敗錯誤。這就是OB租戶資源相對不足了。觀察這個記憶體使用進度可以通過 dooba腳本。dooba腳本預設在/home/admin/oceanbase/bin/目錄下。

使用示例如下:

python dooba -h11..84.84 -uroot@sys#obdemo -P2883 -p**

OB有個内部視圖gv$sql_audit可以檢視執行過所有成功或失敗的SQL,用來分析具體的SQL性能。用法詳情參見官網(oceanbase.alipay.com) 或 文章《阿裡資料庫性能診斷的利器——SQL全量日志》。

select /*+ read_consistency(weak) query_timeout(1000000000) */ usec_to_time(request_time) req_time, svr_Ip, trace_id, sid, client_ip, tenant_id,tenant_name,user_name,db_name, query_sql, affected_rows,ret_code, event, state, elapsed_time, execute_time, queue_time, decode_time, get_plan_time, block_cache_hit, bloom_filter_cache_Hit, block_index_cache_hit, disk_reads,retry_cnt,table_scan, memstore_read_row_count, ssstore_read_row_count, round(request_memory_used/1024/1024) req_mem_mb
from gv$sql_audit 
where tenant_id=1012 and user_name in ('demouser') 
order by request_time desc 
limit 100;           

經驗總結

本節是一些測試場景的經驗總結,需要提前了解一些OceanBase的原理特性介紹。

實際測試情形可能會出現由于是三節點部署,是以測試時壓力都打到一台伺服器上,這個建議用六節點測試。還有個辦法就是禁用自動負載均衡手動調整分區位置。調整是OB給業務的手段,業務上有些表會有JOIN,為了性能(避免跨節點請求),業務需要這種幹預能力。

還有一種情形是寫入壓力非常大,跑了一段時間後報記憶體不足的提示,這個就是租戶記憶體資源相對寫入速度和量不足了(OB的轉儲和合并對記憶體的回收趕不上寫入對記憶體的消耗),此時需要擴容或者調整測試需求。OB 2.x版本還有自動對應用寫入限速功能(自我保護),這個會影響測試報告裡性能結果。如果資料庫分析sql性能以及分布式調優都做了,那可以認為目前寫入的TPS就是資料庫的寫入峰值了。需要注意的是不同的硬體,不同的租戶規格,不同的測試場景,這個TPS能力都表現不同。

sysbench有個batch insert功能,當表是分區表的時候,預設這個batch insert 很可能會産生分布式事務,性能比單表寫入要慢。需要靠提高用戶端并發數來提升總的吞吐量。此外這個批量的大小不宜太大。由于OB支援SQL執行計劃緩存,SQL文本過大且并發很高時,會在SQL解析環節面臨記憶體不足問題。資料庫記憶體的大部分還是主要用于存取資料。JAVA的addBatch方法也同理,建議批量大小設定為100以内。

在查詢驗證資料的時候,可能會碰到逾時類錯誤。OB裡逾時的可能場景有多個:

  • SQL語句逾時,由租戶變量 ob_query_timeout控制。機關是微秒,預設是10秒。
  • 事務空閑逾時,由租戶變量 ob_trx_idle_timeout控制。機關是微秒,預設120秒。
  • 事務逾時,由租戶變量 ob_trx_timeout控制。機關是微秒,預設100秒。

以上逾時時間都可以在租戶裡根據實際情況修改。

關于OB的錯誤号解釋可以檢視官網系統錯誤碼 (

https://oceanbase.alipay.com/docs/oceanbase/%E5%8F%82%E8%80%83%E7%B1%BB/%E7%B3%BB%E7%BB%9F%E9%94%99%E8%AF%AF%E7%A0%81/tslkmg)

其他

關于使用分布式資料庫測試,不同的做法可以得到不同的結果。即使是同一個OB租戶(執行個體),不同的人設計的表結構不同,或者SQL不同都有可能取得不同的性能資料。這些不同都是可以從原理上給出解釋。熟知這些原理的更容易發揮OB的分布式資料庫特點。使用越深入會發現這個原理越多且更加有趣。當然環境、場景的不同,還是會遇到一些問題。如果有碰到問題,歡迎公衆号留言讨論。

推薦閱讀

OceanBase資料庫實踐入門——常用操作SQL OceanBase分區表有什麼不同? 阿裡資料庫性能診斷的利器——SQL全量日志 揭秘OceanBase的彈性伸縮和負載均衡原理

更多後續分享敬請關注公衆号:obpilot

OceanBase資料庫實踐入門——性能測試建議