一、介紹
延遲是AlwaysON的最大敵人之一。對AlwaysON而言,其首要目標就盡量減少(無法避免)主副本、輔助副本的數據延遲,實現主副本、輔助副本的“數據同步”。只有主副本、輔助副本的同步延遲越小越高,只讀訪問的實性性才會越高,數據庫的RTO(Estimating Failover Time )和RPO(Estimating Potential Data Loss)也才會越小。
但延遲可能存在於AlwaysON同步的各個環節中,因此,在分析現延遲情況時,應該首先理解AlwaysON的同步過程,然后切分到每個過程中進行監控和分析。
二、AlwaysON同步的6大步驟
在我的上篇文章《AlwaysON的同步原理及同步模式》中,曾介紹過AlwaysON的同步過程。歸結起來,主要包括如下六個步驟:
① log flush(primary)
② log capture(primary)
③ send(primary and secondary)
④ log receive and cache(secondary)
⑤ log hardened(secondary)
⑥ redo(secondary)
前兩個步驟發生在主副本,最后三個步驟發生在輔助副本,中間的第三個步驟發生主副本和輔助副本之間。
另外,如果是同步提交模式,還需要增加一個步驟:輔助副本在步驟5之后,會發送一個(日志硬化)確認信息給主副本,然后才能進入redo階段。
三、監控AlwaysOn的同步過程
Log Flush(Primary)
Log Flush就是將log buffer中的日志刷入到磁盤中。在SQL Server中,log buffer的大小固定為60KB,當發生事務提交時、checkpoint、或者buffer可用空間不足時,日志就會被flush磁盤中。
AlwaysON只有待主副本的日志刷入磁盤后才能繼續后面的步驟(為了保護主副本的數據一致性)。因此,log flush的越快,AlwaysON后續步驟進入的就越早,延遲就越小,反之亦然。
那么,在這個階段中,如何監控log flush的速度和性能呢?通常,我們使用如下兩個性能計數器:
- Avg. Disk sec/Write
這是一個磁盤的性能計數器,這個指標反映的是flush期間磁盤的寫操作的平均響應時間,如果10ms以內,說明磁盤性能非常好,如果在10ms到20ms,說明性能較好,如果在20ms到50ms說明性能較差,如果在50ms以上,說明性能很差。
這個指標直接反映了每秒flush的日志大小,因在不同的時段、不同的業務中日志產生的大小可能不同,因此不能提供一個標准值用來衡量flush性能的好壞,不過,當這個值很大時,說明數據庫操作(增、刪、改)比較頻繁,需要引起注意,結合后續步驟中的其他指標一起分析。
log capture(primary)
log capture發生在log flush之后,是AlwaysON中最重要的階段(個人認為)。在這個階段中,主副本上會為每個輔助副本維護一個發送隊列,隊列的內容就是從日志緩沖或者磁盤中capture的日志,而隊列的大小則反映了主副本和輔助副本數據不同步的差量。
顯然,在這個階段最可能影響AlwaysON同步性能的兩個關鍵因素就是:capture日志的來源(是磁盤還是內存)和隊列的大小。
在上述兩個因素中,我需要着重解釋下capture日志的來源:是磁盤還是內存?我們自導,從內存中讀取數據效率要遠遠高於從磁盤中讀取,所以在AlwaysON中,SQL Server會盡可能多將日志緩沖到內存中。但緩沖的大小是有限的,如果日志量太大、緩沖的大小不足,則不得不讀取磁盤。
下面我們來熟悉下如何監控日志的來源和發送隊列的大小:
監控日志的來源:
日志到底是讀取自內存還是磁盤,通過如下性能計數器可窺見一斑:
- SQL Server:Databases:Log Pool Requests/sec和SQL Server:Memory Manager:Log Pool Memory (KB)
解釋:前者表示每秒中從內存中請求日志塊的數量;后者表示log pool memory的大小;
說明:對於這兩個值,我們希望越高越好,則說明越多的日志可以從內存中讀取到。
- SQL Server:Databases:Log Pool Disk Reads/sec和SQL Server:Databases:Log Pool Cache Misses/sec
解釋:兩個性能計數器其實都表示內存中找不到要讀取的日志,必須從磁盤中讀取;
說明:如果兩個值越高,說明內存性能不足,SQL Server沒法緩沖更多的日志。
監控日志發送隊列:
在主副本上執行如下語句即可:
SELECT ag.name AS ag_name, ar.replica_server_name AS ag_replica_server, dr_state.database_id as database_id, dr_state.log_send_queue_size, is_ag_replica_local = CASE WHEN ar_state.is_local = 1 THEN N'LOCAL' ELSE 'REMOTE' END , ag_replica_role = CASE WHEN ar_state.role_desc IS NULL THEN N'DISCONNECTED' ELSE ar_state.role_desc END FROM (( sys.availability_groups AS ag JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id ) JOIN sys.dm_hadr_availability_replica_states AS ar_state ON ar.replica_id = ar_state.replica_id) JOIN sys.dm_hadr_database_replica_states dr_state on ag.group_id = dr_state.group_id and dr_state.replica_id = ar_state.replica_id;
log_send_queue_size表示主數據庫中尚未發送到輔助數據庫的日志記錄量 (KB),也就是上文說的發送隊列,如果某個輔助副本的發送隊列持續增加,說明此副本與主副本同步的數據差量就越大。
在上例中,server1是主副本,server2是輔助副本。主副本的log_send_queue_size始終為null,server2的log_send_queue_size為154067KB。顯然,此時server2明顯落后於主副本。
send(primary and secondary)
send階段是 AlwaysON同步過程中最簡單的一個步驟(個人認為)。在這個階段中,我們主要關注網絡性能就好了,因為主副本必須借助網絡才能把發送隊列的日志傳輸到到對應的輔助副本。如果網絡不好,AlwaysON就會產生延遲,更有甚者,如果是在同步提交模式下,還會導致主副本的事務遲遲不能提交。
對網絡的監控主要通過如下兩個性能計數器即可:
- Network Interface:bytes sent/sec
解釋:網卡每秒發送的字節數;
說明:對於這個指標我們不能孤立來看,需結合發送隊列的大小,當發送隊列很大,但此性能指標持續比較低時,有可能是網絡有問題(當然,如果是同步模式,需要考慮輔助副本log harden的速度才能決定是否一定跟網絡有關)。
- Network Interface:output Queue Length
解釋:網卡的發送隊列大小;
說明:一般來說,網卡的隊列大於2時,說明網絡存在擁堵,可能出現了網絡問題。
Log Receive and cache(sencondary)
log receive and cache發生在輔助副本上,表示輔助副本接受來自主副本發送的日志塊、並緩沖起來的過程。
這個階段其實跟網絡速度和內存大小有關,兩者在上文中均有介紹,這里不做過多闡述,只跟大家介紹下這個階段中AlwaysON專有性能計數器。
解釋:每秒接受的日志大小(單位為KB);
說明:接受的速度越快,說明網絡性能和內存性能均越好。
Log Hardened(sencondary)
介紹log hardened時不得不談一談第一個步驟的log flush,兩者其實是一個東西,只是表述不一樣。因為它們無論是工作原理還是對事務提交的影響都非常類似,因此對log hardened的監控,直接參考步驟一就好了。
redo(secondary)
redo的對象是輔助副本中已經固化的日志(該日志可能是內存中副本,也可能來自磁盤上),是AlwaysON中“實現”數據同步的步驟,其他步驟都是為此做鋪墊,即便是上個步驟的日志固化,它也只能保證主副本和輔助副本的數據一致,而真正的數據同步必須等待輔助副本的redo完成。
在這個階段中,有兩個因素決定了數據延遲的大小:redo的日志大小和redo的速度。下面我們從這兩個角度來了解下redo階段的性能監控:
監控redo日志大小
SELECT ag.name AS ag_name, ar.replica_server_name AS ag_replica_server, dr_state.database_id as database_id, dr_state.redo_queue_size, is_ag_replica_local = CASE WHEN ar_state.is_local = 1 THEN N'LOCAL' ELSE 'REMOTE' END , ag_replica_role = CASE WHEN ar_state.role_desc IS NULL THEN N'DISCONNECTED' ELSE ar_state.role_desc END FROM (( sys.availability_groups AS ag JOIN sys.availability_replicas AS ar ON ag.group_id = ar.group_id ) JOIN sys.dm_hadr_availability_replica_states AS ar_state ON ar.replica_id = ar_state.replica_id) JOIN sys.dm_hadr_database_replica_states dr_state on ag.group_id = dr_state.group_id and dr_state.replica_id = ar_state.replica_id;
監控redo的速度
redo的速度通過如下性能計數器即可:
解釋:輔助副本每秒完成redo的字節數;
說明:在分析此指標時,必須結合redo的日志大小,將兩者的結果相除可得到一個大致的同步延遲時間。
四、跟蹤延遲
通過擴展事件跟蹤,我們可以知道日志塊移動的每個步驟,並且可以確切地知道事務延遲來自何處。
通常,延遲來自三個部分:
- 主庫日志固化的持續時間:它等於Log_flush_start(步驟2)和Log_flush_complete(步驟3)的時間之和。
- 從庫日志固化的持續時間:它等於Log_flush_start(步驟10)和Log_flush_complete(步驟11)的時間之和。
- 網絡傳送的持續時間 :primary:hadr_log_block_send_complete-> secondary:hadr_transport_receive_log_block_message(步驟6-7)和(secondary:hadr_lsn_send_complete-> primary:hadr_receive_harden_lsn_message(步驟12-13)的時間之和
創建擴展事件:
/* Note: this trace could generate very large amount of data very quickly,
depends on the actual transaction rate. On a busy server it can grow several GB per minute,
so do not run the script too long to avoid the impact to the production server. */ CREATE EVENT SESSION [AlwaysOn_Data_Movement_Tracing] ON SERVER
ADD EVENT sqlserver.file_write_completed,
ADD EVENT sqlserver.file_write_enqueued,
ADD EVENT sqlserver.hadr_apply_log_block,
ADD EVENT sqlserver.hadr_apply_vlfheader,
ADD EVENT sqlserver.hadr_capture_compressed_log_cache,
ADD EVENT sqlserver.hadr_capture_filestream_wait,
ADD EVENT sqlserver.hadr_capture_log_block,
ADD EVENT sqlserver.hadr_capture_vlfheader,
ADD EVENT sqlserver.hadr_db_commit_mgr_harden,
ADD EVENT sqlserver.hadr_db_commit_mgr_harden_still_waiting,
ADD EVENT sqlserver.hadr_db_commit_mgr_update_harden,
ADD EVENT sqlserver.hadr_filestream_processed_block,
ADD EVENT sqlserver.hadr_log_block_compression,
ADD EVENT sqlserver.hadr_log_block_decompression,
ADD EVENT sqlserver.hadr_log_block_group_commit ,
ADD EVENT sqlserver.hadr_log_block_send_complete,
ADD EVENT sqlserver.hadr_lsn_send_complete,
ADD EVENT sqlserver.hadr_receive_harden_lsn_message,
ADD EVENT sqlserver.hadr_send_harden_lsn_message,
ADD EVENT sqlserver.hadr_transport_flow_control_action,
ADD EVENT sqlserver.hadr_transport_receive_log_block_message,
ADD EVENT sqlserver.log_block_pushed_to_logpool,
ADD EVENT sqlserver.log_flush_complete ,
ADD EVENT sqlserver.log_flush_start,
ADD EVENT sqlserver.recovery_unit_harden_log_timestamps
ADD TARGET package0.event_file(SET filename=N'c:\mslog\AlwaysOn_Data_Movement_Tracing.xel',max_file_size=(500),max_rollover_files=(4))
WITH (MAX_MEMORY=4096 KB,
EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
MAX_DISPATCH_LATENCY=30 SECONDS,
MAX_EVENT_SIZE=0 KB,
MEMORY_PARTITION_MODE=NONE,
TRACK_CAUSALITY=OFF,
STARTUP_STATE=ON
) GO
五、預警
IF EXISTS(SELECT 1 FROM sys.objects o WHERE o.[object_id]=OBJECT_ID('[dbo].[Proc_DBA_AlwaysonWarning]') AND o.[type] IN(N'P',N'PC')) DROP PROC [dbo].[Proc_DBA_AlwaysonWarning] GO -- ============================================= -- Description: alwayson預警 -- Remark : 所有參數均為產生預警的參數 -- ============================================= CREATE PROCEDURE dbo.Proc_DBA_AlwaysonWarning @syncMode BIT=NULL, --"同步模式" 是否為同步提交,是1否0,如為NULL則不處理。默認為 NULL (不處理是否異步) @syncStateIsFinished BIT=0, --"同步狀態" 是否為 "SYNCHRONIZED",是1否0,如為NULL則不處理。默認為 0 (如同步狀態為未完成則預警) @syncHealth BIT=0, --"同步健康狀態" 是否為健康, 是1否0,如為NULL則不處理。默認為 0 否 (如健康狀態為不健康則預警) @redoDelaySeconds INT=600, --"Redo延遲(秒)" > 多少則預警。默認為 600 (s) @logDelaySeconds INT=600, --"Log傳送延遲(秒)" > 多少則預警。默認為 600 (s) @redoWaitQueueKB BIGINT=10240, --"Redo等待隊列(KB)" > 多少則預警。默認為 10240 (10MB) @logWaitQueueKB BIGINT=524288 --"Log傳送等待隊列(KB)" > 多少則預警。默認為 524288 (512MB) AS BEGIN SET NOCOUNT ON; ;WITH t AS ( SELECT ar.replica_server_name AS [副本名稱] , ar.availability_mode_desc as [同步模式], DB_NAME(dbr.database_id) AS [數據庫名稱] , dbr.database_state_desc AS [數據庫狀態], dbr.synchronization_state_desc AS [同步狀態], dbr.synchronization_health_desc AS [同步健康狀態], ISNULL(CASE dbr.redo_rate WHEN 0 THEN -1 ELSE CAST(dbr.redo_queue_size AS FLOAT) / dbr.redo_rate END, -1) AS [Redo延遲(秒)] , ISNULL(CASE dbr.log_send_rate WHEN 0 THEN -1 ELSE CAST(dbr.log_send_queue_size AS FLOAT) / dbr.log_send_rate END, -1) AS [Log傳送延遲(秒)] , dbr.redo_queue_size AS [Redo等待隊列(KB)] , dbr.redo_rate AS [Redo速率(KB/S)] , dbr.log_send_queue_size AS [Log傳送等待隊列(KB)] , dbr.log_send_rate AS [Log傳送速率(KB/S)] FROM [master].sys.availability_replicas AS AR INNER JOIN [master].sys.dm_hadr_database_replica_states AS dbr ON ar.replica_id = dbr.replica_id WHERE dbr.redo_queue_size IS NOT NULL ) /* @syncMode BIT=NULL, --"同步模式" 是否為同步提交,是1否0,如為NULL則不處理。默認為 NULL (不處理是否異步) @syncStateIsFinished BIT=0, --"同步狀態" 是否為 "SYNCHRONIZED",是1否0,如為NULL則不處理。默認為 0 (如同步狀態為未完成則預警) @syncHealth BIT=0, --"同步健康狀態" 是否為健康, 是1否0,如為NULL則不處理。默認為 0 否 (如健康狀態為不健康則預警) @redoDelaySeconds INT=60, --"Redo延遲(秒)" > 多少則預警。默認為 60 (s) @logDelaySeconds INT=600, --"Log傳送延遲(秒)" > 多少則預警。默認為 600 (s) @redoWaitQueueKB BIGINT=10240, --"Redo等待隊列(KB)" > 多少則預警。默認為 10240 (10MB) @logWaitQueueKB BIGINT=524288, --"Log傳送等待隊列(KB)" > 多少則預警。默認為 524288 (512MB) */ SELECT CASE WHEN ( (@syncMode=0 AND [同步模式]!='SYNCHRONOUS_COMMIT') or ( @syncMode=1 AND [同步模式]='SYNCHRONOUS_COMMIT' ) ) OR ( (@syncStateIsFinished=0 AND [同步狀態]!='SYNCHRONIZED') or ( @syncStateIsFinished=1 AND [同步狀態]='SYNCHRONIZED' ) ) OR ( (@syncHealth=0 AND [同步健康狀態]!='HEALTHY') or ( @syncHealth=1 AND [同步健康狀態]='HEALTHY' ) ) OR ( [Redo延遲(秒)] > @redoDelaySeconds ) OR ( [Log傳送延遲(秒)] > @logDelaySeconds ) OR ( [Redo等待隊列(KB)] > @redoWaitQueueKB ) OR ( [Log傳送等待隊列(KB)] > @logWaitQueueKB ) THEN 1 ELSE 0 END AS Warning, [副本名稱], [同步模式], [數據庫名稱], [數據庫狀態], [同步狀態], [同步健康狀態], [Redo延遲(秒)], [Log傳送延遲(秒)], [Redo等待隊列(KB)], [Redo速率(KB/S)], [Log傳送等待隊列(KB)], [Log傳送速率(KB/S)] FROM t END GO EXEC sys.sp_addextendedproperty @name=N'Version', @value=N'2.0' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'PROCEDURE',@level1name=N'Proc_DBA_AlwaysonWarning'