이전까지는SELECT
쿼리에 관련된 내용을 중점적으로 다루었다면, 이번 포스팅부터 DML(UPDATE
, DELETE
, INSERT
)튜닝에 관련된 내용을 다루고자 한다. DML 튜닝을 하기에 앞서, 이번 포스팅에서는 DML의 성능에 영향을 미치는 요소가 무엇인지, 어떻게 성능에 영향을 미치는지 알아보려고 한다.
DML의 성능에 영향을 주는 요소중, 이전 포스팅에서 다루지 않은 내용은 다음과 같다.
본 글은 MySQL + InnoDB 기준으로 작성되었습니다. DBMS마다 구현이 다를 수 있습니다.
무결성 : 데이터 무결성(영어: data integrity)은 컴퓨팅 분야에서 완전한 수명 주기를 거치며 데이터의 정확성과 일관성을 유지하고 보증하는 것을 가리키며 데이터베이스나 RDBMS 시스템의 중요한 기능이다.
무결정 제약 조건(Integrity Constraints)은 데이터베이스 시스템에서 데이터의 정확성, 일관성, 유효성을 유지하기 위해 설정하는 규칙이나 제약사항을 뜻한다. 이러한 제약 조건들이 데이터의 쓰기 과정에서 적용되며, 이는 데이터의 무결성을 보장하는 데 필수적인 것들이다. 어찌보면 꽤나 포스팅을 지속한 입장에서 매우 기초적인 내용을 다루게 되는 셈인데, 그럼에도 중요한 내용이니 각 제약 조건별로 개념을 알아보겠다.
도메인 제약 조건 (Domain Constraints)
이 제약 조건은 특정 컬럼에 입력될 수 있는 데이터의 타입, 범위, 형식을 정의한다. 예를 들어, 나이 열에 음수가 입력되지 않도록 하거나, 성별에 "남", "여" 이외의 값이 입력되지 않도록 하는 것을 말한다.
기본 키 제약 조건 (Primary Key Constraints)
각 row(레코드)의 고유성을 보장하기 위해 사용되며, 테이블 내에서 모든 row가 고유한 값을 가져야 하는 컬럼(또는 컬럼의 조합)에 설정된다. PK로 지정된 컬럼은 NULL 값을 가질 수 없으며, 중복된 값 또한 가질 수 없다.
외래 키 제약 조건 (Foreign Key Constraints)
두 테이블 간의 관계를 정의하고, 참조 무결성을 유지하는 데 사용된다. 외래 키는 한 테이블의 컬럼이며, 다른 테이블의 기본 키나 고유 키와 연결된다. 이를 통해 관련된 데이터 간의 일관성을 보장한다.
고유 제약 조건 (Unique Constraints)
테이블의 특정 컬럼에 중복된 값이 입력되지 않도록 하는 제약 조건이다. 기본 키 제약 조건과 유사하지만, NULL 값의 중복을 허용할 수 있다는 점에서 차이가 있다. MySQL에서는 유니크 인덱스로 관리를 하게 되는데, 인덱스보다는 사실상 제약 조건이라고 보는 것이 맞다.
체크 제약 조건 (Check Constraints)
테이블의 컬럼 값이 특정 조건을 만족해야 함을 명시하는 제약 조건이다. 예를 들어, 어떤 숫자열이 특정 범위 내의 값만을 가져야 하거나, 문자열이 특정 패턴을 따라야 하는 경우에 사용된다.
그렇다면, 해당 제약 조건들이 영향을 미치는 이유는 무엇일까?
개인적으로 DML의 성능에 영향을 미치는 요소중, 가장 먼저 떠오르는 것은 인덱스인데, 이는 레코드의 삽입, 수정, 삭제가 이루어질 때 인덱스도 함께 삽입, 수정, 삭제가 이루어져야하기 때문이다. 무결성 제약 조건도 인덱스와 비슷한 맥락에서 DML의 성능에 영향을 미친다.
다음과 같은 테이블이 있다고 가정해보겠다.
CREATE TABLE Products (
ProductID INT AUTO_INCREMENT PRIMARY KEY,
ProductName VARCHAR(100),
Price DECIMAL(10,2)
);
이 때 Products
테이블에 INSERT
를 할 경우, ProductID
는 자동 증가하는 PK이므로, 새로운 ProductID
가 기존에 존재하는 ProductID와 중복되지 않도록 관리되어야 한다. 대량의 데이터 삽입이 발생하는 배치같은 경우, 기본 키에 대한 고유성 검사의 수행이 성능에 악영향을 미치게 된다. 또한, MySQL의 경우 PK를 클러스터링 인덱스로도 관리하는데, 이 경우 프라이머리 키의 변경(예를 들면, 이메일을 PK로 했을때 이메일을 변경하는 경우)하거나, 레코드의 저장 속도가 느려질 수 있다.
DB서버에서 트랜잭션을 처리중일 때, 갑자기 서버 컴퓨터를 관리하는 건물에서 정전이 발생했을 경우 데이터베이스는 이를 어떤 방식으로 복구할 수 있을까?
리두 로그는, DB 서버가 비정상적으로 종료됐을 떄 데이터 파일에 기록되지 못한 데이터를 잃지 않게 해주는 안전장치이다.
리두 로그는 ACID중 영속성(Durability)을 보장하기 위한 수단이다. 위와 같은 상황에서 유실된 트랜잭션을 복구하거나, 마지막으로 커밋 됐지만, 데이터 파일에 기록되지 않은 내용을 리두 로그를 이용하여 마지막 커밋된 내용으로 복원할 수 있다. 또한 롤백됐지만 데이터 파일에 기록된 데이터의 경우에도, 해당 트랜잭션의 상태를 알기 위해 활용될 수 있다.
MySQL의 경우, 리두 로그의 활성화와 비활성화 기능을 MySQL 서버 변수설정을 통해 제공한다. 데이터의 영속성을 보장하는 것은 RDBMS의 기능 중에서도 가장 중요한 부분중에 하나일텐데, 왜 이런 옵션을 제공하는지 생각해보면 이는 리두 로깅 자체의 오버헤드로 인한 가능성이 크다.
대규모의 테이블에 여러 개의 인덱스가 생성되어있는 테이블의 경우, 트랜잭션이 커밋될 때마다 디스크에 리두 로깅의 내용을 동기화하는 것은 많은 부하가 따를 것이다. MySQL의 경우에는 innodb_flush_log_at_trx_commit
이라는 변수로 어느 시점에 리두 로그를 디스크에 기록할지 제어할 수 있으며, 서비스의 요구사항에 따라서 이를 적절하게 설정할 수 있어야 한다.
즉, 리두 로그는 활성화 되어있을 경우 모든 비정상적인 종료, 백업에 대비해서 모든 트랜잭션의 커밋 내용을 기록하게 되어있다. 대규모의 데이터 적재 작업같은 경우, INSERT
가 실행될때마다 리두 로그에 이를 기록하고 디스크에 다시 적재하는 과정을 거치는데, 이는 분명히 리두 로그 자체가 성능에 영향을 미치는 요소가 된다. MySQL은 innodb_redo_log_enabled
라는 상태변수를 통해서 리두 로그 활성 여부를 확인할 수 있으며, ALTER INSTANCE DISABLE/ENABLE INNODB REDO_LOG
명령어를 통해 리두 로그를 활성/비활성화 시킬 수 있다.
트랜잭션 격리 수준을 다뤘던 포스팅에서 언두 로그에 대해 다룬 적이 있다. 언두 로그는 격리 수준 구현을 위해서 로깅하는 공간이며, 서로 다른 트랜잭션의 데이터 일관성과 동시성을 위해서 이용되는 로그이다. 또한 때에 따라서는 롤백에 이용되기도 한다.
언두 로그 또한 리두 로그와 마찬가지로, 트랜잭션에서 변경, 삽입, 삭제가 발생할 때마다 추가 로깅이 발생하기 때문에 DML의 성능에 영향을 미칠 수 밖에 없다.
DML이라는 키워드 자체가 워낙 범위가 넓기 때문에, 이번에 다룬 내용 외에도 인덱스나 Lock 등 고려해야될 요소가 많고, 이들을 온전히 이해하고 있어야 DML의 어느 지점에서 병목이 발생했는지 파악하며 적절하게 튜닝할 수 있을 것이다. 다음 포스팅에서는 이번 포스팅에서 다루지 못한 영향을 미칠만한 요소들과, 특정 맥락에서 발생한 DML 튜닝을 해결하는 내용의 포스팅을 작성해보도록 하겠다.