MySQL 옵티마이저는 ORDER BY 또는 GROUP BY를 인덱스를 사용해 처리 가능한 경우, 쿼리의 실행 계획에서 이 인덱스의 가중치를 높이 설정해서 실행된다.
mysql> EXPLAIN
SELECT *
FROM employees
WHERE hire_date BETWEEN '1985-01-01' AND '1985-02-01'
ORDER BY emp_no;
+----+-----------+-------+---------+--------+-------------+
| id | table | type | key | rows | Extra |
+----+-----------+-------+---------+--------+-------------+
| 1 | employees | index | PRIMARY | 300252 | Using where |
+----+-----------+-------+---------+--------+-------------+
이 쿼리는 대표적으로 다음 2가지 실행 계획을 선택할 수 있음.
ix_hiredate 인덱스를 이용해 “hire_date BETWEEN ‘1985-01-01’ AND ‘1985-02-01’” 조건에 일치하는 레코드를 찾은 다음, emp_no로 정렬해서 결과를 반환
employees 테이블의 프라이머리 키가 emp_no 이므로 프라이머리 키를 정순으로 읽으면서 hire_date 칼럼의 조건에 일치하는지 비교 후 결과를 반환.
상황에 따라 1번이 효율적일 수도 있고, 2번이 효율적일 수도 있음.
일반적으로, hire_date 칼럼의 조건에 부합되는 레코드 건수가 많지 않다면 1번이 효율적임.
그런데, 가끔 MySQL 옵티마이저가 예제 쿼리의 실행 계획과 같이 2번 실행 계획을 선택하는 경우가 있을 수 있음.
실행 계획에서는 PRIMARY 키를 풀 스캔하면서 hire_date 칼럼의 값이 1985년 1월인 건만 필터링하도록 쿼리를 처리하고 있음.
이렇게 체크해야 하는 레코드 건수가 상당히 많음에도, 정렬된 인덱스 활용으로 실행 계획이 수립되는 것은 옵티마이저가 실수로 잘못된 실행 계획을 선택한 것일 가능성이 높음
이런 경우가 빈번하진 않지만 가끔 옵티마이저가 이런 실수를 함.
MySQL 8.0.20 버전까지는 이 같은 옵티마이저 실수가 자주 발생하면, 다른 실행 계획을 사용하게 하기 위해 특정 인덱스(ORDER BY를 위한 인덱스)를 사용하지 못하도록 “IGNORE INDEX” 힌트를 사용했음.
MySQL 8.0.21 버전부터 MySQL 서버 옵티마이저가 ORDER BY를 위한 인덱스에 너무 가중치를 부여하지 않도록 prefer_ordering_index 옵티마이저 옵션이 추가됨.
조인 최적화 알고리즘
MySQL에는 조인 쿼리의 실행 계획 최적화를 위한 알고리즘 2개가 있음.
이 알고리즘은 MySQL 5.0 부터 있던 기능임.
그 중요성에 비해, 모른느 경우가 많아 함께 설명한다.
MySQL의 조인 최적화는 나름 많이 개선됐다고 함.
하지만 사실 테이블 개수가 많아지면 최적화된 실행 계획을 찾는 것이 상당히 어려워짐.
하나의 쿼리에서 조인되는 테이블의 개수가 많아지면 실행 계획을 수립하는 데만 몇 분이 걸릴 수도 있음.
테이블의 개수가 특정 한계를 넘어서면 그떄부터는 실행 계획 수립에 소요되는 시간만 몇 시간이나 며칠로 늘어날 수도 있음..
왜 이런 현상이 생기고, 이 현상을 피하는 방법을 살펴보자.
MySQL 에는 최적화된 조인 실행 계획 수립을 위한 2가지 알고리즘이 있음.
적절한 한글 명칭이 없어서 영어를 그대로 표기함.
다음과 같이 간단한 4개의 테이블을 조인하는 쿼리 문장이 조인 옵티마이저 알고리즘에 따라 어떻게 처리되는지 보자.
mysql> SELECT *
FROM t1, t2, t3, t4
WHERE ...
Exhaustive 검색 알고리즘
Exhaustive 검색 알고리즘은 MySQL 5.0과 그 이전 버전에서 사용되던 조인 최적화 기법임.
FROM 절에 명시된 모든 테이블의 조합에 대해 실행 계획의 비용을 계산해서 최적의 조합 1개를 찾는 방법임.
위 그림은 4개의 테이블 (t1~t4)이 Exhaustive 검색 알고리즘으로 처리될 때 최적의 조인 순서를 찾는 방법을 표현한 것임.
테이블이 20개라면, 이 방법으로 처리했을 떄 가능한 조인 조합은 모두 20! (Factorial, 3628800)개가 됨.
이전 버전에서 사용되던 Exhaustive 검색 알고리즘에서는 사실 테이블이 10개만 넘어가도 실행 계획 수립에 몇 분 이걸림
그리고 테이블이 10개에서 1개만 더 늘어나도 11배의 시간이 더 걸림.
Greedy 검색 알고리즘
Greedy 검색 알고리즘은 Exhaustive 검색 알고리즘의 시간 소모적인 문제점을 해결하기 위해 MySQL 5.0부터 도입된 조인 최적화 기법임.
위 그림은 4개의 테이블(t1~t4)이 Greedy 검색 알고리즘으로 처리될 때(optimizer_search_depth 시스템 변수의 값은 2로 가정) 최적의 조인 순서를 검색하는 방법을 보여줌.
Greedy는 Exhaustive 검색 알고리즘보다는 조금 복잡한 형태로 최적의 조인 순서를 결정함.
위 그림의 순서는 다음과 같음.
전체 N개 테이블 중 optimizer_search_depth 시스템 설정 변수에 정의 된 개수의 테이블로 가능한 조인 조합을 생성함.
1번에서 생성된 조인 조합 중에서 최소 비용의 실행 계획 하나를 선정한다.
2번에서 선정된 실행 계획의 첫 번째 테이블을 “부분 실행 계획”의 첫번째 테이블로 선정한다.
전체 N-1개의 테이블 중(3번에서 선택된 테이블 제외)에서 optimizer_search_depth 시스템 설정 변수에 정의된 개수의 테이블로 가능한 조인 조합을 생성함.
4번에서 생성된 조인 조합들을 하나씩 3번에서 생성된 “부분 실행 계획”에 대입해 실행 비용을 계산함.
5번의 비용 계산 결과, 최적의 실행 계획에서 두 번쨰 테이블을 3번에서 생성된 “부분 실행 계획”의 두 번째 테이블로 선정한다.
남은 테이블이 모두 없어질 때까지 4~6번까지의 과정을 반복실행하면서 “부분 실행 계획”에 테이블의 조인 순서를 기록한다.
최종적으로 “부분 실행 계획” 이 테이블의 조인 순서로 결정된다.
Greedy 검색 알고리즘은 optimizer_search_depth 시스템 변수에 설정된 값에 따라 조인 최적화의 비용이 상당히 줄어들 수 있음.
optimizer_search_depth 시스템 변수의 기본값은 62임.
MySQL에서는 조인 최적화를 위한 시스템 변수로 optimizer_prune_level 과 optimizer_search_depth 가 제공됨.
optimizer_search_depth 시스템 변수는 Greedy 검색 알고리즘과 Exhaustive 검색 알고리즘 중에서 어떤 알고리즘을 사용할지 결정하는 시스템 변수임. optimizer_search_depth 는 062 까지 정숫값을 설정할 수 있음. 162까지의 정숫값이 설정되면, Greedy 검색 대상을 지정된 개수로 한정해서 최적의 실행 계획을 산출함. optimizer_search_depth 값이 0이면 Greedy 검색을 위한 최적의 조인 검색 테이블의 개수를 MySQL 옵티마이저가 자동으로 결정함.
optimizer_search_depth 설정 값과 쿼리의 조인 테이블 개수에 따라 Exhaustive 검색만 사용되거나 Greedy 검색과 Exhaustive 검색이 동시에 사용됨. 조인에 사용된 테이블의 개수가 optimizer_search_depth 설정 값보다 크면 optimizer_search_depth 만큼의 테이블은 Exhaustive 검색이 사용되고 나머지 테이블은 Greedy 검색이 사용됨. 만약 조인에 사용된 테이블 개수가 optimizer_search_depth 설정값 보다 작으면 Exhaustive 검색만 사용됨.
optimizer_search_depth 시스템 변수는 기본 값이 62 이면 많은 테이블이 조인되는 쿼리에서 상당히 부담이 될 수 있음.
특히, optimizer_prune_level 시스템 변수가 0일 경우, optimizer_search_depth 설정 값이 쿼리의 성능에 심각한 영향을 미칠 수 있으므로 optimizer_search_depth 를 4~5정도로 설정하는 것이 좋음.
optimizer_prune_level 시스템 변수는 5.0 부터 추가된 Heuristic 검색이 작동하는 방식을 제어함.
Exhaustive 검색 알고리즘과 Greedy 검색 알고리즘 중에서 어던 알고리즘을 사용하더라도 MySQL 옵티마이저는 여러 테이블의 조인 순서를 결정하기 위해 상당히 많은 조인 경로를 비교함.
Heuristic 검색의 가장 핵심적인 내용은 다양한 조인 순서의 비용을 계산하는 도중, 이미 계산했던 조인 순서의 비용보다 큰 경우에는 언제든지 중간에 포기할 수 있다는 것임.
아우터 조인으로 연결되는 테이블은 우선순위에서 제거하는 등 경험 기반의 최적화도 Heuristic 검색 최적화에는 포함되어 있음.
optimizer_prune_level 이 1로 설정되면 옵티마이저는 조인 순서 최적화에 경험 기반 Heuristic 알고리즘을 사용함.
이 값이 0이라면 경험 기반의 Heuristic 최적화가 적용되지 않음.
실제 Heuristic 조인 최적화는 조인 대상 테이블이 몇 개 되지 않더라도 상당한 성능 차이를 낸다.
그러므로 특별한 요건이 없으면 optimizer_prune_level 을 0으로 설정하지 말자.
테이블 조인에서 많은 쿼리의 실행 계획 수립이 얼마나 느려질 수 있는지, optimizer_prune_level , optimizer_search_depth 를 조정하면 얼마나 더 빨라질 수 있는지 아래의 예시로 확인할 수 있다. (p368 부터 예시가 잘 설명되어 있음.)
칼럼 2개와 프라이머리 키, 보조 인덱스를 가진 테이블 tab01 부터 tab30까지 생성하고, 레코드를 2000건 정도 INSERT 함.
tab01 ~ tab30 까지 전부 조인하여 select 하는 쿼리(p368) 의 실행계획을 위 시스템 변수를 바꿔가며 실행 계획 수립 시간을 확인해보자.
optimizer_prune_level 시스템 변수를 1로 설정하고 optimizer_search_depth 세션 변수의 값을 1부터 5씩 증가시키며 62까지 변화시켜가면서 쿼리 실행 계획 수립에 걸린 시간은, 거의 시간차이 없이 0.01초 이내에 완료됨
MySQL 5.1 버전에서는 위 상황에서 Heuristic 최적화를 적용해도 실행 계획 수립에 1초 넘는 시간이 걸렸음.
지금 처럼 optimizer_search_depth 값 변화와 관계없이 실행 계획 수립이 아주 빠르게 처리되는 것은 MySQL 서버의 조인 최적화나 딕셔너리 정보 검색 성능이 버전이 올라감에 따라 많이 개선되었기 때문임.
이번에는 optimizer_prune_level 시스템 변수를 0으로 고정하고 optimizer_search_depth 세션 변수의 값을 1부터 5씩 증가하면서 실행 계획 수립 시간을 확인함.
5까지는 0.03초 정도였지만 10 부터 4.6초, 15부터는 너무 많은 시간이 소요되었음.
즉, 8.0 버전의 조인 최적화는 많이 개선되어 optimizer_search_depth 변수 값에는 크게 영향을 받지 않은 것으로 보임
하지만, optimizer_prune_level 을 0으로 설정하면 optimizer_search_depth 값 변화에 따라 실행 계획 수립에 소요되는 시간이 급증하는 것을 확인할 수 있었음.
즉, 예전 버전의 MySQL 서버에서는 조인 최적화와 관련된 휴리스틱(Heruistic)의 문제점이 있었지만, MySQL 8.0에서는 이런 조인 최적화 관련된 휴리스틱을 비활성화할 필요가 거의 없어짐.
쿼리 힌트
MySQL 버전이 업그레이드 되고 통계 정보나 옵티마이저의 최적화 방법들이 더 다양해지면서 쿼리의 실행 계획 최적화가 많이 성숙하고 있음.
하지만 여전히 MySQL 서버는 우리가 서비스하는 비즈니스를 100% 이해하지 못함.
그래서 서비스 개발자나 DBA 보다 MySQL 서버가 부족한 실행 계획을 수립할 떄가 있음.
이런 경우, 옵티마이저에게 쿼리의 실행 계획을 어떻게 수립해야할지 알려줄 수 있는 방법이 필요함.
일반적인 RDBMS에서는 이런 목적으로 힌트가 제공되며, MySQL에서도 다양한 옵티마이저 힌트를 제공함.
MySQL 서버에서 사용 가능한 쿼리 힌트는 다음과 같이 2가지로 구분할 수 있음.
인덱스 힌트
옵티마이저 힌트
인덱스 힌트는 예전 버전의 MySQL 서버에서 사용되던 “USE INDEX” 같은 힌트를 의미함.
옵티마이저 힌트는 MySQL 5.6버전부터 새롭게 추가되기 시작한 힌트들을 지칭함.
여기에 포함되지 않은 STRAIGHT_JOIN과 같은 힌트들도 있음.
여기서는 옵티마이저 힌트가 아닌 것들은 모두 모아서 인덱스 힌트 절로 분류해서 살펴봄.
인덱스 힌트
“STRAIGHT_JOIN”과 “USE INDEX” 등을 포함한 인덱스 힌트들은 모두 MySQL 서버에 옵티마이저 힌트가 도입되기 전에 사용되던 기능들임.
이들은 모두 SQL의 문법에 맞게 사용해야 하므로 사용하게 되면 ANSI-SQL 표준 문법을 준수하지 못하게 되는 단점이 있음.
MySQL 5.6 버전부터 추가되기 시작한 옵티마이저 힌트들은 모두 MySQL 서버를 제외한 다른 RDBMS에서는 주석으로 해석하기 때문에 ANSI-SQL 표준을 준수한다고 볼 수 있음.
그래서 가능하다면 인덱스 힌트보다는 옵티마이저 힌트를 사용할 것을 추천함.
또, 인덱스 힌트는 SELECT 명령과 UPDATE 명령에서만 사용할 수 있음.
STRAIGHT_JOIN
“STRAIGHT_JOIN”은 옵티마이저 힌트인 동시에 조인 쿼드이기도 함.
STRAIGHT_JOIN은 SELECT, UPDATE, DELETE 쿼리에서 여러 개의 테이블이 조인되는 경우, 조인 순서를 고정하는 역할을 함.
다음 쿼리는 3개 테이블을 조인하지만 어느 테이블이 드라이빙 테이블이되고 어느 테이블이 드리븐 테이블이 되는지 알 수 없음.
옵티마이저가 그떄그때 각 테이블의 통계 정보와 쿼리 조건을 기반으로 가장 최적이라고 판단되는 순서로 조인함.
mysql> EXPLAIN
SELECT *
FROM employees e, dept_emp de, departments d
WHERE e.emp_no=de.emp_no AND d.dept_no=de.dept_no;
+----+-------------+-------+--------+-------------+-------+-------------+
| id | select_type | table | type | key | rows | Extra |
+----+-------------+-------+--------+-------------+-------+-------------+
| 1 | SIMPLE | d | index | ux_deptname | 9 | Using index |
| 1 | SIMPLE | de | ref | PRIMARY | 41392 | NULL |
| 1 | SIMPLE | e | eq_ref | PRIMARY | 1 | NULL |
+----+-------------+-------+--------+-------------+-------+-------------+
위 쿼리의 실행 계획을 확인해 보면, departments 테이블을 드라이빙 테이블로 선택하고, 두 번쨰로 dept_emp 테이블을 읽은 뒤, 마지막으로 employees 테이블을 읽었음을 알 수 있음.
일반적으로 조인을 하기 위한 칼럼들의 인덱스 여부로 조인의 순서가 결정되며, 조인 칼럼의 인덱스에 아무런 문제가 없는 경우 레코드가 적은 테이블을 드라이빙으로 선택한다.
이 쿼리의 경우 departments 테이블이 레코드 건수가 가장 적어서 드라이빙으로 선택됐을 것으로 보임.
만약, 이 쿼리의 조인 순서를 변경하려는 경우, STRAIGHT_JOIN 힌트를 사용할 수 있음.
mysql> EXPLAIN
SELECT STRAIGHT_JOIN e.first_name, e.last_name, d.dept_name
FROM employees e, dept_emp de, departments d
WHERE e.emp_no=de.emp_no AND d.dept_no=de.dept_no;
+----+-------------+-------+--------+-------------------+--------+-------------+
| id | select_type | table | type | key | rows | Extra |
+----+-------------+-------+--------+-------------------+--------+-------------+
| 1 | SIMPLE | e | ALL | NULL | 300473 | NULL |
| 1 | SIMPLE | de | ref | ix_empno_fromdate | 1 | Using index |
| 1 | SIMPLE | d | eq_ref | PRIMARY | 1 | NULL |
+----+-------------+-------+--------+-------------------+--------+-------------+
STRAIGHT_JOIN 힌트는 옵티마이저가 FROM 절에 명시된 테이블의 순서대로 조인을 수행하도록 유도함.
employees → dept_emp → departments 조인을 수행하는 것을 실행계획에서 확인 가능.
주로, 다음 기준에 맞게 조인 순서가 결정되지 않는 경우에만 STRAIGHT_JOIN 힌트로 조인 순서를 조정하는 것이 좋음.
임시 테이블(인라인 뷰 또는 파생된 테이블)과 일반 테이블의 조인
이 경우, 거의 일반적으로 임시 테이블을 드라이빙 테이블로 선정하는 것이 좋음.
일반 테이블의 조인 칼럼에 인덱스가 없는 경우에는 레코드 건수가 작은 쪽을 먼저 읽도록 드라이빙으로 선택하는 것이 좋은데, 대부분 옵티마이저가 적절한 조인 순서를 선택하기 때문에 쿼리를 작성할 때부터 힌트를 사용할 필요는 없다.
옵티마이저가 실행 계획을 제대로 수립하지 못해서 심각한 성능 저하가 있는 경우에는 힌트를 사용하면 됨.
임시 테이블 끼리 조인
임시 테이블(서브 쿼리로 파생된 테이블)은 항상 인덱스가 없기 떄문에 어느 테이블을 먼저 드라이빙으로 읽어도 무관하므로 크기가 작은 테이블을 드라이빙으로 선택해주는 것이 좋음.
일반 테이블끼리 조인
양쪽 테이블 모두 조인 칼럼에 인덱스가 있거나 양쪽 테이블 모두 조인 칼럼에 인덱스가 없는 경우, 레코드 건수가 적은 테이블을 드라이빙으로 선택해주는 것이 좋음.
그 이외의 경우는 조인 칼럼에 인덱스가 없는 테이블을 드라이빙으로 선택하는 것이 좋음.
위 쿼리의 예시에서, 만약 employees 테이블의 건수가 훨씬 많지만, 조건을 만족하는 employees 테이블의 레코드 건수가 훨씬 적은 경우라면, STRAIGHT_JOIN 힌트를 이용해 employees ㅌ에ㅣ블을 드라이빙되게 하는 것이 좋음.
USE INDEX / FORCE INDEX / IGNORE INDEX
조인의 순서를 변경하는 것 다음으로 자주 사용되는 것이 인덱스 힌트임.
STRAIGHT_JOIN 힌트와 달리, 인덱스 힌트는 사용하려는 인덱스를 가지는 테이블 뒤에 힌트를 명시해야 함.
대체로, MySQL 옵티마이저는 어떤 인덱스를 사용해야 할지를 무난하게 잘 선택하는 편임.
하지만 3~4개 이상의 칼럼을 포함하는 비슷한 인덱스가 여러 개 존재하는 경우, 가끔 옵티마이저가 실수함. 이런 경우, 강제로 특정 인덱스를 사용하도록 힌트를 추가한다.
인덱스 힌트는 크게 다음 3종류가 있음.
3 종류의 인덱스 힌트 모두 키워드 뒤에 사용할 인덱스의 이름을 괄호로 묶어서 사용한다.
괄호 안에 아무것도 없거나 존재하지 않는 인덱스 이름을 사용할 경우, 쿼리의 문법 오류로 처리됨.
또, 별도로 사용자가 부여한 이름이 없는 프라이머리 키는 “PRIMARY” 라고 명시하면 됨.
USE INDEX
가장 자주 사용되는 인덱스 힌트임.
MySQL 옵티마이저에게 특정 테이블의 인덱스를 사용하도록 권장하는 힌트임
대부분, 인덱스 힌트가 주어지면 옵티마이저는 사용자의 힌트를 채택하지만, 항상 그 인덱스를 사용하는 것은 아님.
FORCE INDEX
USE INDEX와 비교해서 다른점은 없음.
USE INDEX보다 옵티마이저에게 미치는 영향이 더 강한 힌트로 생각하면 됨.
하지만 USE INDEX 힌트만으로도 옵티마이저에 대한 영향력이 충분히 크기에 FORCE INDEX는 거의 사용할 필요가 없어 보임.
지금까지 경험으로 보면 대체로 USE INDEX 힌트를 부여했는데도 그 인덱스를 사용하지 않는 경우라면 FORCE INDEX 힌트를 사용해도 그 인덱스를 사용하지 않았음.
IGNORE INDEX
USE INDEX나 FORCE INDEX와는 반대로, 특정 인덱스를 사용하지 못하게 하는 용도로 사용하는 힌트임.
때로는 옵티마이저가 풀 테이블 스캔을 사용하도록 유도하기 위해 IGNORE INDEX 힌트를 사용할 수도 있음.
위 3종류의 인덱스 힌트 모두 용도를 명시해 줄 수 있음.
용도는 선택사항이며, 특별히 인덱스 힌트에 용도가 명시되지 않으면(사용 가능한 경우) 주어진 인덱스를 3가지 용도로 사용함.
USE INDEX FOR JOIN
여기서 JOIN이라는 키워드는 테이블 간의 조인뿐만 아니라 레코드를 검색하기 위한 용도까지 포함하는 용어임.
이미 실행 계획 부분에서도 한 번 언급했듯, MySQL 서버에서는 하나의 테이블로부터 데이터를 검색하는 작업도 JOIN이라고 표현하므로 FOR JOIN 이라는 이름이 붙은 것임.
USE INDEX FOR ORDER BY
명시된 인덱스를 ORDER BY 용도로만 사용할수 있게 제한함.
USE INDEX FOR GROUP BY
명시된 인덱스를 GROUP BY 용도로만 사용할 수 있게 제한함.
이렇게 용도를 3가지로 나누긴 했지만 ORDER BY, GROUP BY 작업에서 인덱스를 사용할 수 있다면 나은 성능을 보장함.
용도는 옵티마이저가 대부분 최적으로 선택하기 때문에 인덱스의 용도까지는 크게 고려하지 않아도 됨.
위 종류 3가지 인덱스 힌트는 p375에 잘 설명되어 있음.
전문 검색(Full Text search) 인덱스가 있는 경우, MySQL 옵티마이저는 다른 일반 보조 인덱스 (B-Tree 인덱스)를 사용할 수 있는 상황이라고 하더라도 전문 검색 인덱스를 선택하는 경우가 많음.
옵티마이저는 프라이머리 키나 전문 검색 인덱스와 같은 인덱스에 대해서는 선택 시 가중 치를 두고 실행 계획을 수립하기 때문임.
인덱스의 사용법이나 좋은 실행 계획이 어떤 것인지 판단하기 힘든 상황이라면, 힌트를 사용해 강제로 옵티마이저의 실행 계획에 영향을 미치는 것은 피하는 것이 좋음.
이제 MySQL의 옵티마이저도 한눈에 파악할 수 있을 정도의 최적화는 눈 깜짝할 사이에 처리하기 때문임.
최적의 실행 계획은 데이터의 성격에 따라서 시시각각 변하므로 지금 프라이머리 키를 사용하는 것이 좋은 계획이었더라도, 내일은 달라질 수 있기 때문에 가능하다면 그때그때 옵티마이저가 당시 통계 정보를 가지고 선택하는 것이 가장 좋음.
가장 훌륭한 최적화는 그 쿼리를 서비스에서 없애 버리거나 튜닝할 필요가 없게 데이터를 최소화 하는 것임.
그것이 어렵다면 데이터 모델의 단순화를 통해 쿼리를 간결하게 만들고, 힌트가 필요치 않게 하는 것임.
어떤 방법도 없다면, 그다음으로 힌트를 선택하는 것임.
일반적으로 실무에서는 핲쪽의 작업들에 상당한 시간과 작업 능력이 필요하므로 항상 이런 힌트에 의존하는 경우가 많음.