데이터가 쌓이고, 처리량이 많아져야 어디가 느려지는지 분별이 가능하다. 그 때 쿼리의 성능을 평가하고 수정하는게 효과적이다.
2. find slow query
느려지는 쿼리를 찾는다. DB 제품마다 슬로우쿼리를 찾는 방법이 다르다.
3. index
우선 고려해볼만한 방법은 index. 인덱스는 읽기 속도를 비약적으로 높여주지만, 쓰기 성능을 하락시킨다.
그럼에도 읽기 성능을 높이는 것은 성능 향상 효과가 크기 때문에 활용하게 된다.
4. application
다음으로 활용할만한 것은 DB를 사용하는 Application의 캐시를 활용해보는 것이 방법. 자주 호출되는 데이터를 캐시에 저장함으로써 DB 부하를 줄일 수 있다.
5. denormalization
최후의 보루는 이상적으로 정규화된 표에 손을 대는 것. 이를 denormalization(역정규화)라고 한다.
역정규화는 큰 대가가 필요한 작업으로 역정규화를 시도하기 이전에 다른 방법을 먼저 찾아보는 것이 바람직하다.
온갖 시도를 해봤음에도 성능 향상이 필요하다면, 외과적 시술로서 역정규화를 시도해보아야 한다.
역정규화(Denormalization)
역정규화는 성능이나 개발의 편의성을 위해서 기 만들어진 구조를 변경하는 것.
정규화는 대체로 쓰기의 편리함을 위해서 읽기의 성능을 희생하는 것이라고 말할 수 있다.
정규화를 하게되면 표를 여러개로 쪼개게 되고 표들을 다시 사용할 때는 join을 사용하는데, join은 "비싼"작업 이다.
이때문에 읽기 성능이 떨어지게 된다.
하지만 운영이 있어서 일반적으로 쓰기보다 읽기가 굉장히 자주 일어나게 되고 서비스 성능을 위해서는 읽기 성능이 중요하기 때문에 우리는 읽기 성능을 높이기 위한 위의 작업들을 시도해보게 된다.
그 마지막 시도로 해볼 수 있는 것이 표를 다시 뜯어고치는 역정규화 라는 작업이다.
정규화와 비교해서 보다 다양한 방법이 있을 수 있다. 이 강의에서는 이 몇가지 방법을 살펴본다.
1. JOIN을 줄이기(컬럼중복)
목표 : topic_tag_relation.topic_title 의 값이 MySQL 인 태그의 이름을 알고 싶다.
아래와 같이 쿼리를 작성한다.
SELECT
tag.name
FROM topic_tag_relation AS TTR
LEFT JOIN tag
ON TTR.tag_id = tag.id
WHERE topic_title = 'MySQL';
이와 같이 JOIN을 사용해서 데이터를 가져올 상황이 굉장히 많이 일어나는데, JOIN은 "비싼"작업으로 JOIN을 줄이는 방향으로 표를 합치기로 한다.
ALTER TABLE `topic_tag_relation` ADD COLUMN `tag_name` VARCHAR(45) NULL AFTER `tag_id`;
UPDATE `topic_tag_relation` SET `tag_name` = 'rdb' WHERE (`topic_title` = 'MySQL') and (`tag_id` = '1');
UPDATE `topic_tag_relation` SET `tag_name` = 'free' WHERE (`topic_title` = 'MySQL') and (`tag_id` = '2');
UPDATE `topic_tag_relation` SET `tag_name` = 'rdb' WHERE (`topic_title` = 'ORACLE') and (`tag_id` = '1');
UPDATE `topic_tag_relation` SET `tag_name` = 'commercial' WHERE (`topic_title` = 'ORACLE') and (`tag_id` = '3');
이렇게 하면 정규화와 반대로 중복을 허용하는 형태가 되고, 시스템의 복잡도가 높아지지만,
성능 향상을 위해서 이 형태를 취한다.
역정규화 이후의 쿼리는 아래와 같이 단순해진다.
SELECT tag_name FROM topic_tag_relation WHERE topic_title = 'MySQL';
2. 계산작업을 줄이기
목표: 각각의 저자가 몇개의 글을 작성했는지를 목록으로 표현한다.
topic에서 각 저자 아이디가 몇개씩 확인되는지를 찾는다.
SELECT
author_id, COUNT(author_id)
FROM
topic
GROUP BY author_id;
그런데 이 작업이 자주 사용되는 작업이라면, GROUP BY는 상당히 "비싼" 작업이므로, 역정규화를 고려해볼 수 있다.
author 테이블에 컬럼을 하나 추가해서 topic 에 글이 해당 저자 아이디로 글이 작성되면 1씩 추가한 내용을 넣어준다.
ALTER TABLE `author` ADD COLUMN `topic_count` INT NULL AFTER `profile`;
UPDATE `author` SET `topic_count` = '2' WHERE (`id` = '1');
UPDATE `author` SET `topic_count` = '1' WHERE (`id` = '2');
코드를 보니 자동으로 카운트 되는 게 아니라 그냥 count 수치를 계산해서 넣어주셨다.
정적인 테이블로 상정한 모양.
어쨌든 이와 같은 역정규화 시 조회 쿼리는 아주 가벼워진다.
SELECT
id, topic_count
FROM
author;
3. 표를 쪼개기 : 컬럼을 기준으로 테이블을 분리
위와 같이 topic 테이블의 description이 용량이 굉장히 큰 경우, 다른 컬럼만 조회하는 경우에도 작업이 무거워 질 수 있다.
이 경우 description만 제외한 테이블을 분리해볼 수 있다.
이렇게 description 을 분리해두면, 조회가 빨라지고 여러 머신에 분산처리가 가능해진다. 이를 샤딩(Sharding)이라고 한다.
4. 표를 쪼개기 : 행을 기준으로 테이블 분리
행이 너무너무 많아서 조회가 느려진다면, 행을 기준으로 분리하는 방법도 있다.
author_id 1000번까지는 topic_1000 테이블에, author_id 1500 까지는 topic_1500에 저장하는 방식이다.
하지만 이와 같은 방식은 사고 위험이 굉장히 높아지기 때문에, 정말 방법이 없을 경우에 실행하며, 정교한 방법론에 의거해서 실행하여야 한다.
보통은 다른 솔루션을 먼저 처리하는 것이 바람직하다.
5. 관계의 역정규화 - 지름길을 만든다
목표 : autnor_id 가 1 인 저자의 태그 아이디와 태그명을 조회한다.
SELECT
tag.id, tag.name
FROM
topic_tag_relation AS TTR
LEFT JOIN tag ON TTR.tag_id = tag.id
LEFT JOIN topic ON TTR.topic_title = topic.title
WHERE author_id = 1;
topic_tag_relation 에서 tag_id 를 찾고, tag.name 값을 잡기 위해 tag를 JOIN 한다.
WHERE 문을 쓰기위해서 author_id 값도 필요한데,
author_id 값을 잡기 위해 topic 테이블도 JOIN 한다.
그런데 이런 작업이 자주 일어난다면, JOIN이 많아서 성능이 저하된다.
이 때, topic_tag_relation 에 author_id 를 추가해줌으로써 JOIN을 줄일 수 있다.
ALTER TABLE `topic_tag_relation` ADD COLUMN `author_id` INT NULL AFTER `tag_name`;
UPDATE `topic_tag_relation` SET `author_id` = '1' WHERE (`topic_title` = 'MySQL') and (`tag_id` = '1');
UPDATE `topic_tag_relation` SET `author_id` = '1' WHERE (`topic_title` = 'MySQL') and (`tag_id` = '2');
UPDATE `topic_tag_relation` SET `author_id` = '1' WHERE (`topic_title` = 'ORACLE') and (`tag_id` = '1');
UPDATE `topic_tag_relation` SET `author_id` = '1' WHERE (`topic_title` = 'ORACLE') and (`tag_id` = '3');
이렇게 하면 JOIN을 한번만 해서 조회가 가능해진다.
SELECT
tag.id, tag.name
FROM
topic_tag_relation AS TTR
LEFT JOIN tag ON TTR.tag_id = tag.id
WHERE TTR.author_id = 1;