본문 바로가기

SQL Server/Service Broker

[Service Broker] 성능 최적화 - 150 Trick

이전 글의 소스를 유심히 살펴보셨다면 아래 테이블에서 [_150TrickEnableFlag] 컬럼이 어떤 역할을 하는지 알고 계실 것 같습니다.

1
2
3
4
5
6
-- Dialog Pool 설정 테이블 생성
CREATE TABLE dbo.DialogPoolConfig (
    _150TrickEnableFlag bit NOT NULL,
    DialogCount int NOT NULL
);
GO
cs

[P_StartUp_CreateDialogs]의 내용은 아래와 같았죠.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
CREATE PROCEDURE dbo.P_StartUp_CreateDialogs
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
 
DECLARE @i int = 1
    , @intDialogCount int = 0
    , @bit150TrickEnableFlag bit
    , @uidDialogHandle uniqueidentifier;
 
SELECT @intDialogCount = DialogCount * (CASE _150TrickEnableFlag WHEN THEN ELSE 150 END)
    , @bit150TrickEnableFlag = _150TrickEnableFlag
FROM dbo.DialogPoolConfig;
 
TRUNCATE TABLE dbo.DialogPool;
 
WHILE @i <= @intDialogCount
BEGIN
    BEGIN DIALOG CONVERSATION @uidDialogHandle
    FROM SERVICE InitDBService
    TO SERVICE N'TargetDBService'
    ON CONTRACT LoggingContract
    WITH ENCRYPTION = OFF;
 
    IF @bit150TrickEnableFlag = OR (@bit150TrickEnableFlag = AND (@i % 150= 0)
     INSERT dbo.DialogPool (DialogHandle)
     VALUES (@uidDialogHandle);
 
    SET @i += 1;
END
GO
cs

[_150TrickEnableFlag] 컬럼의 값이 "1"이면 [DialogCount] 값의 150배에 해당하는 Dialog Handle을 생성합니다.

그리고, 25행 ~ 27행과 같이 150번째의 핸들만 DialogPool 테이블에 INSERT 합니다.

25
26
27
IF @bit150TrickEnableFlag = OR (@bit150TrickEnableFlag = AND (@i % 150= 0)
    INSERT dbo.DialogPool (DialogHandle)
    VALUES (@uidDialogHandle);
cs

즉, 150 Trick이란..

(필요한 Dialog Handle 수) x (150) 개의 Dialog Handle을 생성하고, 매 150번째의 핸들만 대화에 사용하는 것


그런데.. 이렇게 하면 뭐가 좋아진다는 걸까요???

이야기의 출발은 이렇습니다.

예전에 어떤 개발팀이 SSB로 대량 메시지를 전송하는 테스트를 했습니다.
(직전의 포스트에서 다룬 성능 최적화를 위한 기본 요건을 갖춘 상태였습니다.)

그런데, INITIATOR 서버의 wait stat을 살펴보니 PAGELATCH_EX/SH가 꼭대기에 있는 겁니다.
어떤 테이블에선가 page에 latch가 계속 걸려.. 이로 인한 wait가 병목이 되었던거죠.

이전의 포스트에서 sys.sysdesend 테이블을 잠시 언급한 적이 있습니다.

이 테이블은 INITIATOR 서버에서 전송하는 메시지를 위한 대화 핸들 값을 저장하고, 상태를 UPDATE하는데 사용되는데, 위에 언급한 page latch는 이 테이블에서 발생하는 문제입니다.

메시지가 전달될 때마다 sysdesend 테이블이 update되어야하는데, Thread마다 핸들을 분리한다해도.. 핸들이 한 페이지에 몰려 있다면.. page에 latch가 걸려 성능 문제를 야기하게 됩니다.


이 문제를 해결하려면 Thread에 할당된 Dialog Handle이 모두 자기만의 페이지를 갖고 있으면 되겠죠.

방법은 이미 맨 위에 설명되어 있으니, 우리는 150이라는 숫자에 초점을 맞춰 보겠습니다.

먼저 sysdesend 테이블의 레이아웃은 아래와 같습니다.

column name

data type

null option

size

handle

uniqueidentifier

not null

16

diagid

uniqueidentifier

not null

16

initiator

tinyint

not null

1

sendseq

bigint

not null

8

sendxact

binary

not null

6


- handle 컬럼에 unique clustered 인덱스가 있습니다.
- 생성되는 handle 값은 순차적입니다. (즉, page split이 발생하지 않습니다.)

모든 컬럼이 고정 길이형 데이터 타입을 사용하고 NULL을 허용하지 않습니다.

레코드가 차지하는 공간은 데이터가 47byte (16 + 16 + 1 + 8 + 6)이고 row overhead는 7byte, row offset은 2byte죠.

96 byte의 헤더를 고려하면, 결국 한 page에 들어갈 수 있는 레코드는 최대 144개입니다. (page당 32 byte의 공간이 남습니다.)

매 144번째 생성되는 Dialog Handle만 Pool에 넣어 한개의 Thread에 할당해 준다면, Pool에 있는 모든 핸들은 sysdesend 테이블에서 모두 다 서로 다른 page에 있게 됩니다.

그 결과로서 page latch문제를 해결할 있습니다.

이렇게보면 144 Trick이 맞는데.. 굳이 150 Trick으로 이름짓고 150번째 Dialog Handle을 사용하는 이유는 잘 모르습니다.

144대신 150을 사용하면 page가 몇 개 낭비되지만.. 무시할 만한 수준이고, 144보다는 150이라는 숫자가 보다 친숙해질 수 있기 때문이 아닐까 짐작해 봅니다.

그래서 저도 144 Trick이 아닌, 원래의 150 Trick을 그대로 소개드립니다.

(이유를 발견하셨다면 댓글 부탁드립니다~)



150 Trick이 만능은 아니다?!

애초에 150 Trick이 필요한 이유는 PAGELATCH_EX/SH로 인한 Hot-Spot 이슈 때문입니다.

조건에 따라 다르지만 150 Trick을 적용하면 초당 전송 메세지의 수가 최대 3배 가량 개선됩니다.

하지만, 동시에 사용하는 핸들 수가 적거나 전송하는 메시지가 충분히 대량이 아니라서.. Hot-Spot 이슈가 일어나지 않는다면 오히려 150 Trick을 적용하는 것이 느리겠죠.

불필요하게 많은 페이지를 사용하므로 I/O가 증가하기 때문입니다.

통상 15개 미만의 Thread에서 초당 전송할 수 있는 메시지의 수는 150 Trick이 더 적다고 합니다.