권순용의 DB 이야기
목록 쿼리를 최적화하자. 1부. 1 1 99,999+

by axiom 목록쿼리 최적화 ROWNUM n-Row 처리 [2013.08.07]


인터넷을 사용하다 보면 많은 종류의 게시판을 만나게 된다. 동호 회의 게시판도 존재할 것이고 일반 게시물을 보여주는 게시판도 존재할 것이다. 이러한 모든 게시판들은 목록 쿼리를 이용하게 된다.

개발자들에게 성능을 최적화하기에 가장 힘든 애플리케이션이 무엇인지를 질문하면 많은 개발자들은 목록 쿼리라고 말한다. 그 렇다면 목록 쿼리에는 어떠한 비밀이 숨겨져 있기에 개발자들이 성능 최적화에 힘들어 하는 것일까?

게시판 등의 목록 쿼리를 보면 하나의 페이지에 보여주는 데이 터의 건 수는 이미 정해져 있다. 한 페이지에 적게는 20건의 데이 터를 보여주거나 많게는 50건의 데이터까지 보여 주는 경우도 있다.

여기서 중요한 것은 20건의 데이터이건 또는 50건의 데이 터이건 간에 한 페이지에서 보여주는 양이 많지 않다는 것이다. 50건의 데이터를 추출하는 것은 절대 많은 양의 데이터는 아니 다.

이것이 바로 목록 쿼리의 성능을 향상시킬 수 있는 한 가닥의 희망은 아닐까 생각한다. 이번 호에서는 목록 쿼리에 대해 자세히 확인해 보자.

목록 쿼리의 전체 데이터 처리 방식을 이해하자

목록 쿼리는 전체 범위로 수행하는 순간 더 이상의 성능 향상 은 있을 수 없다. 전체 데이터 처리 방식을 n-Row 처리 방식으로 변경하는 순간 눈앞에서 엄청난 성능 향상을 체험할 수 있을 것이다.

여기서 언급하는 예제는 특별한 언급이 없는 한 첫 번째 페이지를 추출하는 목록 쿼리다.

전체 데이터 처리의 목록 쿼리를 이해하기 위해서는 우리가 지 금 개발하고 있는 목록 쿼리를 확인해 보면 될 것이다. 현재 대부 분의 목록 쿼리가 전체 데이터 처리 방식으로 구현되고 있기 때문이다.

물론, 목록 쿼리를 n-Row 처리 방식으로 구현하는 사이 트도 있을 것이다. 이제부터 기존 목록 쿼리를 수행하는 일반적 인 형태를 확인해 보자.

SELECT 종류, 제목, 발송일자, 크기
  FROM 
     ( SELECT ROWNUM 순번, 종류, 제목, 발송일자
         FROM 
            ( SELECT 번호, 종류, 제목, 발송일자
                FROM 메일
               WHERE 메일_아이디 = 'KKK'
               ORDER BY 발송일자 DESC
            )
      )
 WHERE 순번 BETWEEN 1 AND 20;

특정 메일_아이디 값에 대해 20건씩 목록으로 추출하는 목록 쿼리를 작성할 때 위와 같이 작성하는 경우가 많다. 위의 목록 쿼 리는 ORDER BY 절을 사용했기 때문에 앞에서 언급한 것과 같 이 n-Row 처리를 수행하지 못하게 된다. 이 뜻은 무엇인가?

n-Row 처리를 수행하지 못하기 때문에 전체 데이터 처리를 수행하게 된다는 것이다. 조건을 만족하는 데이터가 1만 건의 데 이터라고 가정한다면 위의 예제는 20건의 데이터를 추출하기 위 해 1만 건의 데이터를 액세스해야 한다.

1만 건의 데이터를 액세스해서 최종적으로 20건의 데이터를 추출한다면 이는 1만 건의 데이터를 액세스하는 것도 문제이지만 액세스한 1만 건의 데이 터 중 20건의 데이터를 제외한 모든 데이터를 버려야 한다는 것 또한 엄청난 부하를 발생시킨다.

그렇다면 위의 목록 쿼리는 실제 데이터 액세스에 대한 부하밖 에는 없는 것일까? 이외에 또 다른 부하가 존재한다.

가장 안쪽에 있는 인라인 뷰에서 1만 건의 데이터를 액세스해서 발송일자 컬럼에 의해 최근 데이터로 정렬을 수행하게 된다. 그러므로 대용량 데이터의 정렬에 의한 추가적인 부하가 발생하게 된다. ORDER BY 절에 의한 전체 데이터 처리를 수행하기 때문이다. 그 다음 인라인 뷰를 확인해 보자.

1만 건의 데이터에 대해 액세스를 수행한 후 발송일자 컬럼에 의해 1만 건의 데이터를 최근 데이터로 정렬을 수행했다. 정렬된 데이터에 대해 ROWNUM 연산자를 통해 순번을 할당하게 된다.

따라서 1만 건의 데이터 중 가장 최근 데이터는 당연히 순번 1을 할당받게 되며 가장 과거의 데이터는 순번 20,000번을 할당받게 된다.

정렬까지 모두 수행한 1만 건의 데이터에 대해 다시 처음부터 액세스해서 번호를 할당하는 것 자체도 부하이다.

결국, ROWNUM 연산자를 통해 순번을 할당하는 것이 추가적인 두 번째 부하이다. 물론, 이미 액세스한 데이터이므로 메모리에서 액세스하겠지만 이 또한 부하를 발생시킬 수 있다.

추가적인 문제는 마지막 주 쿼리에 존재한다. WHERE 조건 에 설정한 마지막 주 쿼리는 1~20,000까지의 순번이 할당된 1 만 건의 데이터에 대해 순번이 1부터 20까지를 추출하게 된다.

여기서 중요한 성능 저하의 요인이 존재하게 된다. 분명한 것은 순번에 인덱스가 없다는 것이다. 순번 컬럼에는 인덱스가 존재하 지 않을 뿐만 아니라 순번 컬럼에는 인덱스를 생성할 수조차 없다.

테이블에 존재하는 컬럼이 아니라 가공해 만든 컬럼에는 인 덱스를 생성할 수 없기 때문이다.

인덱스가 존재하지 않는 20,000개의 순번에서 1부터 20까지를 추출하기 위해서는 어떻게 수행해야 하는가? 결국, 20,000개의 모든 데이터를 액세스해서 순번을 하나하나 확인해야만 20개의 데이터를 추출할 수 있게 된다.

여기서 1만 건의 데이터는 차례 대로 순번을 할당 받았기 때문에 위에서부터 20건의 데이터만을 확인하면 순번이 1부터 20까지의 데이터를 찾을 수 있기 때문에 해당 쿼리는 1만 건의 정렬된 데이터에서 20건의 데이터만 확인 하면 될 것으로 생각할 수 있다.

하지만, 우리가 생각하는 것처럼 수행되지 않는다는 것이 문제 이다. 순번 1부터 20까지의 데이터를 추출하기 위해서는 인덱스 가 없기 때문에 1만 건의 데이터를 모두 확인해야 한다는 것이다.

순번의 값이 1부터 20까지를 조회한다면 20건이 추출되리라 예상하지만 오라클은 인덱스가 없다면 20건이 추출될지 중복된 순번이 존재해 20건이 추출될지를 모르기 때문에 1만 건의 데이 터에 대해 모든 데이터를 한 번 더 액세스해야 한다. 이 또한 부 하를 발생시키는 부분이 될 것이다.

전체 데이터 처리의 목록 쿼리의 문제점을 이해하자

이처럼 기존의 전체 데이터 처리 방식의 목록 쿼리는 많은 문 제를 발생시키게 된다. 위와 같이 전체 데이터 처리를 수행한다 면 다음과 같은 문제가 발생하게 된다.

  • - 조건을 만족하는 모든 데이터 추출
  • - ORDER BY 절에 의한 조건을 만족하는 모든 데이터에 대해 정렬 발생
  • - 순번 할당을 위한 전체 대상 데이터를 다시 액세스
  • - 원하는 순번 값을 결과로 추출하기 위해 모든 대상 데이터를 다시 액세스

결국, 20건의 데이터로 구성된 하나의 페이지를 완성하기 위해 조건을 만족하는 전체 데이터를 3번 액세스하게 되며 그 중간에 정렬을 수행하게 된다.따라서 데이터가 많다면 성능을 절대 보장받을 수 없게 된다.

ORDER BY 절에 의해 전체 데이터 처리가 발생하므로 조건을 만족하는 모든 데이터를 추출해야 하며 이로 인해 원하는 데이터를 추출하기 위해서는 해당 데이터를 몇 번 액세스해야 한다.

최종으로 20건의 데이터를 추출하면 되지만 전체 데이터 처리에서는 전체를 액세스해야 한다는 것이 가장 큰 첫 번째 문제일 것이다.

전체 데이터 처리로 정렬해 순번을 할당한 데이터에 대해 원하는 순번을 추출하기 위해 한 번 더 모든 데이터를 액세스 해야 한다는 것이 두 번째 문제이다.

이 부분은 이미 추출된 데이터를 메모리에서 읽었기 때문에 성능 저하는 심하지 않을 수 있지만 다시 한 번 전체를 확인해야 된다는 부담을 가지게 되며 이는 분명히 성능 저하를 발생시키게 된다.

이 두 가지 문제는 n-Row 처리와 ROWNUM 연산자를 이용해 최적화할 수 있다. 정렬을 수행하는 세 번째 문제와 인라인 뷰를 한 번 더 액세스해서 순번을 할당하는 문제는 인덱스를 이용해 해결할 수 있다.

그렇다면 네 번째 문제는 어떻게 해결할 수 있겠는가? 네 번째 문제는 마지막에 ROWNUM <= 20 조건을 추가해 해결할 수 있다.

이와 같이 전체 데이터 처리 방식의 목록 쿼리는 여러 가지 문 제로 성능 저하를 발생시키게 된다.

이러한 문제는 앞서 언급한 각각의 요소에 의해 해결할 수도 있지만 결국은 전체 처리 방식 의 목록 쿼리를 n-Row 처리 방식의 목록 쿼리로 변경함으로써 해결할 수 있게 된다.

- 강좌 URL : http://www.gurubee.net/lecture/2287

- 구루비 강좌는 개인의 학습용으로만 사용 할 수 있으며, 다른 웹 페이지에 게재할 경우에는 출처를 꼭 밝혀 주시면 고맙겠습니다.~^^

- 구루비 강좌는 서비스 제공을 위한 목적이나, 학원 홍보, 수익을 얻기 위한 용도로 사용 할 수 없습니다.

by 아발란체 [2013.08.08 10:50:35]

MySQL에서는 별로 고민 안하던 부분이 오라클에서는 늘어지는 질의어에 대해 의문이 있었는데
거기에 대해 많은 이해를 주네요 ~ :)

생각 했던 것보다 부하가 많네요.

근데!!!!
4가지 해결 방법으로 완성된 질의어도 올려주시면  안될까용. +ㅁ+)/

댓글등록
SQL문을 포맷에 맞게(깔끔하게) 등록하려면 code() 버튼을 클릭하여 작성 하시면 됩니다.
로그인 사용자만 댓글을 작성 할 수 있습니다. 로그인, 회원가입