Post

<오라클 성능 고도화 원리와 해법1> Ch01-10 대기 이벤트

오라클 성능 고도화 원리와 해법1 Ch01 오라클 아키텍처 - 10 대기 이벤트

(1) 대기이벤트란?

우리 사회는 매우 분업화된 조직 사회다. 분업해서 일을 진행하다 보면 내가 계속 일을 진행하기 위해선 다른 사람이 일을 마치기를 기다려야만 할 때가 많다. 만약 앞 사람의 작업이 오래 걸릴 것 같으면 눈빠지게 기다리는 것보다 잠시 잠을 청하는 게 나을지도 모른다. 앞사람이 일을 마치고 나를 깨우면 그때 일어나 작업을 계속하면 된다.

오라클 인스턴스도 조직화된 분업 사회처럼 많은 프로세스(또는 쓰레드)들이 역할을 분담해서 각자 맡은 바 임무를 수행한다. 함께 일을 하는 동안 프로세스 간 커뮤니케이션과 상호 작용이 필요하고 때로는 다른 프로세스가 일을 마칠 때까지 기다려야만 하는 상황이 자주 발생한다. 그러면 오라클 프로세스는 일을 계속 진행할 수 있는 조건이 충족될 때까지 수면(sleep) 상태에 빠지는데, 이런 현상이 발생하는 것을 ‘대기 이벤트(Wait Event)’라고 부른다. 그리고 그때마다 오라클은 그 상태 정보를 파일 또는 SGA 메모리 내에 저장해둔다.

대기 이벤트는 원래 오라클 개발자들이 디버깅 용도로 개발한 것이라고 한다. 오라클 개발자들도 공유 자원에 대한 경합이나 기타 원인에 의한 대기가 발생할 때마다 관련 로그를 생성하도록 커널 코드에 기능을 추가했던 것이고, 그것이 오늘날 OWI(Oracle Wait Interface)라는 이름을 덧입으면서 성능 관리 분야에 일대 변혁을 가져오게 되었다.

본서가 대기 이벤트를 중심 주제로 다루지는 않기 때문에 어느 장에서도 대기 이벤트만을 모아 일목요연하게 정리하지는 않는다. 대신 각 장의 내용을 이해하는 데 도움이 되는 대기 이벤트는 필요한 만큼 중간중간 설명이 이루어질 것이다.

대기 이벤트를 시스템 커널 레벨에서 설명하면, 프로세스가 할 일을 모두 마쳤거나 다른 프로세스를 기다려야 하는 상황에서 CPU를 쥔 채 대기하면 불필요하게 CPU 자원을 낭비하는 것이므로 CPU를 OS에 반환하고 수면(Sleep) 상태로 빠지는 것을 말한다. 수면에 빠진다는 것은 프로세스가 wait queue로 옮겨지는 것을 말하며, wait queue에 놓인 프로세스에게는 CPU를 할당해줄 필요가 없으므로 OS는 해당 프로세스를 스케줄링 대상에서 제외시킨다.

선행 프로세스가 일을 마치면 OS에게 그 사실을 알려 자신을 기다리던 수면 상태의 프로세스를 깨우도록 신호를 보낸다(interrupted). 그러면 OS는 그 프로세스를 runnable queue에 옮김으로써 가능한 한 빨리 CPU를 할당받아 일을 재개할 수 있도록 스케쥴링한다.

아래는 유닉스에서 vmstat 유틸리티를 통해 시스템 상황을 모니터링한 것이다.

맨 왼쪽에 보이는 ‘r’은 현재 일을 수행 중이거나 runnable queue에서 CPU 리소스를 기다리는 프로세스 개수다. 이 수치가 CPU 개수를 초과하고 CPU 사용률이 100%에 근접한다면 CPU 병목 현상이 발생한 것으로서, 할 일이 산적해있는데 프로세스들이 빨리빨리 CPU를 할당받지 못해 runnable queue에서 오래 대기하고 있음을 의미한다. ‘w’는 wait queue에 놓인 프로세스 개수를 의미한다. 즉, Sleep 상태의 프로세스 개수로서 이 값이 큰 것도 병목일 수 있다. 특히, 오라클 입장에서는 대기 이벤트가 많이 발생한 것이므로 어떤 종류의 대기 이벤트가 발생 중인지 뷰를 통해 확인해봐야 한다.

위 vmstat 결과 화면에는 idle cpu %(맨 오른쪽)가 낮을 뿐 아니라 paging(pin, pout)도 많이 발생하고 있어 시스템 부하가 매우 극심한 상황임을 알 수 있다.

(2) 대기이벤트는언제발생할까?

다른 프로세스가 일을 끝마치기를 기다릴 때 발생하는 대기 이벤트가 지속적으로 많이 발생하면 데이터베이스에 병목이 있음을 알리는 신호지만 모든 대기 이벤트가 그렇지는 않다. 예를 들어, SQL*Net message from client와 SQL*Net more data from client 이벤트는 서버 프로세스가 사용자의 명령이나 신호를 기다릴 때 나타나므로 병목이 발생했다고 볼 수 없다. 그 외에도 pmon timer, smon timer, px idle 대기 등은 서버 프로세스가 할 일이 없기 때문에 발생하는 idle 대기 이벤트이기 때문에 데이터베이스 튜닝 시 무시해도 된다.

대기 이벤트가 발생하게 되는 상황을 아래와 같이 크게 3가지로 요약할 수 있다.

  1. 자신이 필요로 하는 특정 리소스가 다른 프로세스에 의해 사용 중일 때
  2. 다른 프로세스에 의해 선행 작업이 완료되기를 기다릴 때
  3. 할 일이 없을 때 (-> idle 대기 이벤트)

(3) 대기이벤트는언제사라질까?

우리가 야간에 취침할 때면 아침에 늦지 않게 일어나려고 알람을 켜 놓는 것처럼 오라클 프로세스도 수면 상태로 들어갈 때 타이머를 설정한다. 선행 프로세스가 자신을 흔들어 깨우지 않더라도 타이머에 설정된 시간이 도래(timeout)할 때마다 한번씩 깨어나 자신이 기다리던 리소스가 사용 가능해졌거나 해야 할 일이 생겼는지 확인한다. 타임아웃(timeout) 설정 값은 대기 이벤트마다 모두 다르다.

알람(timeout)에 의해 깨어났는데 리소스가 아직 사용 중이거나 선행 프로세스가 일을 마치지 못했다면 다시 수면 상태로 빠진다. 대기가 자주 발생하는 것도 문제지만 타임아웃이 자주 발생한다면 대기 이벤트에 의한 지연(latency) 시간이 길어지는 것이므로 더 큰 적신호로 받아들여야 한다.

앞에서 대기(wait) 이벤트가 발생할 수 있는 3가지 경우를 설명했는데, 반대로 대기 중이던 프로세스가 활동을 재개하는 시점은 다음과 같다.

  1. 대기 상태에 빠진 프로세스가 기다리던 리소스가 사용 가능해지거나
  2. 작업을 계속 진행하기 위한 선행 작업이 완료되거나
  3. 해야 할 일이 생겼을 때

(4) 래치와 대기 이벤트 개념 명확화

OWI(Oracle Wait Interface)를 따로 공부한 사람들조차도 래치와 대기 이벤트를 명확히 구분 못하는 경우를 종종 보는데, 래치와 대기 이벤트는 구별되어야 한다.

래치를 얻는 과정 자체가 경합을 의미하지는 않는다. 공유된 자원을 읽으려면 래치를 얻는 것이 당연한 일이므로 v$latch 뷰에서 gets 횟수가 증가한다고 해서 문제될 것은 없다. 그저 공유 자원에 대한 접근 요청이 많았던 것으로 이해하면 된다. 다만, 그 과정에서 다른 프로세스와 경합이 발생하는지를 관심 있게 살펴봐야 하며, 만약 그렇다면 시스템의 동시성이 저하되므로 문제다.

v$latch 뷰를 조회해보면, 각 래치 종류별로 gets, misses, spin_gets, sleeps 항목들이 집계돼 있는데, 간단히 살펴보자.

  • gets: 래치 요청 횟수를 말한다.

  • misses: 래치를 요청했는데 다른 프로세스에 의해 자원이 사용 중이어서 첫 번째 시도에서 곧바로 래치를 얻지 못한 횟수다. 래치 miss를 만난 프로세스는, 이후 spin12) 과정에서 래치 획득에 성공하거나(spins_gets) 정해진 횟수만큼의 spin 후에도 래치 획득에 실패해 대기 상태(sleep)에 들어가는, 둘 중 하나의 길을 걷게 된다. gets에서 misses 횟수를 빼면 다른 프로세스의 래치 해제를 기다리지 않고 곧바로 래치 획득에 성공한 횟수(simple_gets)가 구해진다.

    simple_gets = gets - misses

    1. CPU를 점유한 상태로, 해당 리소스에 대한 래치가 해제될 때까지 액세스 시도를 반복하는 것을 말함.
  • spin gets: 래치를 요청한 첫 번째 시도에서 곧바로 래치를 얻지는 못했지만 이후 spin하는 과정에서 래치 획득에 성공한 횟수다. misses에서 sleeps을 뺀 횟수와 일치한다. 즉, 첫 번째 시도에서 래치를 얻지는 못했지만, 다행히 Sleep 전에 래치를 얻게 되는 경우다.

  • sleeps: 래치를 요청했는데 자원이 사용 중이어서 곧바로 래치를 얻지 못했고, 정해진 횟수만큼13) 계속 spin했는데도 결국 래치를 얻지 못해 대기 상태로 빠진 횟수다. 이때 발생하는 것이 latch free 대기 이벤트다. 잠시 후에 깨어나 다시 spin을 했는데도 래치 획득에 실패하면 또다시 latch free 대기 상태로 빠지게 된다. 래치는 Lock처럼 큐잉(queuing) 메커니즘이 작동하지 않기 때문에 래치 획득에 성공할 때까지 반복적인 액세스 시도가 있을 뿐, 우선권을 부여받지는 못한다. 따라서 가장 먼저 래치를 요구했던 프로세스가 가장 늦게 래치를 얻을 수도 있다.

    1. _spin_count = 2000 (default)
This post is licensed under CC BY 4.0 by the author.