로그와 보관 정책
기록을 어디에 쌓고 언제 지울지 정합니다.
로그 표는 다릅니다
지금까지 만든 표는 읽고 쓰는 비율이 비슷했습니다. 로그는 다릅니다.
| 보통 표 | 로그 표 | |
|---|---|---|
| 쓰기 | 가끔 | 끊임없이 |
| 읽기 | 끊임없이 | 가끔, 대개 최근 것만 |
| 고치기 | 합니다 | 하지 않습니다 |
| 지우기 | 드물게 | 주기적으로 대량 |
| 한 건의 값 | 큽니다 | 작습니다 |
그래서 5.2 의 판단이 뒤집힙니다. 인덱스를 더하면 조회가 빨라지지만, 로그는 조회보다 쓰기가 수천 배 많습니다.
-- 같은 로그 표를 둘 만들고 한쪽에만 인덱스를 겁니다. CREATE TABLE Board.T_LOG1 ( num int IDENTITY PRIMARY KEY, cont_type int NOT NULL, cont_num int NOT NULL, val int NOT NULL, reg_num int NULL, reg_addr varchar(50) NOT NULL, reg_date datetime2(0) NOT NULL ); -- T_LOG2 는 같은 모양에 인덱스 셋을 더 답니다. CREATE INDEX IX_LOG2_cont ON Board.T_LOG2 (cont_type, cont_num); CREATE INDEX IX_LOG2_reg ON Board.T_LOG2 (reg_date); CREATE INDEX IX_LOG2_user ON Board.T_LOG2 (reg_num);
| 인덱스 | 10만 행 넣기 | 한 건 조회 |
|---|---|---|
| 기본 키만 | 69~71 밀리초 | 598장 |
| 셋을 더 검 | 351~357 밀리초 | 2장 |
하루에 로그를 100만 건 쓰고 조회를 100번 한다면 인덱스를 더하는 쪽이 훨씬 손해입니다. 쓰기에서 잃는 것이 조회에서 얻는 것보다 수천 배 큽니다.
그렇다고 인덱스를 아예 두지 않으면 조회가 못 씁니다. 실제로 하는 조회를 먼저 정하고 그것 하나만 받치는 인덱스를 두십시오. "언젠가 필요할지 모른다" 로 더하지 마십시오.
무엇을 어디에 쌓습니까
로그를 업무 데이터베이스에 두지 마십시오. 이 저장소는
MAYYANET(업무)과
LOGS(기록)를 데이터베이스부터
나눠 두었습니다.
| 나누면 얻는 것 | 까닭 |
|---|---|
| 백업 계획을 달리 둡니다 | 로그는 SIMPLE 로, 업무는 FULL 로(6.2) |
| 디스크를 나눕니다 | 로그가 디스크를 채워도 업무가 멈추지 않습니다 |
| 권한을 나눕니다 | 로그만 보는 계정을 둘 수 있습니다(6.1) |
| 지울 때 영향이 없습니다 | 대량 삭제가 업무 표를 잠그지 않습니다(5.7) |
기록마다 성격이 다릅니다
| 무엇 | 보관 기간 | 지울 수 있습니까 |
|---|---|---|
| 조회수·좋아요 같은 행위 기록 | 며칠~몇 주 | 집계한 뒤 지웁니다 |
| 관리자 작업 기록 | 몇 년 | 지우면 안 됩니다 |
| 로그인·접속 기록 | 법으로 정해진 기간 | 그 전에는 못 지웁니다 |
| 오류·디버그 기록 | 며칠 | 지웁니다 |
| 개인 정보가 든 기록 | 목적을 다하면 | 지워야 합니다 |
보관 기간을 정하는 것은 기술이 아니라 정책입니다. 법으로 정해진 것이 있고, 업무 쪽이 정할 것이 있습니다. 정해지지 않은 채로 쌓기 시작하면 나중에 지울 수 없습니다 — 무엇을 지워도 되는지 아무도 모르기 때문입니다.
개인 정보는 반대로 "언제까지 지워야 하는가" 가 정해져 있습니다. 접속 아이피도 개인 정보로 봅니다. 목적을 다한 뒤에도 남겨 두면 보관하지 않아야 할 것을 보관한 것이 됩니다.
어떻게 지웁니까
오래된 것을 치우는 방법이 셋 있습니다. 지우는 값이 크게 다릅니다.
| 방식 | 지우는 법 | 값 |
|---|---|---|
| 한 표에 쌓고 지우기 | 배치 DELETE(5.7) | 비쌉니다 |
| 기간별 표로 나누기 | TRUNCATE 또는 DROP | 거의 공짜입니다 |
| 파티션 | 파티션 전환 | 거의 공짜지만 Enterprise 입니다 |
| 방법 | 걸린 시간 |
|---|---|
| DELETE | 54 밀리초 |
| TRUNCATE | 1 밀리초 |
기간별로 표를 나누면 지우는 일이 TRUNCATE
하나가 됩니다. 조건을 걸어 찾을 필요도, 배치로 나눌 필요도, 잠금을
걱정할 필요도 없습니다.
실제로 돌고 있는 방식
이 사이트의 LOGS 데이터베이스는
요일별 표 일곱 개를 돌려 씁니다.
CREATE TABLE Logging.WEEK1 ( num INT IDENTITY NOT NULL CONSTRAINT PK_WEEK1 PRIMARY KEY (num DESC), cont_type INT NOT NULL, -- 1:게시판, 2:스도쿠 … [type] INT NOT NULL -- 1:조회, 2:추천, 4:신고 … CONSTRAINT CK_WEEK1_type CHECK ([type] IN (1, 2, 4, 8, 16)), cont_num INT NOT NULL, val INT NOT NULL, reg_num INT NULL, reg_addr VARCHAR(50) NOT NULL CONSTRAINT DF_WEEK1_reg_addr DEFAULT (''), reg_date DATETIME NOT NULL CONSTRAINT DF_WEEK1_reg_date DEFAULT (GETDATE()) ); -- 인덱스는 실제로 하는 조회 하나만 받칩니다. CREATE INDEX IX_WEEK1 ON Logging.WEEK1 (cont_type, [type], cont_num, usable) INCLUDE (val);
CREATE PROCEDURE Logging.P_WEEK_SAVE @cont_type INT, @type INT, @cont_num INT, @val INT, @reg_num INT = NULL, @reg_addr VARCHAR(50) = '', @rst INT = 0 OUTPUT AS SET NOCOUNT ON; DECLARE @week INT = DATEPART(WEEKDAY, GETDATE()), -- 1~7 @sql NVARCHAR(MAX), @sqlp NVARCHAR(MAX); -- 표 이름만 이어 붙이고, 값은 매개 변수로 넘깁니다(4.9). SET @sql = N'INSERT INTO Logging.WEEK' + CONVERT(NCHAR(1), @week) + N' ( [type], cont_num, val, reg_num, reg_addr ) VALUES (@type, @cont_num, @val, @reg_num, @reg_addr); SET @rst = @@ROWCOUNT;'; SET @sqlp = N'@type INT, @cont_num INT, @val INT, @reg_num INT, @reg_addr VARCHAR(50), @rst INT OUTPUT'; EXEC sp_executesql @sql, @sqlp, @type = @type, @cont_num = @cont_num, @val = @val, @reg_num = @reg_num, @reg_addr = @reg_addr, @rst = @rst OUTPUT;
이 방식의 값은 소유권 체인이 끊긴다는 것입니다(6.1). 동적 SQL 이라 표 권한을 사용자가 직접 가지고 있어야 합니다. 로그 데이터베이스라 업무 표와 나뉘어 있어 감당할 만한 선택입니다.
어느 방식을 고릅니까
| 방식 | 어울리는 곳 | 값 |
|---|---|---|
| 한 표에 쌓고 배치 삭제 | 양이 적거나 기간이 들쭉날쭉할 때 | 지우는 값이 계속 듭니다 |
| 요일·월별 표 돌려쓰기 | 보관 기간이 고정일 때 | 표 이름을 정하는 코드가 필요합니다 |
| 기간별 표를 계속 만들기 | 오래 보관해야 할 때 | 표가 계속 늘고 조회가 번거롭습니다 |
| 파티션 | 아주 크고 Enterprise 일 때 | 에디션과 설계 비용 |
-- 매달 초에 다음 달 표를 미리 만듭니다. DECLARE @name sysname = N'ACCESS_' + FORMAT(DATEADD(month, 1, GETDATE()), 'yyyyMM'); IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE schema_id = SCHEMA_ID('Logging') AND name = @name) BEGIN DECLARE @sql nvarchar(MAX) = N'CREATE TABLE Logging.' + QUOTENAME(@name) + N' (…)'; EXEC sp_executesql @sql; END -- 보관 기간이 지난 표를 지웁니다. 이름으로 찾습니다. DECLARE @cut nvarchar(6) = FORMAT(DATEADD(month, -13, GETDATE()), 'yyyyMM'); SELECT N'DROP TABLE Logging.' + QUOTENAME(name) + N';' FROM sys.tables WHERE schema_id = SCHEMA_ID('Logging') AND name LIKE 'ACCESS_%' AND RIGHT(name, 6) < @cut;
표를 계속 만들어 가는 방식은 조회가 번거로워집니다. 넉 달치를
보려면 표 넷을 UNION ALL 해야 하고, 그 뷰를
매달 고쳐야 합니다. 보관 기간이 고정이라면 돌려쓰기가 훨씬
간단합니다.
지우기 전에 요약합니다
로그를 지우면 그 기간의 통계도 함께 사라집니다. 지우기 전에 필요한 것만 요약해 따로 남겨 두어야 합니다.
-- 원본 로그: 하루 100만 건 -- 요약: 하루 몇백 건 CREATE TABLE Statistics.DAILY_VIEW ( stat_date date NOT NULL, cont_type int NOT NULL, cont_num int NOT NULL, view_cnt int NOT NULL, CONSTRAINT PK_DAILY_VIEW PRIMARY KEY CLUSTERED (stat_date, cont_type, cont_num) ); GO -- 새벽에 어제치를 요약합니다. DECLARE @day date = CAST(DATEADD(day, -1, GETDATE()) AS date); -- 다시 돌려도 안전하도록 그날 것을 먼저 지웁니다(6.3). DELETE FROM Statistics.DAILY_VIEW WHERE stat_date = @day; INSERT INTO Statistics.DAILY_VIEW (stat_date, cont_type, cont_num, view_cnt) SELECT @day, cont_type, cont_num, COUNT(*) FROM Logging.WEEK1 WHERE reg_date >= @day AND reg_date < DATEADD(day, 1, @day) AND [type] = 1 -- 조회만 GROUP BY cont_type, cont_num;
| 지키는 것 | 까닭 |
|---|---|
| 요약을 먼저, 지우기를 나중에 | 순서가 바뀌면 그날 통계가 없습니다 |
| 다시 돌려도 같게 | 배치가 실패해 다시 도는 일이 생깁니다(6.3) |
| 어제치까지만 | 오늘 것은 아직 쌓이는 중입니다 |
| 요약이 끝난 것만 지웁니다 | 요약 실패를 눈치채지 못하면 자료를 잃습니다 |
요약이 성공했는지 확인하고 지우십시오. 요약 배치가 조용히 실패한 채로 삭제 배치만 돌면 그 기간이 통째로 사라집니다. 집계 표를 실제로 채우는 방법은 6.8 에서 한 벌로 만듭니다.
직접 해보기
하루 200만 건이 쌓이는 접속 기록 표를 설계하십시오. 보관은 6개월, 조회는 "특정 회원의 최근 접속" 하나뿐 입니다.
로그 표 하나가 3억 행까지 자라 디스크가 거의 찼습니다. 서비스를 멈추지 않고 정리하는 순서를 적어 보세요. 지금은 한 표에 계속 쌓는 구조입니다.
- 로그 표는 다릅니다. 쓰기가 압도적으로 많고 조회는 가끔이라 5.2 의 판단이 뒤집힙니다.
- 인덱스 셋을 더하면 10만 행 넣기가 70밀리초에서 354밀리초가 되고, 조회는 598장에서 2장이 됩니다. 비율이 답을 정합니다.
- 실제로 하는 조회를 먼저 정하고 그것 하나만 받치는 인덱스를 두십시오. "언젠가 필요할지 모른다" 로 더하지 마십시오.
- 로그를 업무 데이터베이스에 두지 마십시오. 나누면 백업·디스크·권한·삭제가 서로 영향을 주지 않습니다.
- 보관 기간은 기술이 아니라 정책입니다. 정하지 않고 쌓기 시작하면 나중에 무엇을 지워도 되는지 아무도 모릅니다.
- 개인 정보는 지워야 하는 기한이 있습니다. 접속 아이피도 개인 정보입니다.
- 지우는 방법에 따라 값이 크게 다릅니다 — 10만 행에서
DELETE54밀리초,TRUNCATE1밀리초입니다. - 기간별로 표를 나누면 지우는 일이
TRUNCATE나DROP하나가 됩니다. 이 사이트는 요일별 표 일곱 개를 돌려 씁니다. - 돌려쓰기는 보관 기간이 고정일 때 가장 간단합니다. 오래 보관해야 하면 기간별 표를 만들어 가되 조회가 번거로워지는 값을 함께 따지십시오.
- 지우기 전에 요약하십시오. 순서가 바뀌면 그 기간의 통계가 사라집니다. 요약이 성공했는지 확인한 뒤에 지웁니다.