반응형

리눅스에서 어떤 프로세스들이 CPU와 메모리를 얼마나 쓰고 있는지 알아보기 위해서는


top 


이라는 명령어를 사용하곤 했습니다. 사실 이와 관련된 포스팅도 한 번 한적있고요 (http://ngee.tistory.com/76)


아래 그림은 리눅스 서버에서 top를 실행한 화면이구요.



근데, 이 top 명령어는 기본적으로 제공되는 것이구요.


단점이라기 보다는, top 보다 CPU와 메모리 점유율에 대해서 상세하고, 보기 좋게 출력해주는 것이 있습니다.


htop 이라는 것인데요. 이것은 apt-get 을 통해서 설치하셔야 합니다.


apt-get install -y htop


실행은 htop을 입력하시면 되구요.


아래 그림이 htop의 실행결과화면 입니다.



잘 보시면, cpu 코어별로 얼마나 사용되고 있는지 보여주고 있습니다.


또한 top과 다르게 폰트색이 적절하게 적용되어 더 깔끔하구요.

 

반응형
LIST
반응형

MySQL에서 대용량 테이블의 경우 성능 개선을 위한 10 가지 방안

대용량 테이블을 운용하는 경우 도움이 될만한 아티클 "Ten ways to improve the performance of large tables in MySQL"이 있어, 이 블로그에 정리해 봅니다.
아래 가지 10가지 방법을 잘 고민해 보면 좋은 도움을 받을 수 있을 것 같습니다.

오늘은 성능 문제를 일으키는 원인이 되는 대용량 테이블의 성능을 개선하는 방법을 살펴본다. 여기의 조언들은 일부는 많은 테이블을 가진 큰 데이터베이스에 적용할 수 있지만 대부분의 경우는 개별적으로 특별히 큰 테이블이 더 큰 문제라는 것이다.

테이블의 내용 변경의 속도는 테이블 사이즈가 커질수록 줄어드는 것을 일반적으로 알려진 사실이다. 아래는 B+Tree 인덱스의 성능을 시계열로 보여주는 그래프이다.



위의 그래프는 MySQL@Facebook에서 포스팅한 것이다. 이것은 insert buffer를 해제한 상태(물론 추천 하지는 않고, 데모 목적)에서 테이블에 10억 행을 insert했을 때의 성능 저하를 나타낸 것이다. 이 벤치 마크는 iibench 도구를 사용했고, TokuTek에서 디자인한 것이다.

그래서 우리는 이러한 성능 저하가 인덱스 구조에 의한 것이라고 생각했지만, 그래도 우리는 이 곡선을 급격하게 내려가지 않고, 옆으로 길게 늘어지도록 할 수 있는 방법을 모색해 봐야 한다.

대용량 테이블의 성능에 영향을 줄일 수 있는 10가지 방법

1. MyISAM 대신에 InnoDB를 사용하자. MyISAM은 테이블의 마지막에 insert하는 경우에는 속도가 빠르지만, 테이블 잠금(update 및 delete에 제한 되지만)이 있고, 데이터를 디스크에서 읽기와 쓰기를 할 때 경합 때문에 키 버퍼를 보호하기 위해 싱글락을 사용한다. 또한 후술하겠지만, 체인지 버퍼 기능을 가지고 있지 않다.

2. InnoDB는 유니크하지 않은 보조 인덱스의 빌딩을 지연시키는 체인지 버퍼 기능(이전에 insert buffer라고 불리던 기능)을 가지고 있다. 이에 대한 자세한 것은 Facebook의 노트에 기술되어 있다. 이것은 위의 그래프에는 보여지지 않지만, insert의 성능을 상당히 빠르게하는 것으로, 기본적으로 활성화되어 있다. 이 기능은 MySQL 5.5에서 좋게 개선 되었기 때문에, 만약 업그레이드 하지 않는 경우에는 즉시하는 것이 좋다.

3. 파티셔닝은 인덱스의 크기를 작게하여 테이블 자체를 효율적으로 작게 나눌 수 있게 된다. 또한, MySQL 5.7.2 DMR에서 상당히 개선된 내부적인 인덱스 잠금(index->lock) 경합(contention)도 줄여 준다.

4. InnoDB의 압축 기능을 사용하자. 몇몇 부하 종류의(특별히 많은 char/varchar/text형 컬럼이있는 경우) 압축 기능은 데이터를 압축해 성능 저하의 곡선을 완만하게 해준다. 또한, 일반적으로 용량이 작은 SSD를 사용해도 된다. InnoDB의 압축 기능은 Facebook에서 제공한 여러가지 패치 덕택에 MySQL 5.6에서는 크게 개선 되었다.

5. 정렬후 대용량의 데이터를 테이블에 로드해라. 정렬된 데이터를 인서트하는 것은, 페이지 분할(메모리 상에 없는 테이블에서 성능은 악화되는)이 작게 될 것이고, 대용량 데이터의 로드는 테이블의 용량과는 특별히 관계가 없지만, redo 로그의 압축 부하를 줄여주는데 도움을 준다.

6. 테이블에서 불필요한 인덱스를 지우자. 체인지 버퍼 기능을 비활성화시키는 UNIQUE 키를 특히 주의하자. 제약 조건을 사용할 이유가 없는 경우, UNIQUE 키를 사용하지 않고 일반적인 INDEX를 사용하자.

7. 5, 6에서 관련된 PRIMARY KEY의 종류도 중요하다. 성능 저하를 빠르게 만들어버리는 GUID와 같은 데이터 타입보다, INT나 BIGINT를 사용하자. PRIMERY KEY가 없는 것도 성능에 부정적인 영향을 준다.

8. 새 테이블에 대용량 데이터를 로드할 경우 PRIMARY KEY가 아닌 인덱스는 나중에 만들자. 모든 데이터가 로드된 후 인덱스를 만든다면, InnoDB는 pre-sort와 및 대용량 로드 프로세스(빠르고 인덱스가 좀 더 콤팩트한 인덱스를 만드는)를 적용 할 수 있게 된다. 이 최적화는 MySQL 5.5에서 이루어졌다.

9. 메모리가 많으면 많을수록 도움을 받을 수 있다. 최근의 메모리의 실제 가격을 비교해 보면 새로운 데이터 베이스 서버에 너무 적은 메모리를 적용하는 것을 자주 볼 수 있다. 간단한 조언을 해 보면, SHOW ENGINE INNODB STATUS의 결과에서 BUFFER POOL AND MEMORY의 reads/s의 보여주고(읽고 있음을 나타냄), Free buffers(이것도 BUFFER POOL AND MEMORY 아래에 있다)의 수가 0이면 메모리를 더 늘리면 혜택이 얻을 수 있다.(innodb_buffer_pool_size를 잘 최적화했다는 가정하에. 이 문서를 참고).

10. 메모리 뿐만 아니라, SSD도 도움이 된다. 그래프의 곡선이 하향이 되는 이유는 테이블이 커져서 일어나는 IO 속성 때문이다. 하드 디스크가 초당 200 오퍼레이션(IOPS)을 수행하는데 반해, 일반적인 SSD는 20000 IOPS 이상 수행이 가능하다.

용어 정의

  • Change Buffer: MySQL 5.5이전에는 insert buffer라고 했으며, 이름 그대로 INSERT의 성능을 향상시키기 위한 버퍼다. insert buffer는 새로운 행을 삽입하는 INSERT에 대해서만 유효했는데, Change Buffer는 UPDATE 및 DELETE도 좋은 효율성을 보여준다.

 

반응형
LIST
반응형

원본: http://gywn.net/2011/12/mysql-three-features/

반드시 알아야할 MySQL 특징 세 가지

Overview

MySQL 요구가 전보다 급증하고 있습니다. 이제 친숙해서 사용하는 간단한 소용량 DBMS 이 아닌, 많은 대형 업체에서도 사용되고 있기 때문에 많은 이슈가 되고 있습니다. 트위터, 페이스북, 구글, 야후 뿐만 아니라 최근들어 SNS 열풍으로 국내에도 MySQL 관련하여 엄청난 붐이 시작되려는 찰나인 듯 하네요.

대형 인터넷 서비스

KTH에 입사를 한 당시에 주력 DB는 Oracle이었습니다. 그러나 라이선스 비용 문제로 주력 DB 선정을 위한 저울질이 시작되었고, 그중 일반 개발자에게도 친숙한 MySQL을 선택했습니다.

MySQL 세가지 특성?

MySQL 3.X 버전으로 광고 시스템을 만든 적이 있습니다. 꽤나 오래된 얘기..
지금 생각하면 당시 광고 시스템에서 DB에 날리는 쿼리는 간단하기는 했지만, 상당한 트래픽을 무난히 견디는 것을 보고 감탄을 금치 않았습니다. 와~! 이거 물건인데? 제 첫 사용 소감이었습니다.하지만 “과연 MySQL에 대한 중요한 특성을 잘 알고 있었을까?” 라는 생각이 들었습니다.그때부터였다. MySQL이라는 녀석과 진지한 악수를 한번 해보고 싶다는 생각을 한 것이.. 그래서 여기저기 해외 사례도 기웃거리고, 서적도 진지하게 읽기 시작했죠. 읽으면서 틈틈이 벤치마킹도 수행해보고 나름의 지식 베이스를 늘려갔습니다.
  • 단일 코어에서 Nested Loop Join 처리
  • 다양한 스토리지 엔진
  • 데이터 복제(Replication) 기능

1) 단일 코어에서 Nested Loop Join 처리

MySQL에서는 모든 SQL 처리를 단일 코어에서 Nested Loop Join 방식으로만 데이터를 처리합니다. 병렬 처리라는 것은 없습니다. 물론 일부 3rd스토리지 엔진을 플러그인으로 설치를 하면 병렬 처리가 가능하다고는 하지만, 기본적인 스토리지 엔진에는 단일 코어 수행합니다. 그렇기 때문에 MySQL 입장에서는 CPU코어 개수를 늘리는 Scale-Out보다는 오히려 단위 처리량이 좋은 CPU로 Scale-Up을 하는 것이 훨씬 유리합니다.
MySQL Single Core Processing

MySQL Single Core Processing

게다가 모든 데이터 처리를 Nested Loop Join(이하 NL join)으로 처리합니다. NL join이란 선행 테이블(A)의 조건 검색 결과 값 하나하나를 엑세스 하면서 연결할 테이블(B)에 대입하여 조인하는 방식입니다. 프로그램적으로 풀자면 2 중 While문과 유사하다고 볼 수 있습니다. 처리할 데이터가 적으면 수행 속도가 빠르지만, A테이블 또는 B테이블 중 하나라도 연산을 해야할 데이터가 많아지만 쿼리 효율이 기하급수적으로 떨어집니다.
Nested Loop Join in MySQL
출처 : http://dev.mysql.com/doc/refman/5.5/en/nested-loop-joins.html
Outer Join이든, Sub Query든, Inner Join이든 모두 NL-Join으로만 처리합니다. DW 또는 데이터 분석 도구보다는 단순 처리를 위한 OLTP 프로세싱에 적합한 DBMS입니다.

2) 다양한 스토리지 엔진

MySQL은 다양한 스토리지 엔진을 지원합니다. MyISAM, InnoDB, Archive, Memory, NDB, Federated 등 기본 엔진 뿐만 아니라 3rd Party 스토리지 엔진도 간단하게 플러그인 형식으로 설치를 할 수 있습니다.
제가 자주 사용하는 스토리지 엔진 세 가지(MyISAM, InnoDB, Archive) 세 가지에 관한 간단한 비교 표입니다.
MySQL Storage Engines

MySQL Storage Engines 비교

  • MyISAM Storage Engine
    인덱스만 메모리에 올려서 테이블 잠금으로 데이터를 처리하는 스토리지 엔진으로 단순 백그라운드에서 로그 수집에 적합합니다. 동시 다발적으로 데이터 변경 작업을 수행하는 로직에는 절대 적합하지 않습니다. 특히 인덱스가 걸려있는 상태에서 대용량(500만 건 이상) 데이터 처리 시 단순 입력 상태에서도 테이블 잠금이 빈번하게 발생할 수 있다는 점을 명심하시기 바랍니다.
  • Archive Storage Engine
    원시 로그 수집에 최적인 스토리지 엔진입니다. 트랜잭션, 인덱스 모두 지원하지 않지만, 테이블에서 데이터 처리를 행 단위 잠금으로 수행하기 때문에 동시 다발적으로 데이터 입력 상황에도 상당히 좋은 퍼포먼스를 제공합니다. 게다가 메모리에서 데이터 압축을 수행하면서 실제적으로 디스크에 기록하기 때문에, InnoDB 대비 꽤 좋은 디스크 용량 효율이 있습니다. 게다가 파티셔닝을 지원하기 때문에 추후 로그 정리 시에도 상당히 간단하죠.
  • InnoDB Storage Engine
    현재 Oracle에서 가장 밀고 있는 스토리지 엔진으로 In-Memory 특성을 가지고 있습니다. 메모리에 인덱스/데이터 모두 올려서 데이터를 처리하기 때문에 데이터 접근 속도가 상당히 빠릅니다. 메모리가 많이 허용되면 엄청난 퍼포먼스를 발휘하는 엔진이죠. 게다가 트랜잭션을 제공하고, 동시 데이터 처리 시에도 행 단위 잠금으로 처리하기 때문에 실제적으로 InnoDB Storage Engine은 OLTP 성 대용량 처리에 가장 적합한 스토리지 엔진이라고 볼 수 있습니다.

위 세가지 엔진을 능숙하게 사용할 수만 있다면 서버 효율성을 상당 수준 이끌어 낼 수 있다고 생각하는데요. 그래서 간단하게 선정 기준을 다음과 같이 만들어 보았습니다.(지극히 개인적인 생각입니다.)

스토리지 엔진 선정 기준

스토리지 엔진 선정 기준

3) 데이터 복제(Replication) 기능

MySQL은 물리적으로 독립적인 디스크 영역에 데이터를 복제(Replication)하여 데이터를 이중화할 수 있습니다.  하단 그림은 가장 대표적인 MySQL Replication Master-Slave구조를 나타낸 그림입니다. 마스터에서 데이터 변경이 작업이 일어나면, 해당 내역이 자동으로 슬레이브로 비동기적으로 전송되어 실제 마스터의 데이터 복사본을 유지하는 것을 의미합니다.
MySQL Replication 서버 구성

MySQL Replication 서버 구성

특정 데이터 디스크 Fail시에도 다른 물리적으로 독립적인 디스크에 데이터가 존재하기 때문에, DB 버그가 아니라면 유실은 거의 없습니다. 게다가 디스크 읽기 분산이 가능하기 때문에 읽기 트래픽이 상당히 큰 서비스에 큰 효율을 갖습니다. 대신 데이터 반영은 오직 하나의 노드, 마스터에서만 가능하기 때문에 쓰기 관련된 부하 분산이 불가능합니다. 그리고 마스터 자체 장애는 쓰기 관련 전체 장애로 발생한다는 점도 꼭 기억하세요.
반응형
LIST
반응형

원본: http://gywn.net/2012/05/mysql-bad-sql-type/

MySQL 성능 죽이는 잘못된 쿼리 습관 

Overview

안정적인 서비스 유지를 위해서는 쿼리 작성이 상당히 중요합니다. 잘못된 쿼리 하나가 전체적인 퍼포먼스를 크게 저해하기도 하고 최악의 경우 장애 상황까지 치닫기 때문이죠

단일 코어에서 Nested Loop Join으로 데이터를 처리하는 MySQL 특성 상 쿼리 구문에 큰 영향을 받습니다. (반드시 알아야할 MySQL 특징 세 가지 참고)

그래서 오늘은 쿼리 작성 시 기피해야 하는 사항 세 가지정도 골라봅니다.

Case 1

SELECT @RNUM:=@RNUM+1 AS RNUM, ROW.*
FROM (SELECT @RNUM:=0) R,
(
    SELECT
        M.MASTER_NO,
        M.TITLE,
        MI.PATH,
        M.REGDATE,
        CM.TYPE
    FROM MAIN AS M
    LEFT OUTER JOIN TAB01 AS MI
        ON M.MASTER_NO = MI.MASTER_NO
    INNER JOIN TAB02      AS CM
        ON M.MASTER_NO = CM.MASTER_NO
    WHERE M.DEL_YN = 'N'
    ORDER BY M.MASTER_NO DESC
) ROW
LIMIT 10000, 10

SQL Plan Case1-1

오라클 쿼리에 익숙하신 분들이 흔히 하는 실수입니다.

오라클 rownum 효과를 내기 위해 (SELECT @RNUM:=0) 로 번호를 붙이다 보니 결과적으로 필요없는 데이터를 스캔합니다. Nest Loop Join으로 데이터를 처리하기 때문에 퍼포먼스가 상당히 떨어집니다.

Row 번호는 어플리케이션 서버에서 생성하고, 다음과 같이 쿼리를 작성하는 것이 좋습니다.

SELECT
    M.MASTER_NO,
    M.TITLE,
    MI.PATH,
    M.REGDATE,
    CM.TYPE
FROM MAIN AS M
LEFT OUTER JOIN TAB01 AS MI ON M.MASTER_NO = MI.MASTER_NO
INNER JOIN TAB02      AS CM ON M.MASTER_NO = CM.MASTER_NO
WHERE M.DEL_YN = 'N'
ORDER BY M.MASTER_NO DESC
LIMIT 10000, 10

SQL Plan Case1-2

변환 전/후 쿼리 프로파일링을 해보면 다음과 같습니다. 변환 후에 필요없는 데이터 스캔에 소요되던 Sending Data가 사라지고, 단순하게 처리됩니다.

SQL Profile Case1

Case 2

Where 조건 Left Value에 함수 적용하여 결과적으로 Full Scan이 발생하는 경우입니다. 서비스 구현 단계에서는 쉽고 직관적으로 보일지는 몰라도, DB 내부 데이터 처리에서 엄청난 자원을 소모합니다.

이런 습관은 DBMS 상관없이 기피해야 합니다.

SELECT *
FROM VIEW_MASTER_LOG_GROUP TAB01
WHERE DATE_FORMAT(ST_LAST_DATE, '%Y-%m-%d') LIKE DATE_FORMAT(NOW(), '%Y-%m-%d');

SQL Plan Case2-1

Where 조건 날짜 검색 로직을 살펴보면 결과적으로 오늘 0시 이후 데이터를 가져오는 구문입니다. 그렇다면 다음과 같이 변환해 봅시다.

인덱스를 타게 Left Value에서 불필요한 Function을 제거하고, Between으로 0시 이후 데이터를 가져옵니다.

SELECT *
FROM VIEW_MASTER_LOG_GROUP TAB01
WHERE ST_LAST_DATE BETWEEN DATE_FORMAT(NOW(), '%Y-%m-%d') AND NOW();

SQL Plan Case2-2

Full Scan이 아닌 Range Scan이며 정상적으로 인덱스를 탑니다.

Case 3

데이터 추가 조회를 위한 Outer Join 사용 시 주의할 점입니다. 바로 위 1차 변환된 쿼리를 기준으로 말씀 드리겠습니다.

하단 쿼리는 Outer Join이 조건 검색에 영향을 미치지 않고 추가 정보 조회만을 위한 역할로 사용될 때 입니다.

SELECT
    M.MASTER_NO,
    M.TITLE,
    MI.PATH,
    M.REGDATE,
    CM.TYPE
FROM MAIN AS M
INNER JOIN TAB01 AS CM
    ON CM.MASTER_NO = M.MASTER_NO
LEFT OUTER JOIN TAB02 AS MI
    ON M.MASTER_NO = MI.MASTER_NO
WHERE M.DEL_YN = 'N'
ORDER BY M.MASTER_NO DESC
LIMIT 10000, 10;

SQL Plan Case3-1

데이터를 10,000번째 위치부터 10 건을 가져온다면 결과적으로 불필요한 10000번 Outer Join이 발생합니다. 쿼리 성능이 상당이 안좋습니다.

물론 데이터가 적을 경우에는 큰 문제가 없지만, 데이터가 누적됨에 따라 서버에 큰 영향을 미칠 수 있습니다. 아래와 같이 수정을 해보죠.

SELECT
    A.MASTER_NO,
    A.TITLE,
    MI.PATH,
    A.REGDATE,
    A.TYPE
FROM(
    SELECT
        M.MASTER_NO,
        M.TITLE,
        M.REGDATE,
        CM.TYPE
    FROM MAIN AS M
    INNER JOIN TAB01 AS CM
        ON CM.MASTER_NO = M.MASTER_NO
    ORDER BY M.MASTER_NO DESC
    LIMIT 10000, 10
) A
LEFT OUTER JOIN TAB02 AS MI
    ON A.MASTER_NO = MI.MASTER_NO;

SQL Plan Case3-2

SQL Plan 정보는 더 안좋은 것처럼 보이지만, SQL을 프로파일링 해보면 다음과 같이 좋은 성능을 확인할 수 있습니다.

SQL Profile Case3

변환 후 프로파일은 더욱 길어지기는 했지만, Outer Join을 위한 Sending Data 시간만큼 단축되었습니다.

Conclusion

3가지 간단한 사례이기는 하지만, SQL 튜닝 시 확인을 해보면 종종 걸리는 문제들입니다. 쿼리 특성에 따라 성능이 좌우되는 만큼 SQL도 서비스 로직을 정확히 파악하여 작성한다면 서버 자원을 효율적으로 배분할 수 있겠죠.

잊지 마세요. MySQL에서는 단일 코어에서 Nested Loop Join 방식으로 데이터를 처리한다는 사실을...

 

반응형
LIST

+ Recent posts