반응형

mysql에 thread_cache_size라는 것이 있다.
이것의 역할은 connection당 스레드를 생성하고 해제할 때 메모리를 할당하고 캐시할 메모리(192K or 256K)를 미리 생성해놓고 적절하게 관리하는 용도이다.
일종의 connection pool기능을 하는 역할인데 현재 서비스 DB를 확인해보니 cache miss rate이 17% 정도된다.

 

mysql> show global variables like 'thread%';
+-------------------+---------------------------+
| Variable_name     | Value                     |
+-------------------+---------------------------+
| thread_cache_size | 8                         |
| thread_handling   | one-thread-per-connection |
+-------------------+---------------------------+

mysql> show status like 'thread_%';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_cached    | 8     |
| Threads_connected | 106   |
| Threads_created   | 62769 |
| Threads_running   | 3     |
+-------------------+-------+

mysql> show status like 'connections%';
+---------------+--------+
| Variable_name | Value  |
+---------------+--------+
| Connections   | 357651 |
+---------------+--------+
1 row in set (0.00 sec)

//cache miss rate(%) = (Threads_created / connections) * 100 --> (62769/357651) * 100 = 17.5%

스레드를 수정하는 것이라서 바로 반영하기엔 부담이 많이 되었고 miss rate 17%가 이게 좋은 것인지 나쁜것이지 판단이 안되었다.
여러가지로 자료조사를 해보니 국내 사이트에서는 튜닝한 사례를 소개한 글은 찾기 어려웠고..
시간이 좀 걸리긴 했지만 percona site와 High Performance MySQL 책에서 어느정도 답을 찾을 수 있었다.

 

1. 자료수집

1) percona site
https://www.percona.com/blog/2017/10/09/mysql-and-mariadb-default-configuration-differences/
여기에서 블로그 타고타고 들어갔던 거 같은데 현재는 못찾겠다.
일단 여기 엔지니어의 얘기로는 default =8 은 작고 적어도 default = 16이상 설정하는 게 좋다라고 되어있었다.

->따라서 1차적으로 thread_cache_size를 default에서 16으로 바꾸는 게 좋겠다고 판단했다.

 

2) High Performance MySQL 내용
#thread_cache_size
Setting this variable has no immediate effect—the effect is delayed until the next time a connection is closed.
At that time, MySQL checks whether there is space in the cache to store the thread.
If so, it caches the Thread for future reuse by another connection.
If not, it kills the Thread instead of caching it. In this case, the number of threads in the cache, and hence the amount of memory the thread cache uses, does not immediately decrease;
it decreases only when a new connection removes a thread from the cache to use it.
(MySQL adds threads to the cache only when connections close and removes them from the cache only when new connections are created.)
이 변수를 설정하는 것은 즉각적인 효과는 없다. 그 효과는 다음 번에 연결(connection)이 닫힐 때까지 지연된다.
이때 MySQL은 캐시에 스레드를 저장할 공간이 있는지 확인한다. 공간이 있다면 또 다른 연결(connection)에 의해 나중에 재사용할 수 있도록 스레드를 캐싱한다.
공간이 없다면 그것을 캐싱하는 대신 스레드를 죽인다. 이 경우 캐시에 있는 스레드 수와 그에 따른 사용 메모리양이 즉시 감소하지는 않는다.
새 연결이 캐시에서 스레드를 제거하여 사용할 때만 감소한다. (MySQL은 연결이 닫힐 때만 캐시에 스레드를 추가하고 새 연결이 생성될 때만 캐시에서 스레드를 제거함)

 

#thread_cache_size
You can compute a reasonable value for this variable by observing the server’s behavior over time.
Watch the Threads_connected status variable and find its typical maximum and minimum.
You might want to set the thread cache large enough to hold the difference between the peak and off-peak usage, and go ahead and be generous,
because if you set it a bit too high it’s not a big problem. You might set it two or three times as large as needed to hold the fluctuations in usage.
당신은 시간이 지남에 따라 서버의 동작을 관찰함으로써 이 변수에 대한 합리적인 값을 계산할 수 있다.
Threads_connected 상태 변수를 보고 일반적인 최대값과 최소값을 찾으십시오.
스레드 캐시를 최대 사용량과 최소 사용량 간의 차이를 충분히 수용할 수 있을 정도로 크게 설정하고, 계속하여 관대하게 처리하십시오.
왜냐하면 너무 높게 설정하는 것은 그리 큰 문제가 되지 않기 때문이다. 사용량의 변동을 억제하기 위해 필요한만큼의 두 세 배정도로 크게 설정할 수도 있다.
-> 이것에서 많은 힌트를 얻었다. 여기에서 설명하는 내용을 정리해보면 평소 connection의 수와 max connection의 수의 차만큼 thread_cache_size를 할당하는 것으로 확인하였다.
그리고 아래는 그것에 대한 예를 들고 있다.

 

For example, if the Threads_connected status variable seems to vary between 150 and 175, you could set the thread cache to 75.
But you probably shouldn’t set it very large, because it isn’t really useful to keep around a huge amount of spare threads waiting for connections;
a ceiling of 250 is a nice round number (or 256, if you prefer a power of two).
You can also watch the change over time in the Threads_created status variable.
If this value is large or increasing, it’s another clue that you might need to increase the thread_cache_size variable.
예를 들어, Threads_connected 상태 변수가 150과 175 사이에 차이가 나는 경우, 스레드 캐시를 75로 설정할 수 있다.
하지만 아마도 당신은 그것을 그리 크게 설정해서는 안 될 것이다. 엄청난 양의 여분의(spare) 스레드가 접속을 기다리게 하는 것은 별로 유용하지 않기 때문이다.
최대 한계값 250은 좋은 근사치값이다. (또는 만약 제곱을 원한다면 256)
Thread_created 상태 변수에서도 시간에 따른 변화를 볼 수 있다.
이 값이 크거나 증가하면 Thread_cache_size 변수를 늘려야 할 수도 있다는 또 다른 단서다.


Check Threads_cached to see how many threads are in the cache already.
A related status variable is Slow_launch_threads.
A large value for this status variable means that something is delaying new threads upon connection.
This is a clue that something is wrong with your server, but it doesn’t really indicate what.
캐시에 이미 있는 스레드 수를 보려면 Thread_cached 를 확인하세요.
관련 상태 변수는 Slow_launch_threads이다.
이 상태 변수의 큰 값은 연결 시 어떤 것이 새 스레드를 지연시키고 있음을 의미한다.
이것은 서버에 문제가 있다는 단서지만, 실제로 무엇을 나타내지는 않는다.

 

It usually means there’s a system overload, causing the operating system not to schedule any CPU time for newly created threads.
It doesn’t necessarily indicate that you need to increase the size of the thread cache.
You should diagnose the problem and fix it rather than masking it with a cache, because it might be affecting other things, too.
이는 대개 시스템 과부하가 발생하여 운영 체제가 새로 생성된 스레드의 CPU 시간을 스케줄(일정을 계획)하지 못하게 한다는 것을 의미한다.
반드시 스레드 캐시의 크기를 늘릴 필요가 있음을 나타내는 것은 아니다.
다른 것에도 영향을 줄 수 있기 때문에 캐시로 가리기보다는 문제를 진단하고 고쳐야 한다.

 

#The Thread Cache
The thread cache holds threads that aren’t currently associated with a connection but are ready to serve new connections.
When there’s a thread in the cache and a new connection is created, MySQL removes the thread from the cache and gives it to the new connection.
When the connection is closed, MySQL places the thread back into the cache, if there’s room.
If there isn’t room, MySQL destroys the thread. As long as MySQL has a free thread in the cache it can respond rapidly to connection requests, because it doesn’t have to create a new thread for each connection.
스레드 캐시는 현재 연결과 연결되지 않았지만 새 연결을 제공할 준비가 된 스레드를 보관한다.
캐시에 스레드가 있고 새 연결이 생성되면 MySQL은 캐시에서 스레드를 제거하여 새 연결에 부여한다.
연결이 닫히면 MySQL은 공간이 있는 경우 스레드를 캐시에 다시 넣는다. 공간이 없으면 MySQL은 스레드를 파괴한다.
MySQL이 캐시에 사용가능한(free) 스레드가 있는 한 각 연결에 대해 새 스레드를 만들 필요가 없기 때문에 연결 요청에 빠르게 응답할 수 있다.

 

The Thread_cache_size variable specifies the number of threads MySQL can keep in the cache.
You probably won’t need to configure this value unless your server gets many connection requests.
To check whether the thread cache is large enough, watch the Threads_created status variable.
thread_cache_size 변수는 MySQL이 캐시에 보관할 수 있는 스레드 수를 지정한다.
서버가 많은 연결 요청을 받지 않는 한 이 값을 구성할 필요가 없을 것이다.
스레드 캐시가 충분히 큰지 확인하려면 Threads_created 상태 변수를 확인하세요.

 

We generally try to keep the thread cache large enough that we see fewer than 10 new threads created each second, but it’s often pretty easy to get this number lower than 1 per second.
A good approach is to watch the Threads_connected variable and try to set Thread_cache_size large enough to handle the typical fluctuation in your workload.
For example, if Threads_connected usually stays between 100 and 120, you can set the cache size to 20.
If it stays between 500 and 700, a thread cache of 200 should be large enough.
우리는 일반적으로 초당 10개 미만의 새로운 스레드가 생성되는 것을 볼 수 있을 정도로 스레드 캐시를 크게 유지하려고 노력하지만, 종종 이 숫자를 초당 1보다 낮게 설정하는 것은 꽤 쉽다.
좋은 접근방식은 Threads_connected 변수를 관찰하고 일반적인 워크로드의 변동을 처리할 수 있을 만큼 thread_cache_size를 크게 설정하는 것이다.
예를 들어, 일반적으로 Threads_connected의 수가 100에서 120 사이인 경우 캐시 크기를 20으로 설정할 수 있다.
500에서 700사이에 머문다면 thread_cache_size는 200정도로 충분히 커야 한다.

 

Think of it this way: at 700 connections, there are probably no threads in the cache;
at 500 connections, there are 200 cached threads ready to be used if the load increases to 700 again.
Making the thread cache very large is probably not necessary for most uses, but keeping it small doesn’t save much memory, so there’s little benefit in doing so.
Each thread that’s in the thread cache or sleeping typically uses around 256 KB of memory.
이렇게 생각해 보십시오. 700개의 연결에서 캐시에 스레드가 없을 수 있으며,
500개의 연결에서, 부하가 다시 700개로 증가할 경우 사용할 수 있는 캐시된 스레드가 200개 있다.
스레드 캐시를 매우 크게 만드는 것은 아마도 대부분의 용도에 필요하지 않을 것이지만, 작게 유지하는 것은 많은 메모리를 절약하지 못하므로 그렇게 하는 것이 별로 이득이 되지 않는다.
스레드 캐시에 있거나 절전 모드인 각 스레드는 일반적으로 약 256KB의 메모리를 사용한다.

 

This is not very much compared to the amount of memory a thread can use when a connection is actively processing a query.
In general, you should keep your thread cache large enough that Threads_created doesn’t increase very often.
If this is a very large number, however (e.g., many thousand threads), you might want to set it lower because some operating systems don’t handle very large numbers of threads well, even when most of them are sleeping
이는 연결이 쿼리를 능동적으로 처리할 때 스레드가 사용할 수 있는 메모리 양에 비하면 그리 많지 않다.
일반적으로 스레드_생성된 스레드 캐시가 자주 증가하지 않을 정도로 충분히 큰 스레드 캐시를 유지해야 한다.
그러나 이 수가 매우 많은 경우(예: 수천 개의 스레드) 일부 운영 체제는 많은 수의 스레드를 잘 처리하지 못하기 때문에 대부분의 스레드가 절전 모드일 때 조차도 이 스레드를 낮게 설정하는 것이 좋다.
-> 예상치 못한 메모리 leak이 발생할 수도 있다고 한다. 반영후 2주정도 확인해서 적정값을 찾아야 한다. 1%내외로 유지하는 것이 목표

 

2. 서비스 DB 반영

 

1차 반영)
percona 엔지니어의 얘기대로 일단 defalut에서 16으로 변경하고 cache miss rate이 떨어지는지 확인함

 

SET global thread_cache_size = 16;


mysql> show global variables like 'thread%';
+-------------------+---------------------------+
| Variable_name     | Value                     |
+-------------------+---------------------------+
| thread_cache_size | 16                        |
| thread_handling   | one-thread-per-connection |
+-------------------+---------------------------+

mysql> show status like 'connections';
+---------------+--------+
| Variable_name | Value  |
+---------------+--------+
| Connections   | 490123 |
+---------------+--------+
1 row in set (0.00 sec)

mysql> show status like 'thread_%';
+-------------------+-------+
| Variable_name     | Value |
+-------------------+-------+
| Threads_cached    | 4     |
| Threads_connected | 106   |
| Threads_created   | 74044 |
| Threads_running   | 3     |
+-------------------+-------+
4 rows in set (0.00 sec)

mysql> show status like '%thread%';
+------------------------------------------+-------+
| Variable_name                            | Value |
+------------------------------------------+-------+
| Delayed_insert_threads                   | 0     |
| Performance_schema_thread_classes_lost   | 0     |
| Performance_schema_thread_instances_lost | 0     |
| Slow_launch_threads                      | 0     |
| Threads_cached                           | 0     |
| Threads_connected                        | 125   |
| Threads_created                          | 83512 |
| Threads_running                          | 5     |
+------------------------------------------+-------+
8 rows in set (0.00 sec)

//cache miss rate(%) = (Threads_created / connections) * 100 --> (74044/490123) * 100 = 15.10%
//-> cache miss rate이 떨어지는 것을 확인함

#주의점!
-> Slow_launch_threads 값을 반드시 확인해야함

 

# Slow_launch_threads
이 값이 크다면 어떠한 원인으로 새 스레드를 지연시키고 있음을 의미한다. 이것은 서버에 문제가 있다는 단서지만, 실제로 무엇인지 나타내지는 않는다.
이는 대게 시스템 과부하가 발생하여 운영체제가 새로 생성된 Thread의 CPU time을 스케줄(일정을 계획)하지 못하게 된다는 것이다.
반드시 스레드 캐시의 크기를 늘릴 필요가 있음을 나타내는 것은 아니다. 다른 것에도 영향을 줄 수 있기 때문에 캐시로 가리기보다는 문제를 진단하고 고쳐야 한다.

 

2차)

1주일정도 모니터링해보니 cache_miss rate 떨어지고 시스템에 영향이 없어 High Performance MySQL 내용대로 추가 반영를 계획함
peak time시 thread_connected를 확인하여 그 차만큼 thread_cache_size를 늘린다.
mysql> show status like 'thread_%'; 로 확인해보니 현재 DB의 connection thread는 110~150사이였다.

바로 48을 반영하고 싶었지만 절반인 24를 할당하여 한번 더 확인하기로 하였다.

 

SET global thread_cache_size = 24;

cache miss rate(%) = (Threads_created / connections) * 100 -> 3.7%
-> 3시간단위의 max와 표준편차를 이력추척해보았는데 3.7%정도 나온다.

조금 더 내려본다. 보통 max가 150정도 low가 110이라 40정도 생각한다. 

이진법으로 확인해서 24의 두배인 48로 올려보고 다시 로그를 모니터링 해본다.

 

3차)

SET global thread_cache_size = 48;
-> High Performance MySQL저자 말대로 나도 이진법을 믿는 사람중에 하나일까?ㅎ

cache miss rate(%) = (Threads_created / connections) * 100 -> 0.35%
-> 이제 적정값을 찾았다. 모니터링하면서 miss rate가 감소하는지 확인한다.

 

3. 반영결과

 

thread_cache_size tunnig

한달 안에 1%로 가는 것이라고 생각했지만, 중간에 1번의 장애라는 변수(Threads_created 값이 급증;)가 생겨서 실제 수지는 그렇게 바로 내려가지 않았다.
하지만 현재도 시간단위 추적해보면 1% 내외이고 적정한 튜닝으로 확인하였다. 언젠간 1%로 내려가겠지..
아래는 반영후 모니터링 값이다. 요즘 장애가 많이 줄었고, 여유로운 cpu usage를 볼때마다 뿌듯하다.

끝~

반응형

'RDB > mysql' 카테고리의 다른 글

점차 멀어지는 mysql vs MariaDB  (0) 2020.11.10
mysql text 형 어떤 걸 쓸까?  (0) 2020.10.15
[튜닝] 유물발굴 (mysql tuning using heavy query)  (0) 2020.09.18
mysql을 시작하기 전에 3  (8) 2020.08.26
mysql을 시작하기 전에 2  (0) 2020.08.11
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,
반응형

1. 유물발굴

 

분명 느린쿼리는 아니었다 수행시간도 0.5초 이내였고, 슬로우쿼리 로그체크 시간은

2초라서 평소에는 슬로우쿼리 목록에서 보이지 않았는데,

트래픽이 몰리고 메모리 할당에 지연이 생기면 항상 가장 많이 보이던..

그래서 이번에 수정하자는 주위의견이 있어 검토에 들어갔다.

삽엽충 정도로 생각했는데, 이게 full processlist 로 확인해보니 티라노 사우르스다.

쿼리 라인이 300 정도 되고 끝도 없는 join 파티에 group by, file sort, temporary

부하에 대한 고려가 전혀없이 한 번에 모든 것을 가져가기 위해 만든 쿼리같아 보였다.

물론 그냥 덮어놓고 싶었지만 뭐 일단 열었으니 발골해서 붓칠이라도 해보기로 했다.

 

2. 개선전 PLAN 확인

 

 

이 쿼리가 OLAP라면 수행시간이 0.5초 내외고 그닥문제 되지 않을 것이다.

하지만 이 쿼리는 OLTP이고 분당 200회이상 호출하는 쿼리이다.

풀스캔의 row수가 너무 크고, 파일소트도 문제지만 임시테이블을 너무많이 사용한다.

메모리 할당 과 해제시 CPU의 자원소모가 심할 것으로 보인다.

 

 

3. 튜닝

1) 풀스캔(ALL) 제거

--> 최대한 제거할수 있는 만큼 풀스캔을 줄이기 위해 인덱스 추가

 

2) 쿼리개선

--> mysql 5.1 버전에서 볼 수 있는 쿼리형식을 5.7에 개선된 형태에 맞게 수정함

 

3) 정렬인덱스(group by) 추가

--> 드라이빙 테이블을 적절하게 활용하지 못하고 file sort를 쓰는 것을 확인하여

      정렬인덱스를 추가하여 인덱싱 가능하게 개선

 

4) function 개선

--> funciton 안에 now()을 쓰는등 비효율적으로 작성된 function을 제거하고

      subquery로 변경하여 메모리 캐싱을 쓸 수 있게 변경함

 

4. 개선후 PLAN 확인

 

 

쿼리수행시간은 0.5초에서 0.06초로 줄였다.

남아 있는 풀스캔은 테이블 데이터 건수가 얼마 안되서 옵티마이저가 풀스캔이 더 효율적이라고 판단한 것이고, filesort와 임시테이블 사용도 줄였다.

조회하는 rows도 30만건에서 710건으로 많이 줄어든 것을 볼 수 있다.

 

 

하나 더 개선하고 싶은 게 있었는데 인덱스 스캔을 하지만 6640 rows를 조회하는 테이블, 인덱스스캔이지만 그리 효율적이지는 않다.

테이블 스키마를 보고 데이터 분석을 해보니 테이블 설계한 것과 실제 쌓인 데이터가 뭔가 맞지가 않았다.

복합PK로 묶고 데이터의 정합성을 맞춘다면

비효율적인 인덱싱도 개선이 가능할 것으로 보이는데, 혼자 처리할 수 있는 일이 아니니 이건 추후에 진행하기로 했다.

 

 

5. 자빅스 모니터링 확인

 

 

 

메모리의 할당/해제와 풀스캔을 줄이니, CPU의 자원사용이 한결 여유로워진 것을 볼 수 있다.

실제 서버상에서 top으로 확인해보면 cpu jumping 이 반영전에 150~300 이었다면

현재는 30~90사이로 많이 안정되었다.

부하에 좀 더 견딜수 있는 단단한 DB가 되어가고 있다.

 

끝~

 

 

 

 

 

 

반응형
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,
반응형

1. Thread pool 이란?

thread를 미리 생성해놓고 적절하게 관리하는 시스템

 

2. connection pool이라는 것도 있던데?

개념이 조금 다른데 위키디피아 내용으로 정리한다.

 

#thread_pool vs Connection pool

 

#connection pool

In software engineering, a connection pool is a cache of database connections maintained so that the connections can be reused when future requests to the database are required
Connection pools are used to enhance the performance of executing commands on a database
Opening and maintaining a database connection for each user, especially requests made to a dynamic database-driven website application, is costly and wastes resources
In connection pooling, after a connection is created, it is placed in the pool and it is used again so that a new connection does not have to be established
If all the connections are being used, a new connection is made and is added to the pool
Connection pooling also cuts down on the amount of time a user must wait to establish a connection to the database.


소프트웨어 엔지니어링에서 연결 풀은 데이터베이스에 대한 향후 요청이 필요할 때 연결을 재사용할 수 있도록 유지 관리하는 데이터베이스 연결의 캐시다.
연결 풀은 데이터베이스에서 명령을 실행하는 성능을 향상시키는 데 사용된다.
각 사용자에 대한 데이터베이스 연결, 특히 동적 데이터베이스 기반 웹 사이트 응용프로그램에 대한 요청은 비용이 많이 들고 자원을 낭비한다.
연결 풀링에서는 연결이 생성된 후 풀에 배치하고 다시 사용하므로 새 연결을 설정할 필요가 없다.
모든 연결을 사용하는 경우 새 연결이 만들어지고 풀에 추가된다.
또한 연결 풀링은 데이터베이스에 대한 연결을 설정하기 위해 사용자가 기다려야 하는 시간을 줄인다.

--> mysql에서는 connection(thread)을 미리 생성해놓고 재사용하는 용도 thread_cache_size가 이 개념과 비슷하다.

 

#thread pool


In computer programming, a thread pool is a software design pattern for achieving concurrency of execution in a computer program.
Often also called a replicated workers or worker-crew model,[1] a thread pool maintains multiple threads waiting for tasks to be allocated for concurrent execution by the supervising program.
By maintaining a pool of threads, the model increases performance and avoids latency in execution due to frequent creation and destruction of threads for short-lived tasks.[2]
The number of available threads is tuned to the computing resources available to the program, such as a parallel task queue after completion of execution.

컴퓨터 프로그래밍에서, 스레드 풀은 컴퓨터 프로그램에서 실행의 동시성을 달성하기 위한 소프트웨어 설계 패턴이다.
종종 복제된 작업자 또는 작업자 스크루 모델이라고도 하며, 스레드 풀은 감독 프로그램에 의해 동시 실행이 할당되기를 기다리는 여러 개의 스레드를 유지한다.
이 모델은 스레드 풀을 유지함으로써 성능을 높이고 단명 작업에 대한 스레드 생성 및 파괴가 빈번해 실행 지연을 방지한다.
사용 가능한 스레드 수는 실행 완료 후 병렬 작업 대기열과 같이 프로그램에서 사용할 수 있는 컴퓨팅 리소스에 맞춰 조정된다.

--> connection(thread)을 미리 생성해놓고 재사용하면서 DB 트랜잭션을 관리하여 부하를 줄인다.

mysql에서만 보면 connection pool의 개념에서 부하를 관리할 수 있게 좀 더 나아간 버전(thread_pool_size)으로 보면 될 듯하다.

 

 

3. Mysql Thread pool

mysql thread pool

 

mysql은 클라이언트에서 접속을 요청할 때마다 해당 요청을 수행하는 스레드를 생성한다.

스레드(connection)를 생성할 때 스레드에서 필요한 thread_stack 등 기본적으로 사용할 메모리를 같이 할당한다.

(평소에는 그렇지 않겠지만 이러한 작업이 반복되다 적정임계치가 넘어서게 되면 DB에 부하를 발생시킨다.)

 

mysql commuity 버전은 thread pool을 사용할 수 없고

mysql enterprise 버전과 MariaDB에서만 사용가능하다.

mysql에서 스레드풀 사용이 유료 라이선스인 이유는 동시접속이 몰려 부하가 발생했을 때 알 수 있는데 아래 그림이 그것을 설명한다.

 

 

4. mysql thread pool의 이점 상세내용

여기에서 설명한 내용이 너무 잘되있어서 그대로 차용하였다.

참조: xdhyix.wordpress.com/2015/06/29/mysql-%EC%8A%A4%EB%A0%88%EB%93%9C%ED%92%80/

 

1) MySQL 서버가 쿼리 수행을 위한 충분한 CPU와 메모리 리소스를 확보할 때까지 쿼리 수행을 기다림
2) 커넥션에 대한 진행중인 트랜잭션 쿼리의 우선순위를 매김
3) 쓰레드를 그룹으로 나누어 그룹당 하나의 액티브 쓰레드를 관리하는데 목표를 두고 동작
4) 쿼리가 지연처리 되거나 오랜시간 수행될 때 데드락을 피함

 

 

5. 결론

엄청나게 트랜잭션이 몰리는 시스템이면 thread pool을 쓰는 게 좋겠지만 오픈소스 DB를 사용하는 목적에 기본적으로 비용절감이라는 것이 있기 때문에 적절하게 thread_cache_size 튜닝을 한다면 어느정도 시스템 안정성을 보장할 수 있다.

-> Clone DB인 MariaDB에서는 무료버전에서도 thread pool을 사용할 수 있다. 개인적으로 상용서비스에서 mysql에서 MariaDB로 가는게 조금 꺼려지는 이유는 장애시 로그에 대한 러퍼런스를 찾기가 쉽지 않다. mysql보다 관련내용이 많이 부족하다. 비슷하지만 detail에서는 조금 다르다.

현재도 상용서비스에 Mysql , MariaDB 다 쓰고 있지만 장애 때 MariaDB쪽에 문제가 생기면 조금 더 부담이 되는 것은 사실이다. ㅎㅎ

끝~

 

 

 

반응형
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,
반응형

#Context switcing 이란?

CPU가 어떤 프로세스를 실행하고 있는 상태에서 인터럽트에 의해 다음 우선 순위를 가진 프로세스가 실행되어야 할 때
기존의 프로세스 정보들은 PCB에 저장하고 다음 프로세스의 정보를 PCB에서 가져와 교체하는 작업을 컨텍스트 스위칭이라 한다.

context-switching

#PCB(process Control Block)란?
Process ID와 상태, 우선순위, 메모리 정보 등을 저장한다.
멀티스레드가 아닌 멀티프로세스 환경에서는 PCB가 PC(program counter)와 Register set 정보도 포함한다.

 

#TCB(Thread Control Block)란?
Thread별로 존재하는 자료구조이며, PC와 Register Set(CPU 정보), 그리고 PCB를 가리키는 포인터를 가진다.
그리고 TCB는 PCB보다 적은 데이터를 가지는 자료구조이다. 해당 Thread에 대한 정보만 저장하면 되기 때문이다

 

#Context switcing이 자주 일어나면 왜 부하가 발생할까?
메모리와 레지스터 사이의 데이터 이동도 I/O이다.
즉, 컨텍스트 스위칭 과정에서 I/O가 발생한다. 빈번한 I/O 발생은 overhead를 발생시킨다.
실행되는 process의 수가 많고, 빈번한 컨텍스트 스위칭이 발생한다면, 이는 성능을 떨어뜨린다.
하지만 I/O가 발생할 때, CPU는 비싼 자원을 기다리게 할 수 없다. 
I/O가 발생할 때, CPU를 게속 사용하려면 컨텍스트 스위칭은 피할 수 없다.

 

기본적인 Context switching은 위와 같고 mysql은 멀티스레드 구조라 아래와 같은 환경이다.

 

thread context-switching

 

스레드 스위치와 프로세스 스위치의 차이는 스레드 스위치 중에는 가상 메모리 공간이 그대로 유지되지만 프로세스 스위치에서는 그렇지 않다는 것이다. 

두 유형 모두 컨텍스트 스위치를 수행하기 위해 운영 체제 커널에 제어 권한을 넘겨주는 것을 포함한다.
레지스터를 전환하는 비용과 함께 OS 커널을 안팎으로 전환하는 프로세스는 컨텍스트 스위치를 수행하는 데 가장 큰 고정 비용이다.

 

context switching을 알아야하는 이유는 mysql의 장애상황이 대부분이 이것과 관련되기 때문이다.

대부분은 상황은 Cost가 큰 실행계획 등으로 인해 실행엔진에서 I/O 핸들러가 Disk에 가서 데이터를 찾을텐데 이게 오래걸리다 보면 waiting을 기다리지 않고 CPU는 다른 thread를 실행하기 위해 context switching을 발생하는 것으로 보인다.

 

#컨텍스트 스위치도 구분이 있다?

MySQL의 경우 스레드를 실행중에

조만간 디스크 IO, 네트워크 IO, 뮤텍스 대기 또는 양보와 같은 일부 차단 작업을 수행해야 한다.

이 경우 실행이 다른 프로세스로 전환되며, 이를 자발적 컨텍스트 스위치라고 한다.
반면에, 프로세스/스레드는 할당된 CPU 시간(그리고 이제는 다른 작업을 실행해야 함)을 사용하였거나 높은 우선순위 작업을 실행해야 하기 때문에 스케줄러에 의해 선점될 필요가 있을 수 있다.
이것을 비자발적인 컨텍스트 스위치라고 한다.

--> 어떤 것이 더 좋은 상황인지 아직 잘 모르겠다. mysql의 context switching 상황에 대해 설명한 블로그의 글을 참조하여 일단 정리한다.

 

#mysql 뮤택스 모든경합이 context-switching?

MySQL에서 모든 경합이 컨텍스트 스위치로 이어지는 것은 아니라는 점에 유의할 필요가 있다.
InnoDB는 자체적인 뮤텍스와 RW-locks를 구현하는데, 이는 종종 자원이 이용 가능해지기를 기다리기 위해 "spin"을 시도한다. 이것은 컨텍스트 스위치를 하기보다는 CPU 시간을 직접적으로 소모한다.

 

 

참조:
https://teraphonia.tistory.com/802
https://jhnyang.tistory.com/33
https://jinnify.tistory.com/36
https://www2.cs.duke.edu/courses/spring01/cps110/slides/threadsync/sld004.htm
https://hwan-shell.tistory.com/197

반응형
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,
반응형

#프로세스란?
자기 자신만의 주소공간을 갖는 독립적인 실행 프로그램
멀티프로세스 : 두 개 이상의 프로세스가 실행되는 것. 프로그램이 여러개 띄워져 있는 형식
멀티 태스킹 : 두 개 이상의 프로세스를 실행하여 일을 처리하는것

 

#스레드란?
스레드(경량 프로세스) : 프로세스 내의 독립적인 순차 흐름 또는 제어
멀티 스레드 : 하나의 프로세스에서 여러 개의 스레드가 병행적으로 처리

 

mysql은 멀티 스레드구조로 standard 버전(무료)까지는 one-thread-per-connection 만 지원하며 enterprise 버전부터 thread-pool 을 사용할 수 있다.
MariaDB와 차이는 여기서 발생하는데 잔버그가 있어도 MariaDB를 사용하는 이유중 하나는 무료로 thread-pool을 쓸 수 있기 때문이다.
다시 mysql로 돌아와서 standard 버전을 기준으로 설명하면 한 커넥션당 하나의 스레드를 연다.
Thread Max값은 my.cnf에 max_connections 파라미터에 설정할 수 있고 이후 중요한 튜닝포인트 중 하나이다.

 

mysql>show global variables like 'thread%';

 

#mysql 조인방식

 

단일 코어에서 Nested Loop Join 처리
mysql에서는 모든 sql처리를 단일 코어에서 Nested Loop Join방식으로 처리를 한다. 기본적인 스토리지 엔진에서는 단일 코어 수행하고, 일부 3rd 스토리지 엔진을 플러그인으로 설치하면 병렬처리가 가능하다.

그래서 cpu 코어 개수를 늘리기 보다는, 단위 처리량이 좋은 cpu로 바꾸는게 더 유리하다

 

#Nested Loop Join(이하 NL-Join)이란?

선행 테이블(A)의 조건 검색 결과 값 하나하나를 엑세스 하면서 연결할 테이블(B)에 대입하여 조인하는 방식이다.
즉, 연결할 테이블 수(조인의 개수)가 늘어날수록, 쿼리 효율이 기하급수적으로 떨어진다.
모든 조인은 NL-Join으로 처리한다.


for문 안에서 for문을 실행시키는 구조라고 보면된다.

mysql은 오라클처럼 힌트가 많은 것이 아니기 때문에 limit 라는 좋은 기능을 잘 사용하고

나머지는 join과 index 스캔을 잘 튜닝해서 사용하면 된다.

반응형
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,
반응형

#mysql DB에서 swap 메모리를 쓴다는 것은?

innodb buffer pool에 할당된 메모리를 다쓰고 메모리가 부족할 때 일시적으로 swap 메모리(disk)를 사용한다.

 

#리눅스에서 swap 메모리설정

vi /etc/sysctl.conf
---------------------------------------------------
## swap setting
vm.swappiness=0

vm.swappiness=0 으로 하면 스왑메모리를 사용하지 않겠다는 것이지만 실제로 0으로 설정해도 mysql DB는 최소한의 스왑메모리를 사용한다. 메모리가 부족하면 DB가 죽을 수 있으므로 swappiness에 0으로 할당되어있다 하더라도 DB OS 자체를 보호하기위해 최소한의 swap 메모리를 사용하는 것으로 보인다.

 

#Swap 메모리 초기화 방법

리눅스에서 swap 메모리를 초기화하는 것은 아주 간단하다.

일시적 메모리 사용의 증가로 swap을 사용하게될 경우 메모리에 여유가 생겨도 swap 메모리는 자동으로 초기화되지 않는다.  이것을 수동으로 초기화하려면 아래의 명령을 입력하면 된다. 당연히 root 권한에서 실행해야 한다.
swapoff -a && swapon -a

 

swapoff 처리에 시간이 조금 오래 걸릴 수 있는데 서버가 멈춘 것은 아니므로 걱정하지 않아도 된다.

swap 메모리에서 필요한 부분을 물리 메모리로 옮기는 처리중인 것이다.

 

#실제 초기화 작업

개발DB는 32G로 되어있고 mysql 인스턴스가 2개 떠있다.
개발장비가 부족하여 어쩔수 없이 멀티 인스턴스로 띄워놓았고, 각각 innodb_buffer_pool 은 16G 할당해놓았다.
당연히 50~80%가 MAX인데 그 이상(100%)을 설정해놓았으니 스왑메모리를 사용하였고 요즘 들어 너무 느려서 스왑메모리 사용을 초기화하고 관련 내용을 정리한다.

[root@admin]# free -m
             total       used       free     shared    buffers     cached
Mem:         32079      28264       3815          1        392       6806
-/+ buffers/cache:      21065      11014
Swap:        15999       5048      10951
//스왑메모리를 5G정도 사용하고 있다.

[root@admin]# swapoff -a && swapon -a
swapoff: /dev/sda3: swapoff failed: 메모리를 할당할 수 없습니다

DB가 메모리를 쓰고 있어서 남는 메모리가 없어 메모리 할당을 할수 없다는 메시지다.
DB 인스턴스 하나를 죽이고 버퍼캐시를 초기화해서 메모리를 비운다.

 

service mysqld_multi stop 1 --password=xxxxxxxx
echo 3 > /proc/sys/vm/drop_caches

[root@admin]# swapoff -a && swapon -a
//오 된다~

 

#top 확인

top - 18:30:39 up 564 days,  2:46,  2 users,  load average: 2.28, 1.89, 1.48
Tasks: 618 total,   2 running, 616 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0%us,  4.1%sy,  0.0%ni, 95.8%id,  0.1%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:  32849832k total, 21902092k used, 10947740k free,     8956k buffers
Swap:  4177092k total,  4177092k used,        0k free,    78480k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                     
30537 root      20   0  102m  628  544 D 91.6  0.0   5:12.30 swapoff                                                                                                      
 5525 root      20   0     0    0    0 S  1.0  0.0  80:24.23 kondemand/3                                                                                                  
 5528 root      20   0     0    0    0 S  1.0  0.0 195:01.54 kondemand/6                                                                                                  
   15 root      RT   0     0    0    0 S  0.7  0.0   0:20.01 migration/3                                                                                                  
 5540 root      20   0     0    0    0 S  0.7  0.0 195:24.81 kondemand/18
//음 뭔가 작업을 하고 있다. 프로세스는 100%까지 쓰는 것으로 보아 cpu는 1core를 사용하는 것을 알 수 있다.
//나머지 DB인스턴스에 영향을 미칠정도는 아닌 것을 알 수 있다.

#메모리 확인

...
[root@admin]$ free -m
             total       used       free     shared    buffers     cached
Mem:         32079      22070      10009          1         12        117
-/+ buffers/cache:      21940      10139
Swap:         3030       3030          0
[root@admin]$ free -m
             total       used       free     shared    buffers     cached
Mem:         32079      22089       9990          1         12        117
-/+ buffers/cache:      21959      10120
Swap:         3000       3000          0
[root@admin]$ free -m
             total       used       free     shared    buffers     cached
Mem:         32079      22091       9987          1         12        117
-/+ buffers/cache:      21962      10117
Swap:         2998       2998          0
[root@admin]$ free -m
             total       used       free     shared    buffers     cached
Mem:         32079      22093       9985          1         12        116
-/+ buffers/cache:      21964      10115
Swap:         2997       2997          0
[root@admin]$ free -m
             total       used       free     shared    buffers     cached
Mem:         32079      22228       9851          1         13        124
-/+ buffers/cache:      22090       9989
Swap:         2803       2803          0

// 5047M에서 swap메모리 사용을 계속 줄여서 0에 수렴하게 만든 후 다시 스왑메모리를 사용하도록 할당하는 작업을 하고 있다.
//근데 이게 생각보다 많이 느리다. SATA2에 5G정도사용했는데.. 30분이상 걸리고 있다.

...
..
.
[root@admin]$ free -m
             total       used       free     shared    buffers     cached
Mem:         32079      28125       3953          1        165       3658
-/+ buffers/cache:      24301       7778
Swap:        15999          0      15999

//완료된 후에 위처럼 스왑메모리가 초기화 되었고 free가 15999(16G)로 초기화 되어 있는 것을 알 수 있다.

다시 DB 인스턴스를 띄운다.

service mysqld_multi start 1

기분탓인가? 툴에서 쿼리 반응속도가 아까보단 좀 낫다.

장비하나 사주면 좋을텐데.. innodb 버퍼풀을 줄이고 싶지만 조인 20개이상 걸린 해비 쿼리가 많아서 줄이면
성능테스트를 할 수가 없다..;
당분간 이걸로 버텨야할 듯하다.
끝~

반응형
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,
반응형

Table B 컬럼추가

 

테이블의 데이터가 쌓이고 컬럼이 많거나 파티션 테이블의 경우는 한번 생성한 스키마에 인덱스를 걸거나 컬럼을 추가할 이슈가 생기면 상당히 까다로운 작업을 해야한다. 

--> 컬럼이 많은 것과 파티션 테이블 중 어느게 더 반영이슈가 있나 테스트 해본적이 있는데 컬럼 30개, 1억건 rows 파티션 테이블보다 컬럼 200개짜리 1000만 rows 테이블의 online DDL이 반영이 더 오래 걸린다.

간단한 DDL문이라고 생각하면 대규모 장애를 만날 수도 있다.
일단 수행시간이 오래걸리고 alter table ALGORITHM=INPLACE, LOCK=NONE; 을 쓸 수없기 때문에
지금은 모르겠지만 암튼 default인 mysql 5.7.19까지는 ALGORITHM=COPY로만 수행이 가능한 것이라.. 작업이 쉽지 않았다. 거기다 이 테이블은 트리거도 걸려있다...;;; 머리가 아프다.

 

상용서비스 반영 전에 적용방식을 테스트해본다.

 

1) Online DDL

mysql> alter table TESTDB.TB_xxxx
    -> add COLUMN xxxx_NO bigint(11) unsigned DEFAULT NULL COMMENT 'xxxxxx',
    -> add COLUMN xxx_xxx_NO bigint(11) unsigned DEFAULT NULL COMMENT 'zzzzzz'
    -> ;
Query OK, 0 rows affected (31 min 31.39 sec)
Records: 0  Duplicates: 0  Warnings: 0

Alter table add column 테스트 해보았는데 30분이상 걸렸다.

이대로 online 반영은 어려울 거 같다.

 

2) TABLE 교체

2-1) 컬럼추가할 테이블A와 동일한 테이블B를 하나 더 만든다 스키마 생성할 때 컬럼을 추가해서 만드는 것이다.
2-2) 이전 테이블A의 데이터를 테이블B에 insert 한 후 테이블을 교체한다.

use TESTDB;
Create table TB_xxxx_TMP
(
...
xxxx_NO bigint(11) unsigned DEFAULT NULL COMMENT 'xxxxxx',
xxx_xxx_NO bigint(11) unsigned DEFAULT NULL COMMENT 'zzzzzz',
  PRIMARY KEY (`xxx_NO`,`xxx_SEQ`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='xxxx이력'
;

INSERT INTO TESTDB.TB_xxxx_TMP
(xxxx_NO, xxx_xxx_NO 컬럼 2개를 제외한 이전 테이블A의 컬럼을 순서대로...)
select * from TESTDB.TB_xxxx;
commit;

Query OK, 8833879 rows affected (14 min 45.61 sec)
Records: 8833879  Duplicates: 0  Warnings: 0
// 테스트기는 약900만 상용서비스 DB는 1200만 정도 있음

rename table TB_xxxx_TMP to TB_xxxx_TMP2, TB_xxxx_TMP to TB_xxxx, TB_xxxx_TMP2 to TB_xxxx_TMP;
//테이블교체는 A->C, B->A, C->B 로 진행한다. 바로 교체보다 안전한 방식임

그래도 10분이상 걸린다...
테스트 해본바로는 insert into select ~ 는 select .. into values ~과는 달리 meta Lock이 걸리지 않았고
작업중에도 TB_xxxx에 insert와 select가 가능했다.
하지만 트랜젝션 격리수준이 default인 repeatable-read라서 안정성 측면에서 트리거를 끊고 작업하는 게 속편할 거 같다.  트리거에 덴적이 있어서 더 조심스러울 수도 있지만;;
사업이랑 협의해서 insert는 반영후 hist에 업데이트하고 update는 무시하기로 협의하였다.
고객트래픽이 몰리는 시간에 반영하긴 어렵고 9시 전에 작업하기로 하였다.

 

#최종작업계획 

2-1) insert update 트리거 중단
2-2) 컬럼추가할 테이블A와 동일한 테이블B를 하나 더 만든다 스키마 생성할 때 컬럼을 추가해서 만드는 것이다.
2-3) 이전 테이블A의 데이터를 테이블B에 insert 한 후 테이블을 교체한다.
2-4) insert update 트리거 재생성
2-5) TB_xxxx 가 트리거를 건 테이블의 pk 비교하여 추가된 부분을 insert함

#실제 반영리뷰
테스트기에서 작업은 14분정도 소요되었지만 실제 상용반영은 5분 29초였고
차이는 리눅스 OS와 mysql 버전은 5.7.19로 같은데, DISK가 테스트기는 SATA2이고 상용기는 SSD이다.
SSD가 3배정도 빠른 것을 알 수 있다..ㅎㅎ

2-5) pk 비교는 Left outer join 으로 하여 TB_xxxx가 null 인 것을 찾는 방식으로 작업하였고
수행시간은 30초정도 걸렸다 1000만건이 넘더라도 Pk의 b-tree 인덱스 성능은 강력하다는 것을 알 수 있다.
작업 끝~

 

#Thereafter

mysql 8.0.12 버전부터(MairDB 10.3.7 부터) Game changer가 등장했다.

상용버전을 mysql 8(=5.8) 로 올려야할 강력한 이유가 생겼다.

그것은 algorithm = instant 인데 Online DDL 을 지원한다.

DBA에게 축복과도 같은 기능으로 추후 Data dictionary 좀 더 공부하고 정리해서 올릴 예정..

반응형

'RDB > mysql' 카테고리의 다른 글

mysql을 시작하기 전에 1  (0) 2020.08.04
mysql swap 메모리 사용 초기화  (0) 2020.07.22
mysql order by 정렬 빼도 될까? 2  (0) 2020.06.26
mysql order by 정렬 빼도 될까? 1  (0) 2020.06.19
MODIFY vs CHANGE 차이  (0) 2020.05.27
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,
반응형

정렬인덱스를 정리하던 중에 흥미로은 블로그의 글을 보고 한번더 확인해보기로 했다.
http://www.gurubee.net/lecture/2260

--> 내용을 요약하자면 복합인덱스를 선언하면 order by을 쓰지 않더라도 ASC정렬이 가능하다는 것이다.

 

일단 복합인덱스 (A,B,C)의 정렬 인덱스를 사용할 수 있는 경우의 수를 알아본다.

--> 기본적으로 인덱스는 equal 조건만 사용가능하고 like 검색시 value% 은 가능하지만 %value은 불가하다.

 

1) 인덱스 사용가능한 경우
#1-1) 정렬만 사용한경우
order by a,b
order by a,b,c
--> Using index condition

 

#1-2) where + 정렬 사용한 경우
where a = 1
order by b,c
--> Using index condition

where a = 1
and b = 2
order by c
--> Using index condition

 

# Using index condition란?
WHERE 절의 인덱스를 이용한 조건에서 체크 조건이 있을 경우 체크 조건 처리를 스토리지 엔진이 하도록 전달하는 것이다.


2) 인덱스 사용 불가능한 경우

#2-1) 정렬만 사용한경우
#a를 뺀 경우
order by b,c
-->복합인덱스는 첫번째 컬럼이 선언되지 않으면 사용x

 

#순서대로 아닐때
order by a,c --> Using where; Using temporary; Using filesort 정렬인덱스 사용x
order by a,b,c,d --> Using where; Using temporary; Using filesort 정렬인덱스 사용x

 

# desc 정렬사용할 때
order by a,b,c desc --> Using where; Using temporary; Using filesort 정렬인덱스 사용x

 

#2-2) where + 정렬 사용한 경우

where a = 1
order by a,c
--> a가 포함되어있으면 Using index condition; Using filesort

where절에 a만 인덱스 쓰는 거고 order by는 인덱스를 사용하지 못하고 재정렬하기 위해 filesort를 쓴다.

 

a가 없으면 Using where; Using temporary; Using filesort 정렬인덱스 사용x

 

#테스트

select
T1.SEQ_NO
,T2.APLY_DV_CD
,T1.STD_NO
,T1.APLY_DATE
,T1.REG_DATE
from TESTDB.TB_TSET_20200626 T1
inner join TESTDB.TB_TSET_20200626_TEL T2
ON T1.SEQ_NO =T2.SEQ_NO
where T1.STTS_CD ='93'
order by T1.STTS_CD,T1.APLY_DATE,T1.REG_DATE
;

 

 

#인덱스 생성전

id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE T2 ALL SEQ_NO       110865 100 Using where; Using temporary; Using filesort
1 SIMPLE T1 eq_ref PRIMARY PRIMARY 92 TESTDB.T2.SEQ_NO 1 10 Using where

 

#정렬을 위해 인덱스를 추가한다.

alter table TESTDB.TB_TSET_20200626
add index IDX_TSET_20200626_12 (STTS_CD,APLY_DATE,REG_DATE);

 

 

 

#인덱스 생성후

id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE T1 ref PRIMARY,IDX_TEST_20200626_12 IDX_TEST_20200626_12 8 const 7668 100 Using index condition
1 SIMPLE T2 ref SEQ_NO SEQ_NO 93 TESTDB.T1.SEQ_NO 1 100  

 

 

#정렬조건 변경
order by T1.STTS_CD,T1.APLY_DATE,T1.REG_DATE -->order by T1.APLY_DATE,T1.REG_DATE

where 절에 STTS_CD쓰고 order by 절에서 APLY_DATE, REG_DATE 만 선언해도 정렬이 가능한지 플랜과 실제데이터를 확인한다.

 

id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE T1 ref PRIMARY,IDX_TEST_20200626_12 IDX_TEST_20200626_12 8 const 7668 100 Using index condition
1 SIMPLE T2 ref SEQ_NO SEQ_NO 93 TESTDB.T1.SEQ_NO 1 100  

플랜 같고, 데이터 정렬도 같다.

 

 

#이제 블로그에서 얘기한대로 order by 자체를 빼고 STTS_CD만 where절에 걸었을 때도 정렬이 되는지 확인해본다.

select
T1.SEQ_NO
,T2.APLY_DV_CD
,T1.STD_NO
,T1.APLY_DATE
,T1.REG_DATE
from TESTDB.TB_TSET_20200626 T1
inner join TESTDB.TB_TSET_20200626_TEL T2
ON T1.SEQ_NO =T2.SEQ_NO
where T1.STTS_CD ='93'
#order by T1.APLY_DATE,T1.REG_DATE
;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE T1 ref PRIMARY,IDX_TEST_20200626_12 IDX_TEST_20200626_12 8 const 7668 100  
1 SIMPLE T2 ref SEQ_NO SEQ_NO 93 TESTDB.T1.SEQ_NO 1 100  

일단 using index condition 없다. 하지만 IDX_TEST_20200626_12 인덱스를 썼다고는 한다. 데이터를 확인해본다.


흥미롭다. 실제로 order by를 쓰지 않았지만 데이터 정렬은 order by를 사용한 것과 동일하다.
이것이 가능한 이유는 IDX_TEST_20200626_12 인덱스를 생성할 때 STTS_CD,APLY_DATE,REG_DATE 순서대로 ASC 정렬을 해서 인덱스를 가지고 있고
STTS_CD만 사용하더라도 정렬된 인덱스의 값을 가져오기 때문에 나머지 컬럼은 선언하지 않더라도 정렬된 데이터를 확인할 수 있었다.

order by 정렬을 뺄 수 있다니..

 

 

정렬 인덱스는 뺄 수 있는 것을 확인했다. 이번엔 좀 다른 것을 테스트 해본다.

select
T1.SEQ_NO
,T2.APLY_DV_CD
,T1.STD_NO
,T1.APLY_DATE
,T1.REG_DATE
from TESTDB.TB_TSET_20200626 T1
inner join TESTDB.TB_TSET_20200626_TEL T2
ON T1.SEQ_NO =T2.SEQ_NO
where T1.STTS_CD ='4'
order by T1.APLY_DATE,T1.REG_DATE
;

# 정렬조건을 바꿔 T1.STTS_CD ='4'로 테스트한다.

id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE T2 ALL SEQ_NO       110865 100 Using where; Using temporary; Using filesort
1 SIMPLE T1 eq_ref PRIMARY,IDX_TEST_20200626_12 PRIMARY 92 TESTDB.T2.SEQ_NO 1 50 Using where

 

 

인덱스 있는데.. 안탄다. temporary에도 모자라서 filesort까지 쓴다.  수행시간도 2.9초나 걸린다.
옵티마이져가 멍청해졌나? 강제로 인덱스를 태워본다.

select
T1.SEQ_NO
,T2.APLY_DV_CD
,T1.STD_NO
,T1.APLY_DATE
,T1.REG_DATE
from TESTDB.TB_TSET_20200626 T1 force index (IDX_TEST_20200626_12)
inner join TESTDB.TB_TSET_20200626_TEL T2
ON T1.SEQ_NO =T2.SEQ_NO
where T1.STTS_CD ='4'
order by T1.APLY_DATE,T1.REG_DATE
;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE T1 ref IDX_TEST_20200626_12 IDX_TEST_20200626_12 8 const 213603 100 Using index condition
1 SIMPLE T2 ref SEQ_NO SEQ_NO 93 TESTDB.T1.SEQ_NO 1 100  

인덱스는 타지만 row수를 보니 더 최악이다...  수행시간도 5초나 걸리고 옵티마이져는 나보다 똑똑했다.;;

 

 

#왜이런 결과가 나올까?

select
count(*)
from TESTDB.TB_TSET_20200626
where T1.STTS_CD ='4';
// 전체 row 439,947 건중에 418,328 가 STTS_CD = 4 이다. 
// 분포도 95% 
// 이러면 옵티마이져가 풀스캔이 유리하다고 판단하고 인덱스를 당연히 안쓴다.

 

 

#앞선 조건은 왜 인덱스스캔이 가능했을까?

select
count(*)
from TESTDB.TB_TSET_20200626
where T1.STTS_CD ='93';
//확인해보니 전체 row수 439,947건중에 STTS_CD = 93 은 4262건이다. 
//분포도 0.97% 

인덱스를 설정할 때 데이터 분포도의 중요성이 여기서 다시 한번 확인된다.
복합인덱스를 선언하더라도 첫번째 컬럼의 분포도가 낮다면 order by 정렬인덱스를 제대로 쓰지 못할 수 있고
인덱스 무효화 혹은 강제로 인덱스를 태우면 풀스캔보다 더 느려지는 현상이 발생한다는 것을 확인했다.
끝~

 

반응형
블로그 이미지

dung beetle

취미는 데이터 수집 직업은 MYSQL과 함께 일하는 DBA의 소소한 일상 이야기

,