본문 바로가기
엑셈 기업문화/엑셈 사람들

[황종필]기본으로부터의 발상의 전환

by EXEM 2009. 3. 10.

차세대 시스템 프로젝트가 한 달여 남은 클라이언트에 1주일(5일)간 지원을 가게 되었다.

4일째 내가 할 일이 거의 다 마무리 되어가고 있어 안심이 되고 있을 때 한가지 재미있는 이야기를 들었다.

"INSERT문이 60초씩 대기하는 경우가 있어요!
하지만 어떤 경우에 대기하는지, 무엇이 문제인지 모르겠어요! "


난 내심 이렇게 생각했다.

"음! 간단할 것 같기도 한데 뭐가 문제라는 거지?
저 문제를 해결하고 보고서를 쓰면 복귀시간하고 얼추 맞을 것 같네. "

이런 생각에 담당자분께 그 문제를 도와드리겠다고 선뜻 나섰다.

이것이 시련의 시작이라는 것은 생각지도 못하고...

우선 담당자분께서 알려주신 시간대에 MaxGague 로그를 찾아 보았다.

"음. 몇시간을 찾아 헤매었지만 문제의 세션을 찾을 수가 없네.
 어떻게 찾아야 할까?
 알려준 시간대가 정확하다고는 하지만 정말 그런지 알 수가 없는데...
 아! 현재 MaxGauge 로그는 ORACLE LITE로 되어 있어 SQL을 수행하여 내가 원하는 정보를 찾아 볼 수가 있지!
 SQL을 작성하여 INSERT문이면서 60초 이상 수행되는 세션의 정보를 찾아 보아야겠다."

내가 원하는 조건을 가지고 SQL을 수행하여 원하는 세션의 정보를 찾아 보았다.

"윽. INSERT...SELECT문이라고 생각했는데 일반 INSERT문이 이렇게 오래 대기할 줄이야.
 내가 보지 않고 다른 사람이 말했으면 안 믿었을 것 같아."

내가 찾은 정보로 MaxGauge에서 Session List 모듈을 이용해 내가 얻은 정보는 다음과 같았다.

"공통적으로 gc buffer busy가 발생하고 있네.
 일반적으로 30~40초 걸리는 세션도 있지만 60초에서 70초를 대기 하는 세션들도 다수가 있군.
 70초이상 대기 하는 세션이 없는건 INSERT를 실패하는 경우도 발생한다고 보아야겠네.
 금융권에서 이런 문제는 아주 큰 문제겠어."

우선 상황을 정리하자면

1. 4 Node RAC에 4개의 노드에서 하나의 테이블에 초당 수천건의 INSERT 작업이 발생하고 있다.
2. 여러 노드에서 하나의 블록을 요구하고 있으므로 피해자가 된 세션들은 gc buffer busy 이벤트를 장시간을 대기하고 있다.
3. 하지만 문제가 된 세션들은 전체 세션들에 비하면 아주 작은 양이라 SYSTEM 관점에서는 전혀 문제가 없어 보인다.


 
내가 우선 처음 생각나는 방안은 아래와 같았다.

"지금 이 문제는 여러 노드에서 하나의 블록을 요청해서 발생하고 있다.
 그렇다면 하나의 노드에서 INSERT 작업을 한다면 문제가 되는 gc buffer busy 대기 현상은 없어질 것이 아닌가?
 하나의 노드에서만 INSERT 작업을 할 수 있는지 물어 보자."


이에 대한 담당자의 반응은 이러하였다.

"아! 그 문제가 있어서 HASH PARTITION을 적용했습니다.
 상당부분 줄긴 했지만 가끔 늦어지는 경우가 있습니다.
 그리고 시스템 구성상 한 노드에서만 작업을 하도록 처리하는 것은 불가능합니다."

다른 방안을 생각해 보겠다고 하고 돌아와서 다시 방안을 고민해 보았다.

"아! HASH PARTITION이 되어 있어 문제가 완화되어 항상 문제가 발생하고 있진 않았구나.
 하지만 불운한 블록(계속해서 경합이 발생하는 블록)이 생길 확률을 배제하진 못할 것 같군.
 256개로 HASH PARTITION을 했더라도 그보다 많은 트랜잭션이 발생하고 있으니 말이야."

하루가 지나도 뾰족한 해결책은 나오지 않았다.

"아! 어렵다! 일반적으로 INSERT는 경합이 거의 발생하지 않아 이런 경우는 골치가 아프군!
 일반적으로 발생하지 않는 이 문제에 대한 가장 근본적인 원인 해결은 무엇일까?
 gc buffer busy를 완화시키는 건 다른 노드에서 사용하는 블록을 사용하지 않도록 하면 되는데...
 맞아! 세그먼트를 분리해 버리면 gc buffer busy는 완전히 없어져 버릴텐데...
 각 노드별로 테이블을 따로 만들면 프로그램을 너무 많이 고쳐야 하고...
 응? 세그먼트를 나누면 해결된다고?
 그러면 각각의 노드에서만 사용할 수 있는 4개의 파티션으로 구성된 PARTITION 테이블을 만들면 되잖아!"

"그러려면 각각의 INSTANCE와 PARTIION을 매핑해줄 수가 있어야 하는데...
 아! INSTANCE 번호를 테이블에 추가한 다음 INSTANCE 번호를 PARTITION KEY로 하는 LIST PARTITION 테이블을 만들면 되겠어.
그리고 같은 노드에서도 여러 세션에서 INSERT 작업시 경합이 발생할 수 있으니까 SUB PARTITION으로 HASH PARTITION를 사용하면 더욱 좋겠군."

위와 같은 생각에 담당자분께 다음과 같이 제안하였다.

1. 각 파티션이 해당 INSTANCE만 인식할 수 있는 식별자를 테이블에 추가한다.
2. 노드간 경합을 줄이기 위해 각 노드만 사용하도록 4개의 LIST PARTTION으로 나눈다.
3. 각각의 노드에서 INSERT시 경합을 줄여주기 위해 SUB PARTITION으로 HASH PARTITION 을 사용한다.

하지만 LIST-HASH PARTITION을 10g에서 지원하지 않기 때문에 RANGE-HASH PARTITION을 최종적으로 제안하였다.


해당 제안은 프로그램의 수정이 불가피 하기 때문에 회의를 한 후 적용 여부를 결정하겠다는 이야기를 듣고 나는 철수 하였다.

이후 차세대 시스템 오픈 당일 지원하였을 때 해당 테이블은 모두 PARTITION이 되어 있어 더 이상 해당 문제는 보이지 않았고 다른 문제도 발생하지 않아 성공적인 오픈 결과를 지켜 보았다.

나중에 생각이 난 것인데 INSTANCE를 구분하는 컬럼의 DEFALUT 값을 "USERENV( 'INSTANCE' )"로 셋팅했다면 프로그램을 변경하지 않아도 되었을 것인데 아쉽게 되었다.

이틀 동안 고민한 이 문제에서 다시 한번 느끼게 된 것은 어려운 문제라도 가장 기본이 되는 문제를 파악하면 의외로 간단한 생각에서 문제의 해결책을 찾을 수 있다는 것이었다.

댓글