기본 콘텐츠로 건너뛰기

Lock정보가 없지만 대기타고 있는 쿼리를 찾아라! SQL Server




얼마전에 며칠을 고생해서 원인 쿼리를 찾은 이야기 입니다. 

보통 쿼리는 Lock에 걸려서 대기 상태에 있다면 쉽게 찾는데요.. 
Lock에 걸리지 않은 상태로 대기를 타는 경우가 있습니다. 

Sql server 에서 일단 쉽게 찾는 방법은
Sp_who2 를 실행하는 것이죠. 

그냥 SSMS에서 서버에 접속해서 새 쿼리창을 열고 
Sp_who2 라고 입력하고 
실행해버리면

현재 물려있는 모든 세션의 정보가 나타납니다. 
여기서 Blkby 라는 필드에서 숫자가 보이면
그 숫자의 SPID가 원인으로 
숫자가 쓰여진 곳의 SPID가 멈춰 있다는 이야기 입니다. 

이렇게 원인 세션을 찾고
그 세션이 어떤 쿼리를 던지고 있는지를 찾아서 
Kill을 할지, 다른 어떤 액션을 취할지를 택하면 됩니다. 

Kill을 하고 싶으면
Kill 999 처럼 SPID를 kill 뒤에 적어주면 되구요.. 
Blkby에 적힌 숫자를 입력하면 원인 쿼리가 강제 종료 되는데요, 

이게 INSERT나 UPDATE의 경우가 많아서 
데이터 유실이 될 가능성이 높기 때문에 
저 같은 경우는 원인 쿼리의 문제를 해결할 방법만 알려주고 
자연히 사라질 때 까지 기다리기는 합니다. 

그래도 급히 대응이 필요하거나 
아무리 기다려도 사라지지 않는 경우에는 
Kill밖에 없겠죠. 

지난 번에는 개발팀에서 
Begin tran을 던지고 쿼리를 던진 뒤에
Commit이나 rollback을 하지 않아서 
Blkby가 보여서 문의했더니 
자신들의 실수임을 확인해서 쉽게 끝났지만,

이번에는 Blkby가 뜨지 않은채로 
많은 세션들이 SUSPEND가 되어버렸죠. 

보통 커넥션 풀을 사용한다 하더라도 
SUSPEND가 되는 경우가 없고 
SOCKET이라면 연결 상태로 정지되어 
SUSPEND가 되기도 하지만, 

기본적으로 SUSPEND는 뭔가 이상하다고 보시는게 좋습니다. 
무언가의 문제로 인해 타임아웃이 발생하거나 하면 
SUSPEND가 되는 경우가 많거든요.. 
일반적인 상태라면 SUSPEND가 없는게 보통입니다.

이번에도 Blkby가 비어있는데도 SUSPEND 된 세션이 많았는데요 
기존 DB담당자는 ai에 던져보니 별 문제가 없었다더라 라고 이야기를 하는데, 
ai의 의견을 전적으로 신뢰하면 안되는 이유 중 하나이죠.

Ai는 SUSPEND를 그냥 세션이 물려 있다 끊긴 것 정도로 대수롭게 판단하지 않는데요.. 
SUSPEND가 없는게 보통인데 SUSPEND가 늘어났다면
어디선가 발목을 잡는 놈이 있구나 생각을 하셔야 합니다. 

여기서 문제가 있다는 랭킹 페이지를 열어 봤는데 
504에러가 나고 있었더라구요.. 

504에러는 웹서버의 반응이 늦어져서 중간에 timeout이 되어 버리면
나타나는 현상입니다. 

원인은 네트워크 문제일 수도 있고, 웹서버의 리소스가 바닥날 수도 있고, 
DB의 반응이 늦어져도 발생할 수 있죠. 

네트워크나 서버의 이슈는 없다고 하는데, 
DB에서도 CPU는 가끔 100%는 치지만 그것도 한 순간이고 
DB서버에서 memory나 네트워크의 부하도 없는 상태입니다. 

한 순간 100% 되는데
504에러는 오전 9시 부터 13시 정도에 이어져서 발생한다고 합니다. 
순간 100%로 4시간이 504에러가 뜬다는게 말이 안되죠. 

그래서 Profiler를 연결해서 계속 떠 있는 SQL을 보다가 
무언가 찾아냈습니다. 

특정 쿼리가 where in (select product_id from #temp_table )
으로 되어 있는데, 
이 쿼리가 Logical Read가 5400만건인거죠.. 
즉, where in 안에 있는 템프테이블이 엄청나게 많았다는 얘기겠죠. 

제가 쿼리 튜닝할 떄 이야기 하는 것 중 하나가 
Where in 안에 들어가는 개수는 1000개 미만으로 해라.. 
뭐 스펙별로 차이는 있겠지만, 
Where in 에 들어가는 데이터는 모두 
Or로 연산하기 때문에 index를 타지 않습니다. 

요런 식으로 템프 테이블을 만들어서 
Where in 으로 넣을 때는 실제 데이터를 꼭 체크하시기 바랍니다. 

그리고 또 별 건 이지만, 
여기서도 랭킹 페이지를 실시간으로 가져와야 한다면서 
각종 랭킹 쿼리를 가져오다보니 70번 넘는 sql을 한 페이지에서 던지는데요.. 
그나마 하나의 커넥션 풀에서 던지므로 그나마 낫긴 하지만, 
이런 여러번의 쿼리를 던지는 페이지는 
절대 처리 요구수 때문에 문제가 발생합니다. 

때문에 랭킹 페이지를 한 번씩 캐시메모리나 캐시 파일로 던져서 
1분에 한 번씩 갱신하든 하는 식으로 해야지, 
모든 유저를 70번씩 쿼리를 던지게 하면 
서비스가 뻗겠죠.. 

게다가 랭킹 쿼리들도 가상 테이블을 작성해서 
랭킹으로 만들고 있는데요.. 

Rank over라는 쿼리를 사용하기 위해서 
필요한 데이터를 템프테이블로 가져오는 것은 이해는 하는데, 

이번에 발생한 문제가 바로 여기에서 생긴 거죠. 

위쪽의 where in 에 넣을 템프테이블을 만드는데 5500만행을 만들다보니
그에 필요한 IO가 대량으로 발생을 하고 
템프테이블을 생성하는 동안 
정합성 때문에 다른 커넥션에서 시도하려는 템프테이블이 대기를 타게 되었습니다. 

이건 템프테이블 작성을 위한 대기이기 때문에
쿼리 대기가 아니라서 
Lock에는 표시가 안되는 것이지요. 

때문에 지금까지 어느 누구도 
Zabbix나 new relic을 봐도 부하가 없어 보이고 
Disk io는 신경도 안쓰고 
다른 쿼리들은 잘 돌아가는데다가 
70개나 되는 쿼리를 하나하나 체크를 못하다보니 
어디서 문제가 생기는지 찾을 수가 없었던 거 같습니다. 

심지어는 disk io가 늘어나면 
당연히 CPU가 늘어나지 않느냐 라는 인프라 팀의 질문이 있었는데요. 

보통 PC는 디스크 조작을 많이 하면 
CPU가 올라가는 것이 보이죠?
그런데 일부 서버는 disk io가 아무리 늘어나도 
CPU는 변화가 없는게 보일 겁니다. 

같은 SCSI컨트롤러를 사용하더라도 
SATA방식의 디스크를 끼우면
SCSI컨트롤러를 패스하고 CPU가 디스크랑 직접 소통을 하기 때문에
Disk io에 비례해서 CPU도 올라가는데, 
SAS디스크를 끼우면 SCSI컨트롤러랑 디스크가 소통하고 
SCSI컨트롤러는 CPU에서 단순 명령만 받기 때문에
CPU가 올라가지 않게 됩니다. 
요즘은 보통 12~24장 정도의 HDD를 넣는 장비가 많아지므로 
CPU가 직접 HDD랑 소통을 안하는게 좋습니다. 
SSD를 샀는데 SATA라서 IO패턴에 따라서는 
RAID로 구성된 SAS보다 더 느려지는 경우도 있죠. 
물론 SATA SSD를 SCSI컨트롤러 위에 RAID구성하면
SCSI 컨트롤러가 중간에 패리티 계산, 저장 알고리즘
, 미러 등의 처리는 모두 하기 때문에
CPU부하도 줄어들기 떄문에 
SATA를 쓰더라도 SCSI 컨트롤러를 좋은 걸 쓰고 
RAID구성을 제대로 하면 충분히 도움은 됩니다.

즉, 이러한 하드웨어의 깊이 있는 지식이 없으면
왜 CPU가 낮은데도 대기가 생기는지에 대해서 
알 수 없었던 내용이었던 거지요. 

이렇게 문제점을 이야기 해서 
개발팀에게 던졌습니다. 
쿼리를 내가 만들어주는게 아니다보니
이젠 개발팀이 어떻게 수정해서 
가져오는지를 봐야겠지요. 

이번 일을 정리하자면

1. 한 페이지 로드할 때 5번 이상 DB호출 하지 말것. 
만약 그 이상 할거면 뭉쳐서 받은 뒤에 흩어낼 수 있는지, 
또는 HTML화 를 해서 유저는 못느끼게 DB콜을 줄일 것. 

2. Where in 안에 1000개 이상의 데이터를 넣지 말것. 

3. 템프테이블에 넣는 양도 최소한으로 할 것. 

4. 디스크 종류만으로 CPU부하가 달라짐
SAS디스크는 CPU부하가 안올라가지만
SATA는 CPU부하가 올라감

이런 특징을 알고 
비슷한 증상이 나왔을 떄 
도움이 되셨으면 합니다. 


giip :: Control all Robots and Devices! Free inter-RPA orchestration tool! https://giipasp.azurewebsites.net/

댓글

이 블로그의 인기 게시물

일본 두바퀴 여행(바이크 편)

영상버전 : https://youtu.be/P3vC17iVu1I 이번에는 일본으로 넘어와서 일본 종주하시는 바이커들을 위한 정보입니다.  일본에서의 2륜의 정의가 면허와 도로교통법이 조금씩 다르다고 합니다.  그래도 그렇게 크게 신경쓸 건 없으니 딱 세 종류로 말씀 드릴께요.  50cc는 원동기 1종이라고 하여 3차선 이상 교차로에서 우회전, 한국에선 좌회전 같이 크게 도는 것이지요..  이게 불가능합니다.  직진 신호로 넘어간 뒤에 방향을 틀고 다시 직진으로 두번 꺾어 가야 하구요,  두 명이 타면 안됩니다.  그리고 맨 가장자리 길로만 가야해서 애매하게 끝에서 두 번째 차선만 직진인 곳들이 있어서 난감할 때가 있지요. 그런데에 직진하면 걸리는 곳이 있다고 합니다. 어느 정도까지 걸리고 안걸리고는 정확히는 모르지만,  직좌 마크가 아닌 좌회전 마크만 있는 곳이 은근히 많으니 조심해야 하겠더라구요.  최고 시속도 30km를 넘기면 안되어 천천히 달려야 합니다.  아뭏든 제약이 엄청나게 많으므로 60cc이상을 가져오시거나 렌트 하시는 것을 추천하구요,  125cc미만은 겐츠키 2종이라고 하여 두 명이 타도 되고, 3차선 이상에서 우회전이 가능합니다.  상당히 제약이 풀리는 대신 고속도로를 탈 수가 없지요.  만약 국도로 천천히 올라오신다면 125cc미만으로도 충분합니다.  실제로 일본인 바이커들 중에서도 국도 종주하는 모습을 많이 볼 수 있구요,  도심에 가면 125cc미만까지만 주차 가능한 바이크 주차장도 꽤 많기 때문에 도심용으로는 메리트가 큰 것 같습니다.  뭐, 125cc대는 곳에 큰 바이크를 대는 경우도 자주 보는데, 아무도 뭐라 안하긴 합니다.  그도 그럴 것이, 일본의 바이크 등록대수는 1031만대 인데도 바이크 전용 주차장은 턱없이 부족하다고 합니다. 바이크 주차장이 저렴하기 때문에 웬만한 ...

BI의 궁극판! Apache Drill을 써보자!

사실 Apache Drill 은 BI(Business Intelligence)라고 부르는 것 보다는 단순 데이터 연결 엔진이다. https://drill.apache.org/ 하지만 내가 왜 극찬을 하느냐면.. DBA로서 항상 문제가 되어왔던게, 이기종 데이터의 변환이나 처리였다. 포맷을 맞추는데 엄청난 시간이 걸리고, 데이터 임포트 실패가 무수하게 나고.. 한 번 잘못 데이터를 추출하면 다시 조정, 변환, 추출하는데 시간이 많이 걸린다. 그런데! Apache Drill은 그냥 RDB를 CSV랑 연결해서 조인해서 통계를 낼 수 있다. 그것도 표준 SQL을 사용하여! 예를 들어, CSV의 세 번째 컬럼이 price 이고, 물건의 판매이력을 PG사에서 CSV로 출력 받았다. 우리 DB와의 검증을 위해서는 수동으로 Import를 한 뒤에 포맷이 안맞아 잘리는 데이터가 있다면 다시 맞춰주고, 재 임포트를 수십 번, 그리고 나서 겨우 들어간 데이터를 조인하여 빠진 데이터를 분간한다. 숫자가 적다면 개발자가 개발로 처리할 수도 있지만, 건수가 하루에 300만건 짜리라면.. 한 달 온 파일은 9천만 건이다. 프로그램으로 고작 처리하는 것이 초당 500건. 거의 20만초, 에러 없이 약 56시간.. 에러가 생기면 다시 56시간.. ㅠㅡㅠ 이런게 현실이기 때문에 쿼리 말고는 방법이 없다. apache drill 의 진면목을 보자! 이번에는 좀 범용 적인 MySQL DB와 붙여 보자. . 난 이번에는 Mac에서 작업을 했기 때문에 그냥 다운 받아서 풀었음.. https://drill.apache.org/download/ 여기서 자기 OS에 맞는 버전을 받아서 설치하시길.. 압축을 풀고 나면 MySQL 커넥터를 붙여야 한다. https://dev.mysql.com/downloads/connector/j/5.1.html 여기서 다운로드 이런 커넥터 들을 붙일 때마다 콘피그를 수정해 줘야 하지만, 몇 번만...

[Classic ASP] Cookie가 삭제 안되는 문제

만든 쿠키가 삭제가 계속 안되서 여기저기 삽질을 했다. 모든 쿠키를 삭제하는 함수도 만들었다. Function CookieClear(cldomain) For Each cookie in Request.Cookies Response.Cookies(cookie).Domain = "." & cldomain Response.Cookies(cookie).Path = "/" Response.Cookies(cookie).Expires = DateAdd("d",-1,now()) Next End Function 그런데.. 안되서 계속 삽질하다가 하나 알았다.  littleworld.net littleworld.co.kr www.littleworld.net  의 모든 값을 지우려고 했으나.. 처음 만든 쿠키가 www.littleworld.net 인 관계로.. 처음에 www.littleworld.net 의 쿠키를 삭제 해야만 나머지가 지워졌다.. -ㅅ-;; 간단하지만 몰랐던 초보적인 사실.. ---- 누구나 쉽게 광고를! http://www.cashtalk.co.kr Free Infrastructure automation & management tool Global Infrastructure Integration Platform http://giip.littleworld.net Subscribe and publish your links as a book with friends  My Favorite Link Share http://link.littleworld.net