테스트 목적
Before와 같은 제약조건의 테이블을 pt-osc로 After와 같이 변경할 때 이슈없는지 확인
(pt-osc 작업 수행 중에 유입되는 DML로 인해 데이터의 일관성이 깨지는지 확인)
테스트 이유
(1) PK 제약조건과 Unique 제약 조건이 동시에 존재하여 ON DUPLICATE문을 사용할 때 데드락 발생
(2) 데드락이 발생한 문제로 절차적 방식으로 코드 롤백
(절차적 방식: 중복되는 값이 존재하는지 DB에서 확인 후, 있으면 UPDATE, 없으면 INSERT)
(3) 제약조건이 하나만 존재하면 (2)번과 같은 방식을 사용할 필요 없이 ON DUPLICATE문을 사용하면 됨 --> 데드락 해소
(4) 다만 온라인으로 PK를 변경해야 하기 때문에 절차적 방식에도 적용될 수 있는 시나리오인지 확인 필요
(5) 4번 과정에서 문제가 없었다면 PK 변경 후 응용 프로그램 코드를 ON DUPLICATE로 배포
##############
## Before ##
##############
CREATE TABLE `test0` (
`id` bigint NOT NULL,
`created_at` timestamp NOT NULL,
`updated_at` timestamp NOT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
`user_id` bigint NOT NULL,
`device_id` varchar(128) NOT NULL,
`revoke_time` timestamp NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_userid_deviceid` (`user_id`,`device_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
#############
## After ##
#############
CREATE TABLE `test0` (
`id` bigint NOT NULL,
`created_at` timestamp NOT NULL,
`updated_at` timestamp NOT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
`user_id` bigint NOT NULL,
`device_id` varchar(128) NOT NULL,
`revoke_time` timestamp NOT NULL,
PRIMARY KEY (`user_id`, `device_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ i
테스트
현재 응용 프로그램 코드에서 1개 트랜잭션 내에서 발생할 수 있는 DML문은 INSERT 또는 UPDATE이기 때문에 각각 20개 정도의 동시 스레드로 나누어 한쪽은 INSERT, 한쪽은 UPDATE 수행 (Total: 40 Threads)
데이터가 없는 경우와 중복되는 경우를 5:5 비율로 나눈 것임
*데이터가 없는 경우: 데이터가 없으면 INSERT
*데이터가 있는 경우: 데이터가 있으면 UPDATE
테스트 테이블은 실테이블의 스키마와 동일하게 구성
CREATE TABLE `test0` (
`id` bigint NOT NULL,
`created_at` timestamp NOT NULL,
`updated_at` timestamp NOT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
`user_id` bigint NOT NULL,
`device_id` varchar(128) NOT NULL,
`revoke_time` timestamp NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_userid_deviceid` (`user_id`,`device_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
INSERT Thread ▼
INSERT 데이터 예시는 아래와 같으며, 스노우 플레이크로 생성되는 id는 순차적으로 증가하기 때문에 테스트 id 값은 단순하게 n+1로 삽입
20,000개 레코드는 미리 INSERT 해놓은 상태에서 id=20001 ~ 40000까지 INSERT
INSERT INTO test0 (id, created_at, updated_at, deleted_at, user_id, device_id, revoke_time)
VALUES (0+1, now(), now(), null, floor(rand()*1000000), uuid(), now())
...
...
INSERT INTO test0 (id, created_at, updated_at, deleted_at, user_id, device_id, revoke_time)
VALUES (0+20001, now(), now(), null, floor(rand()*1000000), uuid(), now())
UPDATE Thread ▼
UPDATE 예시는 아래와 같으며, 테이블에 존재하는 id 값을 5001 ~ 15000까지 순차적으로 업데이트
UPDATE test0
SET updated_at='1990-04-25',
deleted_at='1990-04-25',
user_id=floor(rand()*1000000),
device_id=uuid(),
revoke_time='1990-04-25'
WHERE id = 0+5001
pt-online-schema-change command ▼
DROP PRIMARY KEY 구문이 포함된 경우 작업이 중단되기 때문에 --no-check-alter 옵션 사용
pt-online-schema-change
--no-check-alter
--alter "DROP PRIMARY KEY,
ADD PRIMARY KEY(user_id, device_id),
DROP INDEX ux_userid_deviceid,
ADD INDEX ix_id(id)"
D=test,t=test0
--no-drop-old-table
--no-drop-new-table
--chunk-size=500
--chunk-size-limit=2
--host=test-dba-main-silver-cluster.cusieftkzlyg.ap-northeast-2.rds.amazonaws.com
--user=admin
--progress=time,30
--max-load="Threads_running=20"
--critical-load="Threads_running=100"
--chunk-index=PRIMARY
--charset=UTF8MB4
--password='{패스워드}'
--sleep=0.05
--execute
ps-osc 트리거 ▼
## INSERT 트리거
CREATE DEFINER=`admin`@`%` TRIGGER `pt_osc_test_test0_ins` AFTER INSERT ON `test0` FOR EACH ROW BEGIN DECLARE CONTINUE HANDLER FOR 1146 begin end; REPLACE INTO `test`.`_test0_new` (`id`, `created_at`, `updated_at`, `deleted_at`, `user_id`, `device_id`, `revoke_time`) VALUES (NEW.`id`, NEW.`created_at`, NEW.`updated_at`, NEW.`deleted_at`, NEW.`user_id`, NEW.`device_id`, NEW.`revoke_time`);END
## UPDATE 트리거
CREATE DEFINER=`admin`@`%` TRIGGER `pt_osc_test_test0_upd` AFTER UPDATE ON `test0` FOR EACH ROW BEGIN DECLARE CONTINUE HANDLER FOR 1146 begin end; DELETE IGNORE FROM `test`.`_test0_new` WHERE !(OLD.`id` <=> NEW.`id`) AND `test`.`_test0_new`.`id` <=> OLD.`id`; REPLACE INTO `test`.`_test0_new` (`id`, `created_at`, `updated_at`, `deleted_at`, `user_id`, `device_id`, `revoke_time`) VALUES (NEW.`id`, NEW.`created_at`, NEW.`updated_at`, NEW.`deleted_at`, NEW.`user_id`, NEW.`device_id`, NEW.`revoke_time`); END
## DELETE 트리거
CREATE DEFINER=`admin`@`%` TRIGGER `pt_osc_test_test0_del` AFTER DELETE ON `test0` FOR EACH ROW BEGIN DECLARE CONTINUE HANDLER FOR 1146 begin end; DELETE IGNORE FROM `test`.`_test0_new` WHERE `test`.`_test0_new`.`id` <=> OLD.`id`; END
Load Generator ▼
기존에 사용하던 부하 스크립트에서 INSERT와 UPDATE 부분만 수정하여 사용
수행 순서 ▼
1. INSERT & UPDATE 동시 스레드 40개 수행
2. pt-osc로 DROP PK, ADD PK
테스트 결과
조건-1
- INSERT 20 Thread
- UPDATE 20 Thread
(PK 변경 전)
mysql> SELECT COUNT(*) FROM test0;
+----------+
| count(*) |
+----------+
| 20000 |
+----------+
..
..
..(Inserting & Updating)
..
..(pt-osc for PK)
..
..
(PK 변경 후)
## 20,000건 INSERT 확인
mysql> SELECT COUNT(*) FROM test0;
+----------+
| count(*) |
+----------+
| 40000 |
+----------+
## 10,000건 UPDATE 확인
mysql> SELECT COUNT(*) FROM test0 WHERE id > 5000 AND revoke_time = '1990-04-25 00:00:00';
+----------+
| COUNT(*) |
+----------+
| 10000 |
+----------+
--> 데이터 건 수가 적어서 오류 없이 진행
조건-2
- INSERT 40 Thread
- UPDATE 40 Thread
(PK 변경 전)
mysql> SELECT COUNT(*) FROM test0;
+----------+
| count(*) |
+----------+
| 100000 |
+----------+
..
..
..(Inserting & Updating)
..
..(pt-osc for PK)
..
..
(PK 변경 후)
## 50,000건 INSERT 확인
mysql> SELECT COUNT(*) FROM test0;
+----------+
| count(*) |
+----------+
| 150000 |
+----------+
## 20,000건 UPDATE 확인
mysql> SELECT COUNT(*) FROM test0 WHERE id > 5000 AND revoke_time = '1990-04-25 00:00:00';
+----------+
| count(*) |
+----------+
| 19979 |
+----------+
--> UPDATE 스레드에서 Deadlock 21건 발생
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
ERROR: (1213, 'Deadlock found when trying to get lock; try restarting transaction')
--> PK로 구성된 user_id, device_id를 업데이트 하면서 데드락이 발생하는 것으로 보임
조건-3
- INSERT 40 Thread
- UPDATE 40 Thread
- UPDATE문에서 user_id, device_id 업데이트를 제외(중복되면 제약조건을 다시 업데이트할 필요 없음)
(PK 변경 전)
mysql> SELECT COUNT(*) FROM test0;
+----------+
| count(*) |
+----------+
| 100000 |
+----------+
..
..
..(Inserting & Updating)
..
..(pt-osc for PK)
..
..
(PK 변경 후)
## 50,000건 INSERT 확인
mysql> SELECT COUNT(*) FROM test0;
+----------+
| count(*) |
+----------+
| 150000 |
+----------+
## 20,000건 UPDATE 확인
mysql> SELECT COUNT(*) FROM test0 WHERE id > 5000 AND revoke_time = '1990-04-25 00:00:00';
+----------+
| count(*) |
+----------+
| 20000 |
+----------+
--> PK 업데이트 부분 제거 후 Deadlock 없이 PK 삭제 및 생성 완료
테스트 후 라이브 적용
운영 환경은 조건-3으로 작업 진행하였으며, PK DROP --> PK ADD --> 코드 배포 --> Old Primary Key(id) 제거까지 온라인으로 이상없이 작업 완료
'MySQL' 카테고리의 다른 글
MySQL 버그 리포트 찾는 방법(MySQL 8.0 Default Collation) (2) | 2023.10.28 |
---|---|
MySQL RAND 함수의 난수 생성 원리 (0) | 2023.08.20 |
Aurora MySQL Fragmentation Test & Online DDL (0) | 2023.08.19 |
MySQL 인덱스 스킵 스캔(Index Skip Scan)의 원리 (0) | 2023.08.13 |
Aurora Version & Status 터미널 대시보드 (0) | 2023.08.11 |