레이블이 Database인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Database인 게시물을 표시합니다. 모든 게시물 표시

2016년 7월 28일 목요일

[Tuning] statspack 분석 방법

5. 리포트 분석

5.1 Summary Information

Database    DB Id    Instance     Inst Num Startup Time    Release     RAC
~~~~~~~~ ----------- ------------ -------- --------------- ----------- ---
          3909019673 ora10g              1 14-5월 -06 20:35 10.2.0.1.0  NO
                                         

Host  Name:   STORM-NOTEBOOK   Num CPUs:    1        Phys Memory (MB):    1,022
~~~~

Snapshot       Snap Id     Snap Time      Sessions Curs/Sess Comment
~~~~~~~~    ---------- ------------------ -------- --------- -------------------
Begin Snap:          1 14-5월 -06 22:03:11      16       3.8
  End Snap:         12 14-5월 -06 22:07:29      16       5.3
   Elapsed:                4.30 (mins)

 - 이 부분에는 database ID 및 이름, instance 이름, version 과 같이 statspack report가 수집된 instance에 대한 정보와 report에 이용된 snapshot 정보를 제공합니다.



5.2 Load Profile
                                  Per Second(초당)  Per Transaction(트랜잭션당)
~~~~~~~~~~~~                       ---------------       ---------------
                  Redo size:              4,402.51             81,132.00
              Logical reads:                 58.97              1,086.79
              Block changes:                  7.77                143.14
             Physical reads:                  0.17                  3.07
            Physical writes:                  1.31                 24.07
                 User calls:                  0.43                  7.93
                     Parses:                  6.58                121.29
                Hard parses:                  0.89                 16.36
                      Sorts:                  5.30                 97.71
                     Logons:                  0.03                  0.64
                   Executes:                 11.62                214.21
               Transactions:                  0.05

  % Blocks changed per Read:   13.17    Recursive Call %:    99.72
 Rollback per transaction %:    0.00       Rows per Sort:    10.63


 - 이 부분에서는 snapshot interval 사이의 시스템의 workload(작업부하)가 얼마나 되는지를 설명해 주는 부분입니다.

 - 다른 스냅샷간에서 작성된 2개 이상의 리포트를 사용하여 업무량을 비교 할 때 유효합니다.

 - 즉, Redo 발생 량 및 logical & physical block IO 량 및 parse, sort 정보를 초/트랜잭션 별로 제공을 하고 있어 이 내용을 비교하여 시간대별 작업량의 변동 및 타 시스템과의 workload 비교가 가능합니다.

 - Redo size、Block changes、%Blocks changed per read 이 현저하게 증가한 경우라 한다면 insert/update/delete처리가 보다 많이 행해졌다는 것이 된다.



5.3 Instance Efficiency Percentages (인스턴스 효율)

            Buffer Nowait %:  100.00       Redo NoWait %:  100.00
            Buffer  Hit   %:   99.72    In-memory Sort %:  100.00
            Library Hit   %:   80.63        Soft Parse %:   86.51
         Execute to Parse %:   43.38         Latch Hit %:  100.00
Parse CPU to Parse Elapsd %:   63.41     % Non-Parse CPU:   78.33


 Shared Pool Statistics        Begin   End
                               ------  ------
             Memory Usage %:   77.87   81.52
    % SQL with executions>1:   68.00   75.73
  % Memory for SQL w/exec>1:   87.01   81.60

 
 이 부분은 시스템 성능 진단 과정에서 어떤 부분에 문제가 있는지를 판별 할 수 있는 정보를 줍니다.

  - Buffer nowait
     .process 가 buffer 위하여 기다리지 않고 바로 얻은 비율로 다른 process에 의하여 block이 읽기가 마치기를 기다리거나 incompatible mode에 있어 기다린 횟수가 많은 경우 이 값이 떨어지게 됩니다.

  - Buffer hit
    .buffer cache hit ratio입니다.
    .Hit Ratio 는 60~70% 이상이어야 하며 수치가 적을 때는 db_cache_size를 점검 해야 합니다.

  - Redo Nowait
    .만약 이 비율이 99% 이하의 경우 아래의 내용들을 의심하여 볼 수 있습니다.
     redo log Buffer/File의 크기가 너무 작지 않은가?
     buffer cache에 dirty buffer가 너무 많이 유지되고 있진 않는가?

  - In-memory Sort
    .index 생성, sort morge join, order by, group by , 기타 등등의 sort작업시 disk sort 대 memory sort 비율 입니다.

  - Library Hit    .Library Cache의 Hit Ratio는 90%이상이 되어야 합니다.
    .90% 이하이면 Shared Pool Size를 늘려주거나, SQL 문의 이상을 조사해야 합니다.

  - Memory Usage % : 사용된 Shared Pool의 비율

  - % SQL with executions>1 :재 사용된 SQL문 비율

  - % Memory for SQL w/exec>1:2회이상 실행된 SQL이 사용한 메모리 비율



5.4 Top 5 Wait Events

Event                                            Waits    Time (s)   (ms)   Time
----------------------------------------- ------------ ----------- ------ ------
CPU time                                                         5          76.7
control file sequential read                       527           1      1    9.6
db file parallel write                             184           0      2    5.1
db file sequential read                             43           0      5    3.3
control file parallel write                         86           0      2    3.1
          -------------------------------------------------------------


 이 부분은 시스템이 가진 두드러진 문제점을 간접적으로 설명해 주는 top 5 wait event 정보입니다.
Wait event는 session이 어디서 얼마나 오랫동안 멈춰 있었는지를 설명하는 정보로 가장 문제가 되는 top 5 wait event들에 대한 정보를 제공 합니다.


5.4.1 CPU time 

   - Response Time = Service Time + Wait Time
   - Wait Time = the sum of time spent on Wait Events
   - Service Time = CPU used by this session = CPU Parse + CPU Recursive + CPU Other
 
   - 만약 시스템이 높은 CPU time을 보인다면 statspack report의 'SQL by Gets section' 에서 buffer 를 많이 사용하는 SQL문을 대상으로 tuning작업을 하여 Service time을 줄일 수 있습니다.

   - 만약 CPU time에 비하여 높은 wait time을 보이는 경우 시스템은 resource contention이 있다는 것을 의미하며, 높은 wait event를 보이는 부분부터 wait time을 줄임으로써 시스템 전반적인 Response time을 줄일수 있습니다.


5.4.2 Common Wait Event Problem Areas
 5.4.2.1 buffer busy wait

   - buffer busy wait event는 oracle process가 사용중인 buffer를 기다리는 상태에서 가지게 되는 이벤트 입니다.

   - 일반적으로 buffer busy wait이 심한 경우 hot block에 의한 현상이거나 IO 상의 bottleneck 으로 인한 경우가 가장 흔합니다.\

   - 사실 buffer busy wait에 대한 가장 효과적인 해결책은 buffer cache tuning과 SQL tuning이라고 할 수 있습니다.

5.4.2.2 direct path write

   - Buffer cache를 거치지 않고 PGA의 buffer에서 바로 datafile로 write하는 작업 중 write 요청이 완료되기를 기다리고 있는 상태를 의미합니다.

   - 예를 들어 Disk Sort, hash join, Parallel DML operation, direct path insert 등과 같은 작업 시 write complete가 되기를 기다리는 경우 'direct path write' wait 상태에 있게 됩니다.


5.4.2.3 Log file sync
   - Log file sync는 oracle 이 commit 발행 시 관련된 redo record가 buffer에서 redo logfile에 flush 되는 동안 가지게 되는 wait event로 너무 많은 commit request가 있거나 LGWR의 IO 작업이 원활하지 않는 경우 이러한 현상이 발생할 수 있습니다.

   - redo logfile과 datafile 및 archive 파일을 분리하여 IO를 분산하거나, 가급적 redologfile을  IO 성능 개선을 유도할 수 있는 장치를 사용한다.


5.4.2.4 DB File Scattered Read

   - 일반적으로 FULL 테이블 스캔과 관련된 대기를 나타냅니다

   - 여기서 대기 개수가 많다는 것은 index가 존재하지 않아 full table scan을 하고 있는지를 확인하여 보아야 하며 index가 존재하더라도 부정확한 통계정보로 인하여 full table scan을 하고 있는지를 확인하여 보아야 합니다.


5.4.2.5 DB File Sequential Read

   - DB File Sequential Read 는 index 의 rowid 정보를 이용하여 data block을 access할 때 발생 할 수 있는 wait event로 read block은 1개가 됩니다.

   - 이 wait event가 심한 경우 아래 사항들을 점검하여 조치하도록 합니다.
    .낮은 Buffer cache hit ratio
    .많은 Data update 작업 후 변경되지 않은 table, index statistics
    .Buffer gets가 높은 SQL 문장에 대한 tuning 여부
    .Partitioning 기법 고려
    .많은 chained rows.



5.5 SQL문에 대한 통계정보 

  - Statspack Report에는 resource를 많이 사용한 SQL문장 들을 아래와 같이 resource별로 정리하여 주는 section을 제공하고 있어 SQL tuning 대상 선정에 도움을 주고 있습니다.
  - 스냅샷 레벨 0에서는 아래 정보들이 생성되지 않습니다.

- SQL Ordered by CPU Time :  CPU를 많이 사용한 문장.
 
- SQL Ordered by Elapsed Time : 실행 시간을 많이 사용한 문장

- SQL ordered by Gets : Buffer를 많이 사용한 문장
         
- SQL ordered by Reads : Disk IO를 많이 한 문장

- SQL ordered by Executions : 수행 횟수가 많은 문장
 
  - SQL ordered by Parse Calls : soft parse calls

- SQL ordered by Sharable Memory :  Library cache 내 많은 memory를 사용하고 있는 문장


참고문서
시스템성능 진단을 위한 Statspack 사용방법   - 한국오라클 (주) 제품지원실 이상헌 -
http://blog.naver.com/hirokorea?Redirect=Log&logNo=20023344842

[Database] LOCK 문제를 일으키는 SQL 명령 찾기

LOCK 문제를 일으키는 SQL 명령 찾기
----------------------------------
* 다음 Query는 Lock과 관련된 트랜잭션을 출력해준다.

column username format a10
column sid format 999
column lock_type format a15
column MODE_HELD format a11
column MODE_REQUESTED format a10
column LOCK_ID1 format a8
column LOCK_ID2 format a8
select a.sid,
decode(a.type,
'MR', 'Media Recovery',
'RT', 'Redo Thread',
'UN', 'User Name',
'TX', 'Transaction',
'TM', 'DML',
'UL', 'PL/SQL User Lock',
'DX', 'Distributed Xaction',
'CF', 'Control File',
'IS', 'Instance State',
'FS', 'File Set',
'IR', 'Instance Recovery',
'ST', 'Disk Space Transaction',
'IR', 'Instance Recovery',
'ST', 'Disk Space Transaction',
'TS', 'Temp Segment',
'IV', 'Library Cache Invalidation',
'LS', 'Log Start or Switch',
'RW', 'Row Wait',
'SQ', 'Sequence Number',
'TE', 'Extend Table',
'TT', 'Temp Table',
a.type) lock_type,
decode(a.lmode,
0, 'None', /* Mon Lock equivalent */
1, 'Null', /* N */
2, 'Row-S (SS)', /* L */
3, 'Row-X (SX)', /* R */
3, 'Row-X (SX)', /* R */
4, 'Share', /* S */
5, 'S/Row-X (SSX)', /* C */
6, 'Exclusive', /* X */
to_char(a.lmode)) mode_held,
decode(a.request,
0, 'None', /* Mon Lock equivalent */
1, 'Null', /* N */
2, 'Row-S (SS)', /* L */
3, 'Row-X (SX)', /* R */
4, 'Share', /* S */
5, 'S/Row-X (SSX)', /* C */
6, 'Exclusive', /* X */
to_char(a.request)) mode_requested,
to_char(a.id1) lock_id1, to_char(a.id2) lock_id2
from v$lock a
where (id1,id2) in
(select b.id1, b.id2 from v$lock b where b.id1=a.id1 and
b.id2=a.id2 and b.request>0)
/


( 위의 Query를 실행시키면 다음과 같은 내용이 출력된다. )

SID LOCK_TYPE MODE_HELD MODE_REQUE LOCK_ID1 LOCK_ID2
--- --------------- ---------- ---------- -------- --------
5 Transaction Exclusive None 262172 90
6 Transaction None Exclusive 262172 90
9 Transaction None Exclusive 262172 90

SID 6과 9는 SID 5가 걸고 있는 Lock이 풀리기를 기다리고 있음을 알 수 있다.

* 다음 Query는 Lock과 관련된 테이블을 출력해 준다.

column username format a10
column lockwait format a10
column sql_text format a80
column object_owner format a14
column object format a15
select b.username username, c.sid sid, c.owner object_owner,
c.object object, b.lockwait, a.sql_text SQL
from v$sqltext a, v$session b, v$access c
where a.address=b.sql_address and
a.hash_value=b.sql_hash_value and
b.sid = c.sid and c.owner != 'SYS';
/

( 위의 Query를 실행하면 다음과 같은 결과가 출력된다.

USERNAME SID OBJECT_OWNER OBJECT LOCKWAIT
--------------- --- ------------- -------------- ----------
SQL
---------------------------------------------------------------
LTO2 6 LTO EMP C3D320F4
update lto.emp set empno =25 where empno=7788
LTO3 9 LTO EMP C3D320C8
delete from lto.emp where empno=7788
LTO 5 LTO DEPT
insert into lto.dept values (60,'PROGRAMMER','LOS ANGELOS')

여기서는 USERNAME에 나와있는 유저가 OBJECT에 나와있는 테이블을 수정하려고
함을 나타낸다. LT02,LT03는 LT0가 Commit,Rollback 하기를 기다리고 있음을 알
수 있다. 하지만 여기에는 가장 최근의 DML 명령 하나만 나와있기 때문에 여기
나온 명령이 반드시 Lock을 걸고 있는 명령이라고 단정지을 수는 없다.

관련된 프로세스
---------------
* 다음 Query를 실행해 보면 프로세스와 관련된 정보를 얻을 수 있다.

column "ORACLE USER" format a11
column SERIAL# format 9999999
column "OS USER" format a8
select substr(s.username,1,11) "ORACLE USER", p.pid "PROCESS ID",
s.sid "SESSION ID", s.serial#, osuser "OS USER",
p.spid "PROC SPID",s.process "SESS SPID", s.lockwait "LOCK WAIT"
from v$process p, v$session s, v$access a
where a.sid=s.sid and
p.addr=s.paddr and
s.username != 'SYS'
/

* 위의 Query를 실행하면 다음과 같은 결과가 출력된다.

ORACLE PROCESS SESSION SERIAL# OS USER PROC SESS LOCKWT
USER ID ID SPID SPID
------ ------- ------- ------- ------- ---- ---- ------
LTO 19 5 31 usupport 17312 17309
LTO2 25 6 43 usupport 17313 17310 C3D320F4
LTO3 26 9 1 usupport 17314 17311 C3D320D8

이 때는 다음과 같이 조치한다.

1. LTO에게 Commit,Rollback 할 것을 요구한다.
2. SQLDBA>ALTER SYSTEM KILL SESSION '5,31';
3. %kill -9 17309 (유닉스상의 Shadown Process)
stop/id= (PROC SPID=SESS SPID on vms running single task)

여기서 SYS 유저는 제외시켰는데 필요하다면 Query의 Where 조건에서
s.username != 'SYS' 부분을 삭제하면 된다.

CATBLOCK.SQL & UTLLOCKT.SQL
---------------------------
$ORACLE_HOME/rdbms/admin 디렉토리에 있는 스크립트 가운데 catblock.sql과
utlockt.sql을 사용하여서 Lock 상황을 쉽게 파악할 수 있다. 이들은 다음과
같이 실행한다.

%cd $ORACLE_HOME/rdbms/admin
%sqldba lmode=y
SQLDBA>connect internal
SQLDBA>@catblock
SQLDBA>@catproc

결과는 다음 Query 문으로 알아 본다.

column waiting_session format a8
select lpad(' ',3*(level-1)) || waiting_session,
lock_type,
mode_requested,
mode_held,
lock_id1,
lock_id1,
lock_id2
from lock_holders
connect by prior waiting_session = holding_session
start with holding_session is null;

위의 Query에 의한 출력은 다음과 같다.

WAITING_ LOCK_TYPE MODE_REQUE MODE_HELD LOCK_ID1 LOCK_ID2
-------- ----------------- ---------- ---------- ---------- ----------
5 None
6 Transaction Exclusive Exclusive 262172 90
9 Transaction Exclusive Exclusive 262172 90

여기서 Session 6, Session 9가 Session 5를 기다리고 있음을 알 수 있다.

Lock & Hanging 문제를 추정하는 방법
-----------------------------------
프로그램 상에서 어느 부분이 Lock, Hanging 문제를 일으키는지 알아내기가
여의치 않을때 다음과 같은 방법을 사용해 보기 바란다.

1. init.ora의 sql_trace=ture로 세팅하면 연관된 SQL 명령이 출력될
것이다.
2. OS 상에서도 Process 상태를 점검하여 본다.
3. OS 상의 Debugging 기능을 사용하거나 만약 가능하다면 oradbx를 사용한다.
Platform 에 따라서 없을 수도 있다.
4. 여러가지 Monitoring 방법과 Locking/Blocking 스크립트를 이용한다.

PROCESS STACKS 확인
-------------------
때로는 Hanging Process나 리소스를 점유하고 있는 Process의 Process Stack을
점검해 보는 것이 문제 해결에 도움이 되는 경우가 있다.

1. OS Debugging 프로그램을 이용하여 Hangup 되기 전의 마지막 Call을 확인
한다.

ex)%truss -p

2. oradbx(오라클 debugging 프로그램)은 Support,Development 시에만 사용
된다.

select substr(s.username,1,11) "ORACLE USER" ,
p.pid "PROCESS ID", s.sid "SESSION ID", s.serial#,
osuser "OS USER", p.spid "PROC SPID"
from v$session s, v$access a
where a.sid=s.sid and
p.addr=s.paddr and
s.username != 'SYS'
/

위의 Query를 실행하면 다음과 같은 결과가 출력된다.

ORACLE PROCESS SESSION SERIAL# OS USER PROC SESS LOCKWT
USER ID ID SPID SPID
------ ------- ------- ------- ------- ---- ---- ------
LTO 19 5 31 usupport 17312 17309
LTO2 25 6 43 usupport 17313 17310 C3D320F4
LTO3 26 9 1 usupport 17314 17311 C3D320D8

만약 oradbx가 없다면 다음과 같이 해서 만들어 준다.

%cd $ORACLE_HOME/rdbms/lib
%make -f oracle.mk oradbx

LTO Process가 무엇을 하고 있는지 알고 싶으면 Process Stack을 보면 알수
있다.

ps -ef | grep 17312
usupport 17312 17309 0 Sep 15 ? 0:00 oracleV713(DESCRIPTION=(LOCAL=YE
type
debug 17312 (이 유저의 oracle shadow process)
dump stack
dump procstat

위에서 생성된 트레이스 화일(user_dump_dest 에 생성됨)을 이용하면 Hanging
문제를 해결하는데 큰 도움이 된다.

자주 발생하는 LOCK 문제들
-------------------------
1. Parent-Child 관계로 묶인 테이블에서 Child 테이블에 Index가 없는 상황
에서 Child 테이블을 수정하게 되면 Parent테이블에 TABLE LEVEL SHARE
LOCK이 걸리게 되어서 Parent 테이블에 대한 모든 Update가 금지된다.
2. 블럭의 PCTFREE가 매우 작다면 한 블럭에 여러개의 레코드가 들어 있기
때문에 한 블럭에 과도한 트랜잭션이 들어와서 블럭의 Transaction Layer가
Release 되기를 기다리게 되는 경우도 있다.

Ex)
create table test (a number) initrans 1 maxtrans 1;

SYSTEM: insert into test values (5); /* completed */
SCOTT: insert into SYSTEM.test values (10); /* Scott waits */

SID OWNER LOCK_TYPE MODE_HELD MODE_REQUE LOCK_ID1 LOCK_ID2
---- ----- ------------- ----------- ---------- -------- --------
7 System Transaction Exclusive None 196639 54
10 Scott Transaction None Share 196639 54

[Database] Oracle Wait Event 모니터링

Oracle Wait Event 모니터링
글: 안진철 (jcahn@warevalley.com)
現 ㈜웨어밸리 책임 컨설턴트
前 LG-EDS 기술 연구부문 DB 팀
- 솔루션 컨설팅 경력 6년

연재 순서
[1] Oracle Wait Event 모니터링 (2003년 1월 28일)
[2] Enqueue와 Latch (2003년 2월 12일)
[3] Shared Pool 관련 Wait Event (2003년 2월 26일)
[4] buffer cache 관련 Wait Event (2003년 3월 12일)
[5] redo log 관련 Wait Event (2003년 3월 26일)
[6] Top SQL 튜닝 (2003년 4월 9일)
[1] Oracle Wait Event 모니터링
흔히 DBA를 3D업종이라고 부르는 이유 가운데 하나는 몸은 고달픈데 반해 그 성과가 별로 티가 나지 않는다는 사실 때문일 것이다. 실제로, DBA가 수행해야 하는 일상적인 관리 업무들은 몸은 다소 피곤하게 만들지 몰라도 어느 정도 경험이 쌓이면 그리 부담을 주는 일은 아니다. 우리가 한단계 업그레이드된 전문가로서 인정 받는 DBA가 되기 위해서는 장애상황 혹은 유사 장애 상황에서 DB 모니터링 작업을 수행하고 분석할 수 있어야 한다. 시스템이 갑자기 느려지고 업무가 마비되는 상황에 맞닥뜨렸을 때 문제의 원인이 무엇인지를 집어낼 수 있는 능력이 있어야 하며 최소한 오라클의 문제인지 아닌지를 판단할 수는 있어야 몸으로 야간작업이나 때우는 DBA가 아니라 조직에 없어서는 안될 전문가로서의 나의 존재가치를 인정 받을 수 있을 것이다.
이 글에서는 오라클 Wait Event에 대하여 간단히 알아보고 일시적인 성능저하 상황에서 Wait Event를 모니터링하고 그 원인을 찾아가는 방법에 대하여 다루어 보고자 한다. 짧은 지면 위에 다룰 수 있는 내용도 제한되어 있고 글쓴이의 지식 또한 일천하지만 오라클 전문가가 되기 위해 같은 길을 가고 있는 동료로서 가진 지식 몇 가지 공유한다는 취지로 이 글을 쓴다.
오라클의 Wait Event 정보는 V$SYSTEM_EVENT, V$SESSION_EVENT, V$SESSION_WAIT 등이 있는데, 이 가운데 V$SESSION_WAIT는 각 세션이 현재 Waiting 하고 있는 Event나 마지막으로 Wait한 Event 정보를 보관하고 있으며, V$SYSTEM_EVENT와 V$SESSION_EVENT는 시스템이 Startup된 이후 각각 시스템 전체, 혹은 세션별로 발생한 Wait Event 정보를 누적하여 기록하고 있다.
오라클의 Wait Event는 성격에 따라 Network교신이나 IO를 위해 대기하는 일상적인 Wait와 특정 자원에 대해 여러 프로세스가 동시에 액세스하고자 할 때 발생하는 Wait, 별달리 할 일이 없어 대기하고 있는 Idle Wait 등 세가지 유형으로 구분할 수 있는데 그 유형에 따라 해석방법도 달라진다. 일단, Idle Wait는 일반적인 관심의 대상에서 제외되며 IO나 Network 관련 Wait는 작업량이 증가하면 같이 증가하는 Wait이므로 전체 서비스 시간(CPU time)과 비교하여 상대적으로 평가해야 하며 총 Wait time보다는 평균 Wait Time에 관심을 두고 분석을 해야 할 것이다. 시스템 자원에 대한 Wait는 데이터베이스 서버 튜닝시 가장 주된 관심 대상이 되며 이들 Wait에 대해서는 평균 Wait Time뿐만 아니라 총 Wait Time에도 관심을 가지고 분석해야 할 것이다. 유형별로 대표적인 Wait Event를 살펴본다면 아래와 같다.

[주요 Wait Event] 구분 이벤트명 설 명
일상적인 Wait Event
   db file scattered read - Full Scan시 OS에 I/O를 요청해놓고 대기
   db file sequential read -  Index Scan시 OS에 I/O를 요청해놓고 대기
(IO, Network)
   log file sync - 변경 log buffer를 log file에 반영하는 동안 대기
   DFS lock handle - OPS 환경에서 노드간 분산 Lock 교환에 따른 대기
   global cache cr request - OPS 환경에서 노드간 Buffer Block 교환에 의한 대기
자원 경합에 따른 Wait Event
    enqueue - Type에 따라 세분화 (24개의 enqueue type (9i))
    latch free - Name에 따라 세분화 (239개의 latch가 존재 (9i))
    buffer busy waits - 동일블록에 대한 동시 액세스에 따른 경합
    free buffer waits - free buffer를 할당위해 DBWR의 Write를 대기
    Log buffer space - Log buffer를 할당 받기 위해 LGWR의 write를 대기
    library cache lock - SGA내의 library cache를 참조하기 위한 대기(검색)
    row cache lock - SGA내의 dictionary cache를 참조하기 위한 대기
Idle Event
    SQL*Net message from client -   Client로부터의 작업요청을 대기
    Pmon timer - PMON이 할일 없을 때 대기하는 Event
   
업무시간대에 시스템이 갑자기 느려졌다면서 오라클 서버에 문제가 없는지 문의가 들어오면 글쓴이는 우선 아래의 SQL을 수행시켜본다.
select /*+ ordered / distinct /* 속도를 위해 v$sql을 조인할 경우 중복되는 레코드 제거 */
           s.sid SID, s.username, s.program, p.spid "OS-Pid",w.seconds_in_wait as "W_time(Sec)",
          decode(w.wait_time,0,'Wai-ting', 'Waited') Status, w.ename event,
--              p1text || ':' || decode(event,'latch free',p1raw, to_char(p1)) ||','||
--              p2text || ':' || to_char(p2) ||','|| p3text || ':' || to_char(p3) "Additional Info",
           q.sql_text
from ( select a.*, decode(a.event,'latch free', 'latch free (' ||b.name||')',
                                         'row cache lock', 'row cache lock (' || c.parameter || ')',
                                         'enqueue', 'enqueue ('||chr(bitand(p1, -16777216)/16777215)||
                                                                             chr(bitand(p1,16711680)/65535)||':'||
                                                decode(bitand(p1,65535), 1, 'N', 2, 'SS',3,'SX',4,'S',5,'SSX',6,'X') ||')',
                              a.event ) ename
           from v$session_wait a, v$latchname b, v$rowcache c
         where a.p2 = b.latch#(+) and a.p1 = c.cache#(+) and c.type(+) = 'PARENT'
           and a.event not in ('rdbms ipc message','smon timer','pmon timer','slave wait','pipe get','null event',
                                     'SQL*Net message from client', 'SQL*Net message to client','PX Idle Wait',
                                      'PX Deq: Execution Msg', 'KXFQ: kxfqdeq - normal deqeue',
                                         'ges remote message', 'wakeup time manager', /* idle event 적절히 수정 */
                                         'lock manager wait for remote message', 'single-task message')
        ) w, v$session s, v$process p, v$sql q
where w.sid = s.sid and s.paddr = p.addr
and s.sql_hash_value = q.hash_value(+) and s.sql_address = q.address(+)
order by w.ename;
SQL의 구체적인 내용이야 필요한 정보와 개인적 취향에 따라 달라지겠지만, 중요한 것은 일단 V$SESSION_WAIT 뷰로부터 실시간 Wait Event 정보를 얻어낸다는 것이다. 위 SQL을 수행했을 때 나타나는 결과가 없다면 일단 오라클 측면에서 업무성능을 심각하게 마비시키는 Waiting이 발생하고 있지 않다고 봐도 큰 무리가 없을 것이다.
일반적인 상태에서는 주로 'db file sequential read'나 'db file scattered read' 가 나타날 텐데, 이러한 Wait Event는 보통 짧은 시간 동안 지속되며 대상 자원(블록)을 바꿔가며 Wait가 반복되는 형태로 나타날 것이다. 이는 작업 처리량이 많을 때 일상적으로 발생하는 IO관련 Wait Event이므로 해당 세션에서 IO를 제법 많이 유발하고 있다는 정도로 이해하고 넘어가면 될 것이다. 물론, Wait의 지속시간이 길거나 지나치게 빈번히 나타나는 SQL에 대해서는 비효율적인 실행계획을 수립하고 있지 않은지 검토해서 튜닝해 주어야 한다.
성능저하의 원인이 오라클 쪽에 있는 경우에는 특정 자원에 대한 Waiting이 상당히 오랫동안 지속되어 현재까지 Waiting이 진행 중인 세션들(STATUS가 'Wai-ting' (wait_time=0)이며 'W_time(sec)' (seconds_in_wait) 값이 상당히 큰 세션)이 존재할 가능성이 높다. 오라클의 내부적인 작업들은 매우 짧은 기간에 처리되어야 하므로, Idle event(where절에서 not in으로 처리한 부분, 버전에 따라 달라질 수 있다.) 이외의 특정 Wait Event가 눈에 띌 정도로 검출된다는 것은 오라클 내부적으로는 훨씬 더 많은 Waiting이 발생하고 있다고 생각해야 한다. 바로 이런 세션들이 문제의 범인들이며 이제부터 DBA는 이들 Wait Event에 대한 원인을 파악하여 조치하는 작업을 해주어야 한다. 각각의 Wait Event에 따라 원인을 추적하고 조치하는 방법은 달라질 것이다.
다음 호에서는, 자주 경험하는 몇가지 대표적인 Wait Event들에 대하여 SGA 영역별로 구분하여 좀 더 자세히 살펴보고, 그에 앞서 Lock 또는 Latch Event의 이해를 위해 필요한 Enqueue와 Latch의 개념을 간단히 알아보도록 하겠다.

[다음]
[2] Enqueue와 Latch 개념 이해하기
DBMS의 가장 주된 기능 중에 하나는 동일 자원에 대한 동시 액세스를 관리하는 것이며, 이를 위해 오라클이 사용하는 대표적인 제어 구조가 Enqueue와 Latch이다.
Enqueue 와 Latch는 모두 특정 자원에 대한 접근을 serialize하는 것이 목적이라는 점에서는 같은 Lock의 일종이지만 관리방식이나 용도에서 차이가 있다. Enqueue는 이름에서 보듯 Queue를 통해 관리된다. 대상 자원에 대한 Owner, Waiter, Converter Queue를 관리하면서 먼저 요청한 순서대로 Lock을 획득하도록 하는 구조이며, Exclusive 모드 뿐 아니라 다양한 수준의 공유를 허용한다. 대표적인 것이 테이블 데이터를 Update할 때 사용되는 TM, TX enqueue이다.
반면에, Latch는 Enqueue에 비해 훨씬 단순한 구조로서 매우 짧은 시간 내에 획득되고 해제된다. Queue를 통해 관리되지 않으므로 먼저 Request한 프로세스가 먼저 latch를 획득한다는 보장이 없으며, 대부분의 경우 Exclusive모드로만 획득된다. Latch는 주로 SGA의 특정 메모리 구조체에 대한 액세스(library cache latch, cache buffers chains latch) 혹은 메모리 할당 시 (shared pool latch) 사용되거나 오라클의 중요한 코드가 동시에 수행되지 않도록 하기 위한 용도로(redo writing latch) 사용된다. Latch는 Enqueue보다는 하위 level에서 Locking 자체의 부하를 최소화하며 작동하는 제어 메커니즘이라고 할 수 있으며, 실제로 Enqueue 역시 내부적으로는 Latch (enqueues, enqueue hash chains latch )에 의해 운영된다는 점을 생각하면 둘 사이의 차이를 쉽게 이해할 수 있을 것이다.
■ Enqueue
Enqueue 정보는 내부적으로 Enqueue Resource 배열과 Enqueue Lock 배열에 저장된다. 특정 자원에 대한 Lock이 요청되면 대상을 하나의 Resource로 정의하여 할당하고 그 Resource에 대해 관련 Lock 정보를 Owner, Waiter, Converter가운데 하나로서 Link시키는 방식으로 운영되며, 이러한 정보는 V$RESOURCE와 V$LOCK 뷰를 통해 조회해 볼 수 있다. V$RESOURCE와 V$LOCK은 1:M 관계로 하나의 Resource에 대하여 여러 건의 Lock 레코드가 Owner (LMODE>0, REQUEST=0), Waiter (LMODE=0 ,REQUEST>0), Converter (LMODE>0, REQUEST>0) 중 하나로서 대응된다.
Enqueue Wait이 발생하는 것은 다른 세션이 이미 나보다 먼저 해당 자원에 대한 Lock을 잡고 있으므로 인해 내가 원하는 모드로 Lock을 할당 받을 수 없기 때문이다. 자신이 필요로 하는 Lock의 획득에 실패한 세션은 Owner가 작업을 완료하고 자신을 깨워줄 때까지(세마포어를 포스트해줄 때까지) Waiter 혹은 Converter Queue에서 대기하게 되며, 기다려도 소식이 없으면 3초 간격으로 timeout에 의해 일어나 혹시 Deadlock 상황이 아닌지 점검해 본 후 다시 Sleep에 빠져들기를 반복하게 된다. 튜닝관련 자료를 보다 보면 가끔 Enqueue에 대한 Wait이 많은 경우에 Enqueue_resource나 Enqueue_lock 파라미터를 증가시켜 주어야 한다는 가이드를 보게 되는 경우가 있는데 이 파라미터들은 Enqueue resource와 lock 배열의 크기를 늘려줄 뿐 특정 Enqueue 자원에 대한 동시 경합을 해소시키는 것과는 상관이 없다. Enqueue Wait를 해소하기 위한 구체적인 방법은 Enqueue type에 따라 달라지지만 결국은 Enqueue를 불필요하게 요청하는 경우가 없는지를 살펴 Enqueue에 대한 요청을 최소화하고 Enqueue를 점유하는 시간을 최대한 단축시키는 것이다. TX Enqueue에 대한 Wait은 대상 자원에 대한 Lock을 소유하고 있는 세션과 그 세션이 수행 중인 SQL을 찾아 트랜잭션이 장시간 지속되고 있는 이유가 무엇인지 애플리케이션 측면에서 조사해야 하며, SQ enqueue는 Sequence 값 할당 시 발생하는 경합이므로 cache값을 늘려줌으로써 완화시킨다거나 ST Enqueue의 경합이 존재할 경우에는 Locally managed tablespace를 사용하거나 Initial, Next 등의 extent 크기를 적당한 값으로 조정하여 실시간 공간할당을 감소시켜주는 등의 방법들이 Enqueue Wait에 대처하는 대표적인 사례이다. 지난 호에서 소개한 Session Waiter 스크립트는 Enqueue Wait 이벤트에 대해서 Enqueue type과 모드를 함께 표시하여 주도록 하고 있으며, 참고로 Enqueue type별 누적 Wait현황을 확인하고자 하면 아래 SQL을 수행하면 된다.

select q.ksqsttyp type,
           q.ksqstget gets,
           q.ksqstwat waits,
            round(q.ksqstwat/q.ksqstget,3) waitratio
       from sys.x$ksqst q
where q.inst_id = userenv('Instance')
      and q.ksqstget > 0
order by waits desc
/

■ Latch
오라클 운영 시에 하위레벨에서 내부적으로 처리되는 다양한 조작들이 latch의 관할 하에 수행되는데 V$LATCHNAME을 조회해보면 (9i 기준으로) 239 종류나 되는 Latch가 존재하는 것을 확인할 수 있다. 이 가운데 우리가 자주 접하게 되는 latch는 다음과 같은 정도이며 각 Latch의 기능은 관련 SGA별 Wait를 다룰 때 간단하게나마 소개하도록 하겠다.
Shared pool -  library cache latch, shared pool latch, row cache objects
Buffer Cache - cache buffers chains latch, cache buffers lru latch, cache buffer handle
Redo log - redo allocation latch, redo copy latch, redo writing latch
OPS - dlm resource hash list

▷ Willing to wait 모드와 No-wait 모드
Latch 획득 방식은 No-wait과 Willing to wait 의 두 가지 모드로 구분할 수 있다. Willing to wait 모드는 Latch의 획득에 실패하면 좀더 시간을 끌면서 해당 Latch를 잡을 때까지 재시도를 해보는 방식을 말한다. 일차적으로는 CPU를 놓지 않고 정해진 횟수만큼 Spinning을 한 후 재시도를 해보다가 그래도 실패하면 CPU를 놓고 Sleep하다가 timeout되어 재시도하는 작업을 반복하면서 Latch의 획득을 노력하게 된다. Latch가 sleep에 들어가게 되면 'latch free' wait event 대기가 시작된다. sleep의 지속시간은 sleep 횟수가 늘어갈수록 점점 길어지게 되는데, 따라서 V$LATCH의 Gets와 Sleeps의 비율과 함께 Sleep1~sleep4 항목에서 몇차 Sleep까지 발생했는지 여부도 각 Latch Wait의 심각성을 판단하는 요소 가운데 하나가 된다.
No-wait 모드는 Willing to wait과는 달리 더 이상 미련을 두지 않고 해당 Latch에 대한 획득을 포기하는 것이다. No-wait 모드가 사용되는 경우는 두 가지가 있는데, 하나는 동일한 기능을 하는 Latch가 여러 개 존재하여 그 중에 하나만 획득하면 충분하여서 특정 Latch에 미련을 가질 필요가 없는 경우이다. 물론, 이 때에도 같은 기능의 모든 Latch에 대한 시도가 실패로 끝날 경우에는 Willing to wait 모드로 요청을 할 것이다. No-wait 모드가 사용되는 다른 한가지 경우는 dead lock을 피하기 위해서 이다. 오라클은 기본적으로 latch dead lock 상황을 피하기 위하여 모든 Latch에 level을 부여하여 정해진 순서를 따라서만 Latch를 획득하도록 하고 있는데, 필요에 의해 이 규칙을 어기고 Latch를 획득하고자 할 경우 일단 No-wait 모드로 시도를 해보는 것이다. 다행히 Latch를 잡으면 좋은 것이고 비록 latch를 잡을 수 없더라도 무한정 기다림으로써 dead lock 상태에 빠지는 일은 피할 수 있는 것이다. No-wait 모드의 Latch작업에서는 당연히 Latch 관련 wait이 발생하지 않으며, redo copy latch를 제외하고는 Willing to wait 모드로 Latch를 획득하는 경우가 훨씬 많다.
▷ Parent latch와 Child latch
Latch 가운데에는 동일 기능을 하는 Child latch들의 set으로 운영되는 Latch도 있으며 하나의 Latch로만 운영되는 Latch도 있다. 전자의 대표적인 예로는 cache buffers chains (버퍼캐쉬 블록 들을 같은 이름의 다수의 Latch가 나누어 담당)가 있으며, 후자의 예로는 shared pool latch (shared pool내에서 메모리 할당을 위해 획득해야 하는 Latch로 시스템에 하나만 존재)가 있다. 이와 같은 Latch 관련 통계 정보는 Parent latch와 Child latch의 개념으로 관리가 되는데 Latch set에서 개별 Child latch에 대한 통계정보는 V$LATCH_CHILDREN 뷰를 통해 조회할 수 있으며, 단일 Latch 혹은 Latch set의 마스터 Latch (parent)에 대한 통계정보는 V$LATCH_PARENT 뷰를 통해 조회할 수 있다.
지금까지 한 회 분량을 할애하여 Enqueue와 Latch에 대해 요약해본 이유는, 많은 Waiting이 SGA내의 공유자원 (Block, Cursor 등)에 대한 경합으로 인해 발생하며 이러한 경합은 다시 해당 자원에 대한 동시 액세스를 제어하는 Enqueue와 Latch에 대한 경합으로 흔히 드러나게 되므로 오라클의 Wait Event를 모니터링하기 위해서는 Enqueue와 Latch의 구조와 작동원리에 대해 이해하는 것이 필수적이기 때문이다.

[3] Shared Pool 관련 Wait
■ Share pool과 성능문제
오라클이 공유 메모리(SGA)를 사용하는 가장 큰 이유는 기본적으로 메모리 사용을 최소화하면서 처리성능은 최대화하기 위한 것이다. 한번 액세스된 블록을 Database buffer cache에 캐쉬 함으로써 비용이 큰 Disk I/O를 최소화하는 것처럼, 한번 처리된 SQL의 실행 정보를 Shared Pool에 공유함으로써 파싱 작업을 위한 CPU, 메모리 자원의 사용을 최소화하고 SQL 수행속도를 증가시킬 수 있다. Shared Pool에는 SQL이나 PL/SQL을 수행하기 위한 각종 정보 - SQL구문 및 실행계획, PL/SQL 소스, 테이블, 뷰 등의 각종 오브젝트와 오브젝트 상호간의 의존관계, 권한관계 등 - 가 저장되어 있다. 지면 관계상 이 글에서 Shared Pool의 관리 메커니즘을 상세히 기술할 수는 없지만 몇 가지 내재적인 특징으로 인해 Shared Pool은 오라클의 메모리 영역 가운데에서도 가장 성능문제의 요소가 많은 곳이면서도 효과적인 튜닝이 수월치 않은 영역이기도 하다.
무엇보다, Shared Pool에서 가장 문제가 되는 것은 메모리의 조각화(Fragmentation)이다. Shared Pool에서 라이브러리 캐쉬 오브젝트를 위해 할당되는 메모리 단위를 chunk라고 부르는데 chunk의 크기는 수 바이트에서 수 K바이트에 이르기까지 필요에 의해 다양하게 할당된다. 새로운 chunk의 할당이 필요하게 되면, 프로세스는 이미 존재하는 chunk로부터 필요한 만큼의 크기만을 떼어내어 사용하므로 시간이 흐를수록 점차 메모리가 조각화 되는 것을 피할 수 없다. ( 이는, Pctincrease가 0가 아닌 테이블스페이스에서 익스텐트의 할당과 해제가 반복됨에 따라 공간의 조각화가 심해지는 것을 떠올리면 이해가 쉬울 것이다. ). 어느 정도 정형화된 패턴의 애플리케이션이 수행되는 환경이 아니라, 공유가 불가능한 다양한 형태의 SQL(대표적으로 Literal SQL)이 빈번히 요청되는 환경이라면 Shared Pool 메모리 조각화에 따른 문제는 더욱 심각해진다.
또한, Shared Pool은 일반적인 메모리 캐쉬와는 달리 메모리에 저장되었던 정보를 잠시 기록해둘 대응되는 디스크 공간이 없으므로 한번 flush된 라이브러리 캐쉬 오브젝트를 reload하기 위해서는 해당 정보를 재생성 해야만 한다. 이 과정에서 관련 오브젝트 정보의 검색 및 참조, locking, 메모리 할당 등의 작업을 위해 많은 비용이 들기 때문에 결국 Shared Pool 관련 튜닝의 최대 과제는 SQL 공유를 최대화하여 새로운 파싱 요청과 메모리 요청을 최소화하는 것이라고 할 수 있다. 헌데, 이는 애플리케이션의 설계와 연계되는 영역으로서 이미 개발이 완료된 운영서버에서는 변경작업이 여의치 않은 것이 현실이다. 앞서, Shared Pool이 DBA로서 튜닝이 수월치 않은 영역이라고 표현한 이유 가운데 하나가 여기에 있다.
■ Shared Pool 관련 오해 바로잡기
Shared Pool과 관련하여 판단이 쉽지 않은 부분 가운데 하나가 과연 shared_pool_size를 얼마나 할당할 것인가 하는 것이다. 오라클은 Shared Pool 메모리를 최대한 효율적으로 활용하기 위하여 다양한 기법을 동원하고 있는데, 이러한 메모리 관리 메커니즘에 대해 정확히 알지 못하여 Shared Pool 크기를 지나치게 크게 할당함으로써 오히려 문제를 악화시키는 경우도 드물지 않다. 이러한 오해를 바로잡기 위해 Shared Pool의 메모리 할당과정을 간단하게나마 살펴보도록 하겠다.
새로운 메모리 Chunk가 할당되는 과정을 살펴보면, 우선 프로세스는 Free List를 검색하여 자신이 필요로 하는 크기의 Free Chunk를 찾고, 그러한 Free Chunk가 없으면 원하는 크기보다 한단계 큰 Free Chunk를 찾아서 필요한 크기만큼 분할하여 사용하게 된다. 만약 Free List에서 충분한 크기의 Free Chunk를 찾을 수 없다면, 이미 사용되었으나 현재는 사용되고 있지 않는(unpinned) Chunk들의 LRU List를 검색하여 오래된 것부터 8개씩 flush시켜 Free Chunk로 만든 후 자신이 필요한 크기를 할당하여 사용하게 된다. 만약 이 과정에서 현재 사용중인(pinned) Chunk가 대부분이거나, 너무 메모리 조각화가 많이 일어나서 기존 Chunk를 Flush시킨 후 인접한 Free Chunk들을 병합해보아도 원하는 크기의 Free Chunk를 얻어낼 수 없다면 오라클은 ORA-4031 에러를 발생시키는데, 그 이전에 한가지 최후의 비밀무기가 더 숨어 있다. 바로 Spare Free 메모리라는 것인데 오라클은 인스턴스 기동 후 처음에는 전체 Shared Pool의 50% 가량은 Free List에 올려놓지 않고 아예 숨겨두었다가 앞서와 같이 도저히 피할 수 없는 순간이 되면 조금씩 해제 시켜 사용하도록 한다. 그야말로 메모리의 조각화를 최소화하기 위한 오라클의 눈물 나는 노력이라고 할 수 있을 것이다. 물론 이 영역까지 다 소모한 후에 flush를 통해서도 필요한 Chunk를 확보할 수 없는 상황이 되면 결국 ORA-4031 에러가 발생할 것이다.
많은 이들이 Shared Pool의 남아있는 Free memory의 크기가 작으면 shared_pool_size를 증가시켜주어야 한다고 믿고 있는데 이는 잘못된 것이다. Shared Pool은 정보의 재사용을 위해 운영하는 것이므로 SQL 실행이 끝났다고 해서 해당 Chunk를 Free List로 반납하지 않는다. 즉, Free Memory가 남아있는 한 계속 소모 시키는 방식으로 사용되므로 오랜 시간동안 운영되어온 시스템에서 Shared Pool의 Free Memory가 매우 적게 남아 있는 것은 그 자체로는 문제가 되지 않으며, 오히려 피크타임이 지난 후에도 많은 양의 Free Memory가 남아있다면 이는 Spare Free 메모리도 다 소모하지 않은 상태로서 불필요하게 많은 메모리가 할당되어 낭비되고 있음을 의미한다. 더구나, Shared Pool 크기가 지나치게 크면 Free Memory를 다 사용할 때까지의 기간이 연장되는 효과는 얻을 수 있겠지만, 시간이 지날수록 Memory의 조각화가 더욱 심해지고 Free List의 길이가 길어져 Free Chunk의 검색과 할당에 걸리는 시간이 지연되므로 오히려 성능이 악화되는 결과를 초래할 것이다.
또한, 메모리 조각화에 따른 영향을 줄이기 위해 오라클은 5000 bytes가 넘는 큰 사이즈의 Chunk만을 위해 전체 Shared Pool의 5% 정도를 따로 관리하는 방법을 사용하고 있는데, 경험적으로 보면 이 공간은 거의 사용되지 않고 버려지고 있는 경우가 많다. 이는 V$SHARED_POOL_RESERVED 뷰의 USED_SPACE 값을 확인해 보면 알 수 있으며, 5000 bytes 이상의 large chunk가 거의 요구되지 않는 환경에서는 오히려 이 크기를 줄여주는 것이 나을 것이다.

■ Shared Pool 관련 wait
Shared Pool과 관련하여 흔히 발생하는 Wait은 라이브러리 캐쉬 오브젝트에 대한 동시 액세스와 메모리 할당에 따른 관련 Lock 또는 Latch에 대한 경합이 대부분이며, 구체적인 이름은 다음과 같다. (Latch free 이벤트시 괄호 안의 관련 latch 이름은 v$session_wait의 p2값과 v$latchname의 latch#를 조인하여 얻어낼 수 있다. 1회 SQL 참조)
Latch                                   Lock
latch free ( library cache )           library cache lock, library cache pin
latch free ( library cache load lock)  library cache load lock
latch free ( row cache objects )       row cache lock
latch free ( shared pool )             -      
Library cache lock, library cache pin, library load lock은 각각 특정 라이브러리 캐쉬 오브젝트에 대한 검색이나 변경 및 실행 또는 로드 시에 대상 오브젝트에 대해 할당되며, 이러한 Locking 작업은 library cache latch와 library cache load lock latch의 관할 하에 처리된다. Shared pool latch는 Free List나 LRU List를 검색하거나 메모리를 할당하는 작업에 사용되며, row cache lock과 row cache objects latch는 Data dictionary cache 오브젝트에 대한 동시 액세스를 제어하는데 사용된다.
Latch의 개수는 시스템 전체적으로 하나 또는 제한된 개수가 존재하는 것이고 Lock은 대상 오브젝트 각각 대해 할당되는 것이므로, 엄밀하게 말해서 Lock에 대한 경합은 직접적으로는 특정 라이브러리 캐쉬 오브젝트에 대한 동시 액세스로 인해 유발되는 것인 반면에, Latch에 대한 경합은 시스템 전체적으로 관련 오퍼레이션(즉, SQL 파싱) 자체가 지나치게 많이 발생하거나, 짧은 시간 내에 처리되지 못함으로 인해 유발되는 것이라고 구분해볼 수 있다. 그러나, 결국 이 모든 경합은 근본적으로 Shared Pool의 조각화(Fragmentation)에 따른 문제가 주된 원인이며 다시 이러한 조각화는 요청되는 SQL들이 공유되지 못하고 지속적으로 새롭게 파싱되고 메모리가 할당됨으로 인해 발생하는 것이다. 따라서, 이러한 문제를 해결하는 가장 효과적인 방법은 Literal SQL을 바인드 변수를 사용하도록 수정하거나, SQL작성 표준을 마련하고, HOLD_CURSOR/ RELEASE_CURSOR, SESSION_CACHED_CURSORS, CURSOR_SPACE_FOR_TIME, CURSOR_SHARING 등의 파라미터를 활용하는 등의 방법을 통해 SQL의 공유도를 높여주는 것이며, 또한 자주 사용되는 PL/SQL에 대해서는 DBMS_SHARED_POOL 패키지를 사용하여 메모리에서 Flush되지 않도록 보존하는 등의 조치를 취해주면 도움이 될 것이다. SQL의 수정이 어려운 환경이거나 시스템에 요청되는 SQL의 절대량이 확보된 메모리 공간에 비해 많은 상황이라면 주기적으로 피크타임을 피해 Shared Pool을 직접 Flush(alter system flush shared_pool 명령을 사용한다.) 시켜주는 것도 권장할 만한 관리 방법이다. 많은 이들이 우려하는 바와는 달리 Shared Pool을 직접 flush 시키는 것이 심각한 성능상 문제를 야기하지는 않으며 특히 중요한 패키지나 SQL cursor, Sequence 등이 keep되어 있는 경우라면 더욱 그러하다.
가끔 버그를 포함한 특수한 상황에서 특정 라이브러리 캐쉬 오브젝트에 대한 lock이 장시간 해제되지 못하고 있는 경우도 있는데 이때는 X$KGLLK 뷰를 조회하면 library cache lock에 대한 holder/waiter를 확인하여 조치할 수 있다. 또한, Row cache lock에 대한 경합은 Locally managed tablespace를 도입하거나, DML이 빈번한 테이블에 대한 인덱스의 개수를 줄여주는 등의 조치를 통해 완화될 수 있을 것이다.
부연하자면, Shared Pool과 관련된 Wait는 특정 오브젝트 자원에 대한 경합에 의해 발생하기 보다는 애플리케이션의 설계, 보다 단순화시켜 표현하면 Literal SQL에 의한 메모리 조각화에 의해 발생하는 경우가 많다. 따라서, Shared Pool관련 Wait가 많이 발생하여 오라클이 그로 인한 성능상의 문제를 드러낼 때 눈에 띄는 하나의 주범을 찾아내려는 노력은 별 효과를 거두지 못하는 경우가 많으며, 그러한 시점에 DBA가 즉각적으로 취할 수 있는 조치로는 직접 Shared Pool을 Flush 시키는 정도가 있을 것이다. 결국, 평소에 꾸준한 모니터링을 통해 Shared Pool의 적절한 크기와 관련 파라미터 값을 찾아가는 것, 그리고 무엇보다 애플리케이션 측면에서 튜닝 및 수정 작업을 진행함으로써 성능문제를 사전에 예방하는 것이 최선이다.
[3] Buffer Cache 관련 Wait
■ Buffer Cache 구조
Buffer Cache의 기본적인 기능은 여러 프로세스에 의해 공통으로 자주 액세스 되는 데이터베이스 블록을 메모리에 캐쉬하여 물리적인 디스크 IO를 최소화함으로써 더 빠른 액세스 속도를 제공하기 위한 것이다. 복잡한 설명은 생략하고, Buffer Cache 의 기본구조를 이해하기 위한 몇 가지 핵심 용어들을 간단히 정리해 보도록 하겠다.
▷ Buffer header
모든 버퍼 블록들은 각자의 buffer header를 통해 액세스되고 관리된다. 즉, 메모리에 캐쉬된 특정 데이터 블록에 대한 액세스는 먼저 해쉬 알고리즘을 통해 cache chain 상의 buffer header를 찾고 해당 buffer header에 기록된 데이터 블록의 메모리상 주소를 찾아가 원하는 정보를 읽는 방식으로 이루어진다. Buffer header에 기록되는 주요정보는 다음과 같으며 Buffer header의 내용은 V$bh 뷰를 통하여 조회해볼 수 있다.
     - 메모리상에서의 해당 버퍼블록의 주소
     - 해당 버퍼 블록(실제로는 버퍼헤더)가 포함되어 있는 hash chain
     - LRU, LRUW, CKPTQ와 같은 리스트상에서의 해당 버퍼블록의 위치
     - 해당 버퍼블록에 대한 User, Waiter와 상태를 나타내는 각종 Flag
▷ Hash Buckets/ Hash Chains
Buffer Cache의 모든 블록은 해쉬 알고리즘을 통해 관리된다. 곧, 데이터 블록의 DBA, Class 값으로 Hash Function을 적용하여 해당 블록이 속하는 hash buckets을 할당하며, 동일한 hash buckets에 할당되는 데이터 블록의 버퍼헤더들은 linked list형태로 hash chain을 이루게 된다. Hash buckets/hash chains는 특정 데이터 블록을 찾아가기 위한 수단을 제공한다. 각각의 hash buckets에는 자신에 속한 hash chain을 보호하기 위한 latch(cache buffers chains)가 할당된다.
▷ LRU
LRU는 두개의 리스트, 즉 LRUW와 LRU 리스트의 쌍으로 구성된다. LRUW(LRU Write list)는 dirty list와 같은 말이며, 수정되어 디스크에 반영되어야 할 블록들의 리스트이다. LRU(Least recently used list)는 LRUW에 올라가지 않은 나머지 버퍼 블록들이 등록되어 있다. Buffer cache 상의 버퍼블록은 반드시 LRU나 LRUW 둘 중의 하나에 등록되며, 두 리스트에 동시에 포함되는 경우는 없다. LRU는 Free Buffer를 찾기 위한 수단을 제공한다. 경합을 피하기 위해 버퍼캐쉬 블록들을 여러 개의 LRU쌍으로 나누어 관리할 수 있으며, 각 LRU리스트를 보호하기 위해 Latch(Cache buffers lru chain)가 하나씩 할당된다.
■ Buffer Cache 운영규칙
▷ 메모리상의 특정 버퍼블록을 찾아가거나, 특정 블록이 메모리에 캐쉬 되어 있는지를 확인하기 위해서 오라클은 hash bucket/hash chain 구조를 사용한다.
▷새로운 데이터블록을 디스크로부터 메모리로 읽어 들이기 위한 free buffer를 확보하기 위해 오라클은 LRU 리스트를 사용한다.
▷ 버퍼블록은 LRU나 LRUW 둘 가운데 하나에 등록된다.
▷ 하나의 블록에 대해 시간대가 다른 여러 개의 복사본이 존재할 수 있으며, 그 가운데 오직 CURRENT 버퍼만이 변경될 수 있다.
▷하나의 버퍼블록은 한번에 오직 하나의 프로세스에 의해서만 변경될 수 있다.
■ Buffer Cache 관련 Waits
버퍼캐쉬와 관련되어 흔히 발생하는 대표적인 Wait 이벤트는 다음과 같다.
▷ buffer busy waits
여러 세션이 동시에 같은 블록을 읽으려고 하거나 여러 세션이 같은 블록에 대한 변경작업이 완료되기를 기다리고 있는 경우에 발생하며, 특정 블록에 대한 경합을 해소하기 위한 조치는 블록의 유형에 따라 달라진다. Data block에 대한 경합이 많은 경우는 Pct free나 Pct used 값을 사용하여 블록 당 로우수를 줄이거나, 특정 블록에 로우 입력이 몰리는 구조의 인덱스(right-hand-index)일 경우는 reverse key index의 사용을 검토하는 등의 방법이 있으며, segment header의 경합이 많은 경우는 freelist 수를 늘리거나 Extent의 크기를 증가시키는 등의 방법이 있고, undo header나 undo block에 대한 경합은 롤백세그먼트의 개수나 크기를 증가시키는 것이 전형적인 조치 방법이다. v$waitstat과 x$kcbfwait을 이용하며 Class 또는 file별로 wait 발생상황을 판단할 수 있다.
▷ free buffer waits/write complete waits
DBWR가 dirty buffer를 write하는 동안 서버 프로세스가 대기하고 있는 경우 발생한다. 곧, 너무나 많은 dirty buffer가 생겨나거나 DBWR의 쓰기 속도가 충분히 튜닝 되지 못한 경우에 발생한다. 점검 포인트는 물리적 디스크의 속성(stripe size, layour, cache size) 최적화, Raw device의 활용, Async IO나 multi-DBWR(db_writer_processes) 활용여부 등이다.
위와 같은 버퍼 블록에 대한 경합 역시 비효율적인 실행계획을 통해 수행되는 애플리케이션에 의하여 불필요하게 많은 블록이 메모리로 올라오는 것이 원인일 경우가 많으므로 경합이 빈번한 블록이 속하는 테이블/인덱스 명을 찾아낼 수 있다면 관련 SQL을 찾아내어 보다 효과적인 튜닝작업이 이루어질 수 있을 것이다. v$session_wait의 p1,p2 컬럼에 각각 file#, block#값을 표시하여 주므로 이 값을 이용하여 아래의 SQL문으로 현재 어떤 오브젝트에 대하여 해당 wait가 발생하고 있는지를 추적할 수 있다. ( 1회에 소개한 SQL문에서는 Additional Info 값을 참조. )
     select segment_name, segment_type
     from dba_extents
     where file_id = :file#
     and :block# between block_id and block_id + blocks -1

▷ cache buffers chains latch
SGA내에 캐쉬된 데이터블록을 검색할 때 사용된다. 버퍼캐쉬는 블록들의 chain을 이루고 있으므로 각각의 chain은 이 Latch의 child들에 의해 보호된다. 이 Latch에 대한 경합은 특정 블록에 대한 대량의 동시 액세스가 발생할 때 유발된다. 애플리케이션을 검토해 보아야 한다.
Ø cache buffers lru chain latch
버퍼캐쉬의 버퍼를 LRU 정책에 따라 이동시켜야 할 필요가 있는 경우 프로세스는 이 Latch 획득하게 된다. 이 Latch에 대한 경합은 Multiple buffer pool을 사용하거나 DB_BLOCK_LRU_LATCHES 를 증가시켜 LRU Latch의 개수를 늘려서 해소할 수 있다. SQL문을 튜닝하면 해당 프로세스에 의해 액세스 될 블록의 수가 줄어들 것이므로 당연히 효과를 거둘 수 있다.
위와 같이 버퍼캐쉬를 관리하는 Latch에 대한 경합은 경합이 집중되는 특정 Child Latch에 의해 관리되는 버퍼블록을 찾아 해당 블록이 속한 세그먼트 정보를 알아낸다면 보다 효과적인 조치가 가능할 것인데, latch free wait일 경우 v$session_wait의 p1raw 값이 해당 Latch address를 의미한다. 이 값을 x$bh의 hladdr 값과 조인하면 관련 오브젝트 이름을 추적해볼 수 있다.
     select file#, dbarfil, dbablk, obj, o.name
     from x$bh bh, obj$ o
     where bh.hladdr = :latch_address
     and bh.obj = o.obj#;

5] Redo buffer 관련 Wait
■ Redo buffer 구조
오라클 리두 구조의 핵심은 모든 트랜잭션 정보를 OS 파일에 기록해 둠으로써 시스템 장애가 발생해도 트랜잭션 단위의 일관성을 잃지 않고 데이터베이스를 복구할 수 있도록 하겠다는 것이다. 리두버퍼(redo buffer)는 이처럼 데이터베이스에 가해진 모든 변경내역을 파일에 기록 하기 위해 잠시 사용되는 메모리 영역이며 리두버퍼에 기록된 리두 정보는 다시 리두로그 파일에 기록되어짐으로써 향후 시스템 복구 작업이 필요할 때에 사용하게 된다. 오라클의 리두 구조를 이해하기 위한 핵심적인 개념을 간단히 정리해보면 다음과 같다.
데이터베이스에 대한 변경내역은 블록단위로 저장된다. 물론 변경되는 모든 블록의 복사본을 통째로 저장하는 것은 아니고 블록별로 어떠한 오퍼레이션을 수행하는가, 그리고 그러한 블록별 오퍼레이션을 어떠한 순서로 수행하는가를 기록한다. 이러한 블록별 단위액션을 change vector라고 부르며 change vector가 순차적으로 모여 하나의 의미 있는 redo record가 된다. 리두로그는 시스템내의 모든 프로세스들에 의해 생성되는 redo record를 SCN 순서대로 저장해놓은 것이다. 이때 리두로그에 기록되는 내용에는 테이블이나 인덱스 등의 데이터 블록 뿐만 아니라 UNDO 블록 또는 UNDO 세그먼트 헤더블록에 대한 변경내용을 포함하는 모든 버퍼캐쉬 블록에 대한 변경내역이 대상이 된다.
리두 정보는 항상 실제 변경작업보다 먼저 보관되어야 어떤 상황에서도 복구가 가능해진다. 따라서 트랜잭션을 수행하는(데이터베이스 블록에 변경을 가하는) 프로세스는 우선 자신의 메모리 영역 내에서 수행하고자 하는 작업에 대한 리두 레코드를 만들며, 이를 먼저 로그버퍼에 기록하고 난 후에 실제 버퍼블록에도 리두 레코드에 담긴 내용을 따라 적용하게 된다. 또한 같은 이유로 오라클은 변경된 버퍼캐쉬 블록을 디스크에 기록하기 전에 먼저 관련된 로그버퍼를 로그파일에 기록하는 작업을 처리하게 된다. 따라서, 리두 버퍼 또는 리두 파일 (아카이브 파일을 포함해서)에 대한 쓰기 작업에 병목이 생기면 시스템에 대한 모든 작업 수행이 대기 상태로 빠지게 될 것이다.
트랜잭션 커밋을 요청한 프로세스는 우선 해당 트랜잭션에 대한 로그버퍼가 리두로그 파일에 기록되는 작업이 완료된 후에야 커밋 완료 메세지를 받을 수 있다. 그렇게 함으로써 버퍼캐쉬 변경내역을 모두 디스크에 반영하지 않고도 시스템의 비정상 종료시 리두파일에 저장된 리두 레코드로부터 커밋 트랜잭션을 보존할 수 있게 된다.
■ 리두 버퍼관련 Wait 이벤트
일반적으로는 로그버퍼 관련해서 심각한 Waiting이 발생하는 경우는 드물지만, 가끔 볼 수 있는 리두 관련 Wait 이벤트로는 다음과 같은 것들이 있다.
▷ Log file parallel write
LGWR가 OS에 리두 버퍼를 로그파일에 기록하도록 요청해 둔 상태에서 대기하고 있는 이벤트이다. 이 경우에는 DML 작업시 nologging 옵션 등을 사용하여 시스템에서 발생하는 리두 레코드의 절대량을 줄이거나 하드웨어적으로 DISK IO를 개선시켜주는 것이 방안이다. 
▷Log buffer space
프로세스가 로그버퍼를 할당하기 위해 대기하는 이벤트인데 LGWR가 로그버퍼를 비우는 것보다 더 빠른 속도로 프로세스들이 리두 레코드를 생성하고 있다는 것을 의미한다. 로그버퍼의 크기를 늘려주거나, DISK IO의 속도를 개선시켜 주어야 할 것이다. 로그버퍼는 로그파일에 대응되는 블록이 맵핑이 된 후에 사용될 수 있으므로 로그 스위치 발생시에도 log buffer space 이벤트에 대한 대기가 발생할 수 있다. 로그 스위치가 너무 잦다면 리두 로그 파일의 크기를 증가시켜주는 것이 좋다.
▷ Log file sync
프로세스가 커밋이나 롤백을 수행할 경우 우선 LGWR에게 해당 트랜잭션까지의 로그버퍼를 Write하도록 요청하게 되는데 이때 사용자 프로세스는 LGWR가 쓰기 작업을 완료할 때까지 log file sync 이벤트를 대기하게 된다. 버전 8i 이전에서는 DBWR가 쓰기 작업을 수행하다가 아직 관련 로그버퍼가 파일에 쓰여지지 않을 경우에도 LGWR에 쓰기를 요청하고 log file sync 이벤트에 대기하였으나 8i 이상에서는 log file sync에 대기하는 대신 deferred write queue에 등록한다. 따라서 버전 8i 이상에서 log file sync 이벤트는 사용자 프로세스에 의해 요청되는 커밋, 롤백 처리 시에 발생하며 결국, 시스템 전체적으로 커밋, 롤백이 지나치게 자주 수행되거나 상대적으로 LGWR의 쓰기 속도가 느린 것이 원인일 것이다. 또는, 로그 버퍼가 너무 커서 LGWR가 백그라운드로 flush 시켜주기 전( 보통 3초 간격 및 1/3 이상의 로그버퍼가 찬 경우)에 커밋에 의한 쓰기 요청이 이루어지므로 커밋 시점에 써야 할 양이 많아 대기시간이 길어지는 경우도 있는데 이 경우엔 리두 버퍼의 크기를 오히려 줄여주어야 할 것이다. 또는, LGWR wait for redo copy 이벤트가 많이 나타난다면 redo copy latch가 너무 많아 LGWR이 사용자 프로세스가 버퍼 쓰기 작업을 마칠 때까지 기다리는 일이 잦은 경우를 뜻하며 이 경우엔 _LOG_SIMULTANEOUS_COPIES 파라미터를 사용하여 copy latch의 수를 줄여주는 조치가 필요할 것이다.
시스템에 따라서 언급한 외의 다양한 이벤트 대기와 원인이 존재할 수 있고, 더구나 버전에 따라 redo copy latch와 redo allocation latch를 포함한 리두 운영 방식상 상이한 부분이 많이 존재하여 그에 따른 추가적인 튜닝요소가 있으나 이 글에서는 지면 관계상 8i를 기준으로 간략히 정리해 보았다.
[6] Top SQL 튜닝하기 (맺음)
■ Top SQL 튜닝의 필요성
지난 회까지 실시간 Wait Event 모니터링과 이벤트별 원인분석에 대해서 간단히 살펴보았다. 일시적 성능장애 시 재빨리 원인을 찾아내는 것도 중요하지만 보다 바람직한 것은 이러한 성능문제를 사전에 최대한 예방하는 것임은 두말할 필요도 없다. 오라클 성능문제를 다루는데 있어 강조하고 싶은 한가지는 시스템 자원의 배분을 변경하거나 증가를 고려하기 전에, 불필요한 작업을 최소화함으로써 자원요구 횟수와 자원점유 시간을 줄여주는 노력이 선행되어야 한다는 점이다. Wait Event에 대한 모니터링과 분석이 DBMS의 자원에 대한 경합과 관련된 성능문제를 파악하는데 유용한 방법임에 틀림없지만, 이 같은 정보는 데이터베이스 혹은 그 하위 레벨의 구조적 비효율성을 드러내어줄 뿐 애플리케이션 레벨의 문제를 직접적으로 알려주지는 않는다. 간단히 말하자면, DBMS 튜닝을 위해 정성을 쏟기 이전에 애플리케이션 튜닝에 더 많은 투자를 하라는 것이다. 이런 관점에서 DBA가 비교적 손쉽게 수행할 수 있는 것이 오라클 메모리로부터 악성 SQL을 추출하여 튜닝하는 Top SQL 튜닝이다.
■ Top SQL 추출기준
사용자로부터 요청되어 오라클 내에서 처리되는 모든 SQL은 오라클의 공유 메모리 영역 가운데 shared pool내에 캐쉬 되어 지며 이렇게 캐쉬 되어 있는 SQL과 관련 통계정보는 V$SQL 또는 V$SQLAREA 뷰를 통해서 조회할 수 있다. 이때, Top SQL을 추출하는데 중요하게 사용되는 항목은 buffer_gets, disk_reads, executions, rows_processed 등이며 일반적으로 아래와 같은 기준으로 Top SQL을 추출한다.
▷ Buffer gets 수치가 높은 SQL
Buffer gets은 해당 SQL이 처리되는 과정에서 액세스한 메모리 블록의 수(Logical IO)를 의미한다. 물론 이 값이 높다고 해서 무조건 악성 SQL임을 의미하는 것은 아니다. 즉, 이러한 SQL들 중에는 실제로 요구되는 작업량이 많아서 액세스 블록수가 많은 SQL도 있을 것이며 불필요한 처리를 수행하느라 액세스 블록수가 많은 SQL도 있을 것이다. 어느 경우이든 이 SQL들이 현재 오라클 서버에 부하를 많이 유발하고 있는 SQL들이라는 것만은 분명하며 사소한 비효율적 요소에 의해서도 서버에 큰 영향을 미칠 잠재적인 가능성이 있는 SQL들이므로 일차적으로 점검해 볼 필요가 있다.
악성 SQL여부를 판단하기 위한 Buffer gets의 수치에 절대적인 기준은 없으며 시스템의 데이터 규모와 트랜잭션량에 따라 다르다. buffer gets값을 기준으로 역순으로 정렬한 후 패턴을 살펴 적절한 추출기준을 선택하는 것이 좋을 것이다. 만일, 상위 몇 개의 SQL들과 나머지 SQL들 간의 buffer gets의 편차가 매우 크게 나타난다면 상위 몇 개의 SQL에 대해서만 튜닝을 수행해 주어도 큰 효과를 볼 수 있을 것이다. 일반적으로 시스템에서 수행되는 SQL 가운데 심각한 부하를 야기하는 SQL은 소수에 불과한 경우가 많으며 뒤에 기술될 다른 조건들과 조합하여 최대 Top 50건 정도를 추출하여 효율성을 검증하고 튜닝을 통해 개선하는 작업을 수행하여 주면 충분하다.
▷Buffer gets/Execution 수치가 높은 SQL
SQL의 단위 수행당 buffer gets 수치를 의미한다. 단위 수행당 buffer gets 값이 높다는 것은 해당 SQL의 처리가 비효율적일 가능성이 높음을 의미한다. 액세스 블록수가 비정상적으로 많다는 것은 rows_processed 값과 비교하여 상대적으로 평가되어야 할 부분이다. 실제로 반환하는 로우수가 매우 많은 배치성 SQL이거나 혹은 반환되는 로우수가 1건이라도 Group Function이 사용된 Summary성 SQL이라면 처리과정에서 많은 수의 블록을 액세스하는 것은 불가피하며 이 자체가 문제가 될 수는 없기 때문이다.
▷Execution 수치가 높은 SQL
Executions는 해당 SQL이 수행된 횟수를 의미한다. 수행횟수가 잦은 SQL은 buffer gets가 높을 경우가 많다. 일반적으로 십만 ~ 백만 회 이상 빈번하게 수행되는 SQL이라면 buffer gets/executions 값이 2자리 수 이내의 값을 나타내어야 정상이며 단위 수행당 속도는 0.1초 이내로 매우 빨라야 한다. 따라서, 이러한 SQL의 경우 SQL단위로 보면 튜닝의 효과를 체감하기도 어렵고 필요성을 느끼지 못할 수도 있으나 튜닝을 통해 아주 적은 차이라도 개선을 가져올 수 있다면 시스템 전체적인 관점에서는 매우 큰 효과를 가져다 줄 수 있다는 점이 중요하다. 하루에 백만번 수행되는 SQL에 대하여 0.01초를 개선한다면 시스템 시간으로 하루에 일만초를 절약한 셈이 될 것이다. 이러한 SQL에 대해서는 현재 빠르게 수행되고 있다고 해도 더 빠르게 처리할 여지가 없는지 점검하고 가능한 모든 방안을 동원하여 개선시키도록 노력해야 한다.
▷disk_reads 수치가 높은 SQL
disk_reads는 SQL이 처리되는 과정에서 물리적인 IO를 통해 액세스한 블록의 수를 의미한다. 물리적 IO의 발생여부는 원하는 블록이 메모리에 캐쉬되어 있는지 여부에 따라 달라지므로 수행되는 횟수와 수행되는 시간대의 데이터베이스 캐쉬 상황에 따라 유동적이라고 할 수 있다. 그러나, buffer gets의 값과 비교하여 disk_reads의 비율이 높은 SQL은 Full Scan을 수행하는 SQL일 가능성이 큰데 그 이유는 Full Scan을 통해 액세스되는 블록들은 기본적으로 DB buffer Cache의 LRU 알고리즘에 의해 관리되지 않으므로 작업 후에 곧바로 메모리로부터 밀려나 버릴 가능성이 높기 때문이다. 반면에 인덱스를 통하여 액세스하는 경우, 일상적으로 액세스되는 테이블에 대해서는 인덱스의 root block과 branch block은 항상 메모리에 캐쉬 되어 있을 확률이 높으므로 물리적 IO를 유발하는 비율이 낮을 수 밖에 없다.
■ Top SQL 추출기준
글을 맺기 전에 마지막으로 언급하고 싶은 것 하나는, 문제가 발생했을 때 문제의 원인이 bug로 인한 것일 가능성을 항상 염두에 두어야 헛된 고생을 덜한다는 것이다. 오라클도 사람이 만든 프로그램이므로 버그가 없을 수 없으나 다행히 오라클의 버그 및 패치 관리는 매우 훌륭한 편이다. 오라클 메타링크를 활용하여 유사한 문제가 보고된 적은 없는지 관련 버그에 대한 정보는 없는지 살펴보아야 하며, 평소에 정기적으로 패치 및 버전 관리를 해주는 것이 바람직하다.
오라클 시스템을 운영하다 보면 현실에서는 다양한 문제가 복잡하게 얽혀 나타나므로 명백한 원인을 파악하기가 쉽지 않을 때가 많지만, 운영 시 자신의 시스템에서 자주 발생하는 Wait 패턴 또한 분명히 존재하므로 굵은 가지들부터 하나씩 이해하고 해결해 나가다 보면 오라클이 우리가 보인 애정에 보답해줄 날이 올 것이라 믿는다. 지면 관계상 OPS(RAC) 관련 Wait을 비롯한 기타 Wait 이벤트에 대해 다루지 못한 점, 그리고 각 Wait 이벤트별로 좀더 친절한 설명과 사례를 제시하지 못한 점이 아쉬움으로 남지만, 누구든 아주 작은 것 하나라도 이 글을 통해 새로이 얻을 수 있었다면 그 이상 바랄 것은 없다.

[Tuning] Oracle Tuning

최적화의 중요성 (The Importance of Tuning)


Oracle8은 상당히 최적화되어 있는 소프트웨어 제품이다. 종종 튜닝을 통해 시스템 퍼포먼스를 최적화 하고 자료 병목현상을 막아야 한다. 비록 이 장에서는 단일 프로세서 (single-processor) 시스템을 기준으로 기술되어 있지만, 오라클의 병렬 옵션을 이용하는 시스템에 관한 대부분의 퍼포먼스 튜닝 팁도 여기서 제공된다.

시스템을 튜닝하기 전에 (Before Tuning the System)


시스템을 튜닝하기 전에, 다음장의 "LINUX Tools"에 기술된 리눅스 툴의 정상적인 동작법을 익혀 두어야 한다.
See Also:
Oracle8 병렬 서버 개념과 관리 (Oracle8 Parallel Server Concepts and Administration).
Oracle8 Tuning.


LINUX Tools


리눅스는 데어터베이스 접근의 퍼포먼스를 관찰하고 요구되는 데어터베이스를 결정하는 등의 퍼포먼스 모니터링 툴을 제공한다.
oracle process들의 통게를 제공할 뿐만아니라, 이들은 CPU 이용률, 인터럽트, 스와핑, 페이징, 그리고 전체 시스템을 위한 context switching 등에 대한 통계도 제공한다.
See Also:
리눅스 톨들은 운영체계 문서에 기술되어 있다. 


vmstat


vmstat 유틸리티는 리눅스 상에서 process, virtual memory, disk, paging, CPU 활동도 등을 알려준다. 다음 문장은 시스템 활동도에 대한 요약을 5초 간격으로 8번 보여 준다:
% vmstat -n 5 8

vmstat 명령의 예제 출격이 다음 Figure 3-1에 보여주고 있다.
w column (procs 아래의)은 swap out(디스크로 쓰여진) 되어져 나간 프로세스의 수를 의미한다. 만약 값이 0이 아니라면, 스와핑이 일어 났다는 것을 의미하여 여러분의 시스템은 메모리 부족이 있다는 것을 의미한다. si와 so columns은 각각 초당 swap-ins과 swap-outs의 수를 표시한다. Swap-outs은 거의 항상 0일 것이다.

Figure 3-1 Output from the vmstat -n command

procs memory swap io system cpu r b w swpd free buff cache si so bi bo in cs us sy id 0 0 0 16124 964 524 29904 2 14 13 21 208 3 9 4 87 1 0 0 16124 648 524 30140 0 0 1 0 806 1763 5 8 87 1 0 0 16124 608 524 29904 0 0 0 0 856 1894 5 7 87 0 0 0 16124 612 524 29624 0 0 0 5 734 1586 5 8 88 2 0 0 16124 1656 520 28296 0 0 221 0 687 1395 14 10 77 0 0 0 16124 840 520 29060 0 0 38 0 621 1287 3 4 93 0 0 0 16124 856 520 29196 0 0 0 0 647 1395 4 6 91 1 0 0 16124 708 520 29288 0 0 1 0 618 1287 3 4 93

free


free utility는 스왑 공간 이용률에 관해 알려준다. 스왑공간이 모자랄 경우 시스템이 중단되던지 응답시간이 느려지게 된다.

SQL Scripts


utlbstat 와 utlestat SQL Scripts


utlbstat 와 utlestat SQL scripts는 오라클 데이터베이스 퍼포먼스와, Shared Global Area (SGA) 자료구조를 모니터 하는데 이용되는 프로그램이다. 이 스크립트에 관한 정보는 Oracle8 Server Tuning를 참조하기 바란다. 리눅스상에서, 이 스크립트는 $ORACLE_HOME/rdbms/admin/에 위치해 있다.

메모리 관리 튜닝하기 (Tuning Memory Management)


얼마나 많은 메모리 공간이 필요한지 결정하기 위해서 스와핑과 페이징 공간을 튜닝하는 것으로 메모리 튜닝과정을 시작한다.
오라클 buffer manager는 좀더 자주 접근하는 자료가 좀더 오랫동안 캐시 되어 있도록 되어 있다. Buffer manager와 buffer cache를 모니터링 하는 것은 오라클 퍼포먼스에 상당한 영향을 미친다. 여러분들의 시스템에서 적절한 오라클 버퍼 사이즈는 전반적인 시스템의 부하와 다른 응용프로그램들에 대한 오라클의 상대적인 우선순위의 정도에 달려 있다.

충분한 스왑 공간을 할당하라


스와핑은 리눅스에서 상당한 overhead의 원인이 되기 때문에 최소화 시켜야 한다. 스와핑을 점검하기 위하여 다음 유틸리티들을 이용한다.
free 또는 vmstat -n
만약 여러분들의 시스템이 스와핑이 일어나고 메모리를 유지할 필요가 있다면:
  • 불필요한 시스템 데몬 프로세스 응용프로세스 등을 실행시키지 마라.
  • 어느정도 빈 메모리를 확보하기 위하여 데이터베이스 버퍼 크기를 줄여라.
  • 리눅스 파일 버퍼의 수를 줄여라. 특히 raw 장치를 이용하고 있다면.
스왑공간을 추가하는 것은 구현된 리눅스에 따라 조금씩 다르다. 리눅스상에서 얼마나 많은 스왑 공간이 현재 남아 있는지를 결정하려면 free 유틸리티를 이용한다. 보다 자세한 정보는 리눅스 문서를 참조하기 바란다.
스왑공간의 크기는 여러분들의 RAM 메모리의 2-4배로 시작하라. CASE, 오라클 응용 프로그램 또는 오라클 오피스를 이용하려면 좀더 많은 공간을 필요로 한다. 스왑공간의 사용을 모니터 하다가 필요하다면 스왑공간을 증가시키도록 한다.

Control Paging


프로그램이 실행하기 위해서 전체 프로그램이 모두 다 메모리에 상주하지는 않기 때문에, 페이징(Paging)은 스와핑만큼 심각한 문제로 나타나지는 않는다. 약간의 page-outs은 여러분들의 시스템 퍼포먼스에 유의하게 영향을 미치지는 않는다.
과다한 페이징을 발견하기 위해서, 반응속도가 빠를때 measurements를 실행시켜 두었다가 반응속도가 느려 졌을 때와 비교하도록 한다.
페이징을 모니터하기 위해서 vmstat 또는 free를 이용하도록 하라. vmstat 출력 중 다음 컬럼은 중요하다:
  • vflt/s는 page fault가 나타난 주소변환의 수를 가르킨다. Address translation faults는 프로세스가 참조하고자 하는 주소페이지가 메모리 내에 있지 않을 때 발생한다.
  • rclm/s는 page-out activity에 의해 free list에 추가되고 개정된 유효한 페이지를 말한다. 이 값은 대체로 0이 된다.
만약 여러분들의 시스템이 너무 많은 page-out activity를 보이면, 다음과 같은 해결법을 생각해 보라:
  • 메모리를 증설하라.
  • 일부 작업을 다른 시스템으로 이동하라.
  • 여러분들의 커널을 메모리를 적게 사용하도록 설정하라.

SGA를 단일 공유메모리 세그먼트내에 유지하라.


비록 퍼포먼스상의 이득은 미미하지만, 충분한 공유메모리를 확보하도록 설정하지 않고서는 데이터베이스를 시작할 수 없다.
여러분들은 리눅스 커널의 공유메모리를 증가시키도록 다시 재설정할 필요가 있다. 공유메모리를 위한 리눅스 커널 인자는 SHMMAX, SHMMNI, 그리고 SHMSEG이다. SGA가 단일 공유메모리 세그먼트내에 상주하도록 하려면 SHMAX를 4294967295 (4 GB)로 설정해야 한다.
SGA의 크기는 다음과 같은 단계를 거쳐서 측정할수 있다:
  1. DB_BLOCK_BUFFERS 과 DB_BLOCK_SIZE를 곱하라.
  2. Step 1의 결과를 SORT_AREA_SIZE에 더하라.
  3. Step 2의 결과를 SHARED_POOL_SIZE에 더하라.
  4. Step 3의 결과를 LOG_BUFFER에 더하라.
공유메모리의 상태를 모니터링하기 위하여 리눅스 유틸리티인 ipcs를 이용할 수도 있다.
참조 :
Oracle8 Installation Guide for LINUX의 chapter 2에 있는 "오라클을 위한 리눅스 커널의 설정".


디스크 입출력 튜닝하기 (Tuning Disk I/O)


I/O 병목현상은 가장 쉽게 찾을수 있는 실행상의 문제점이다. 균형잡힌 I/O는 결국 전체 모든 이용가능한 디스크상에서 디스크 접근 시간을 줄이는 것이다. 작은 데이터베이스와 병렬 질의 옵션을 사용하지 않는 곳에서는, 데이타 파일이 다를 경우 테이블스페이스가 이용가능한 디스크 공간내에 분산되어 있는지 확인하도록 한다.

쓰기 대역폭을 넓히기 위해 데이터베이스 Writer를 튜닝하라.


오라클은 database writer (DBWR)가 병목현상을 가지지 않도록 해법을 요구한다:
  • 비동기 입출력(asynchronous I/O)을 이용하라.
  • I/O slaves를 이용하라.

비동기적 입출력 (Asynchronous I/O)


비동기 입출력 (Asynchronous I/O)은 프로세스들이 쓰기를 하고 난후 기다리지 않고 바로 다음 작업으로 진행하도록 해준다. 그렇기 때문에 시스템 퍼포먼스를 높이고 idle time을 줄일수 있게 해 준다. Solaris는 저수준 (raw) 또는 파일 시스템 (filesystem) 데이터 파일 둘다에 대한 비동기 입출력을 지원해 준다.

I/O Slaves


I/O Slaves는 기능이 입출력만을 수행하는 아주 특이화 되어 있는 프로세스이다. Oracle8에서는 처음 등장하는 것이며, 다중 DBWRs (다른 프로세스들 뿐만 아니라)를 대치할수 있다. (사실 multiple DBWRs의 일반적인 형태이며, 다른 프로세스에 의해서 이용될 수도 있다) 그리고 비동기 입출력이 이용할수 있든지 없든지간에 작동한다. I/O Slaves는 그들이 운영하는 중에 조절을 허용하는 초기화 인자를 제공한다. 그들은 Table 3-1에 기술되어 있다.
Table 3-1 I/O Slaves를 위한 초기화 인자
Parameter  Range of Values  Default Value  
DISK_ASYNCH_IO
TRUE/FALSE
TRUE
TAPE_ASYNCH_IO
TRUE/FALSE
TRUE
BACKUP_DISK_IO_SLAVES
TRUE/FALSE
FALSE
BACKUP_TAPE_IO_SLAVES
TRUE/FALSE
FALSE
DBWR_IO_SLAVES
0 - 999
0
LGWR_IO_SLAVES
0 - 999
0
ARCH_IO_SLAVES
0 - 999
0
DB_WRITER_PROCESSES
1-10
1

비동기 입출력이 필요하지 않든지 또는 불가능할 경우가 있다. Table 3-1에 있는 처음 두개의 인자, DISK_ASYNCH_IO 와 TAPE_ASYNCH_IO은 디스크와 테이프 디바이스에 대해서 비동기 입출력을 허용할 것인지를 설정하는 것이다. 각각의 프로세스 형에 따라 I/O Slaves의 수는 기본적으로 0으로 설정되기 때문에 특별히 설정을 하지 않으면 I/O Slaves를 이용하지 않는 것으로 간주한다.
DBWR_IO_SLAVES는 ASYNC I/O (즉 DISK_ASYNCH_IO 또는 TAPE_ASYNCH_IO)가 비활성화 되어 있는 경우에만 0보다 큰 값으로 설정된다. 그렇지 않을 경우 DBWR에 병목현상이 나타나게 될 것이다. 이와 같은 경우 리눅스에서 DBWR_IO_SLAVES의 적절한 값은 4 정도가 된다. LGWR_IO_SLAVES가 설정된 경우 9 slaves 이상 설정할 것을 권하지 않는다.
DB_WRITER_PROCESSES는 DB_WRITER인자를 대치한 것이다. 그리고 하나의 인스턴스에 대하여 데이터베이스 writer process의 초기 갯수를 명시하도록 한다. 만약 여러분들이 DBWR_IO_SLAVES를 이용한다면, DB_WRITER_PROCESSES가 어떻게 설정되던지 간에 단지 하나의 데이터베이스 writer process가 이용될 것이다.

Monitoring Disk Performance


디스크 퍼포먼스를 모니터 하기 위하여, vmstat.를 이용하라.
디스크 퍼포먼스를 위해 눈여겨 봐야 할 vmstat 컬럼은, 블럭 입출력(blocked I/O)을 위해 CPU가 대기하는 시간의 퍼센티지를 나타내는 %wio이다.
주요 인디케이터는 (Key indicators):
  • bread, bwrit, pread 와 pwrit의 합계는 디스크 입출력 서브시스템(disk I/O subsystem)의 상태를 가르킨다. 이 합계가 더 클 수록, 디스크 입출력 병목현상의 가능성은 더 커진다. 물리적으로 디스크의 갯수가 많을 수록, 더 큰 역치값(threshold number)을 가지게 될 것이다. 적당한 값은 두개의 드라이브에 대해서는 40을 넘지 않도록 하고, 4개 내지 8개의 드라이브일때는 기본값이 60을 넘지 않도록 해준다.
  • %rcache는 90보다 크야 하며, %wcache는 60 보다 크야 한다 그렇지 않을 경우 시스템의 디스크 입출력은 한계점에 온 것이다.
  • 만약 %wio는 지속적으로 20이상라면 시스템의 디스크 입출력은 한계점에 온것이다.




디스크 퍼포먼스 문제점 (Disk Performance Issues)

2016년 6월 9일 목요일

[Database] How to Disable Automatic Statistics Collection in 10G ?

The information in this document applies to:

Oracle Server - Enterprise Edition - Version: 10.1.0.1 to 10.2.0.0
Information in this document applies to any platform.

Goal

How to Disable Automatic Statistics Collection in 10G ?

Note
The Automatic Statistics collection feature is enabled by default in 10G.
You can verify this by checking the following :
SQL> SELECT STATE FROM DBA_SCHEDULER_JOBS WHERE JOB_NAME = 'GATHER_STATS_JOB';
STATE
---------------
SCHEDULED

Solution

1)To Disable the automatic statistics collection in 10G , you can execute the following procedure :
SQL> EXEC DBMS_SCHEDULER.DISABLE('GATHER_STATS_JOB')';

2) To check whether the job is disabled, run the following QUERY:
SQL> SELECT STATE FROM DBA_SCHEDULER_JOBS WHERE JOB_NAME = 'GATHER_STATS_JOB';
STATE
---------------
DISABLED



Metalink Note: 311836.1

[Database] checkpoint not complete에 대해서

원본출처 : http://database.sarang.net/?inc=read&aid=9261&criteria=oracle&subcrit=tutorials&id=&limit=20&keyword=&page=9


checkpoint not complete에 대해서

database가 전체적으로 멈춘것처럼 어떠한 새로운 작업도 진행이 안되는 경우, alertSID.log file를 확인해 보면 아래와 같은 message가 적힌 경우가 있다.

Checkpoint not complete

이러한 message가 의미하는 것은 무엇이며, 왜 발생하는지, 그리고 어떠한 조치가 가능한지 자세히 살펴본다.

1. checkpoint란 무엇인가?
~~~~~~~~~~~~~~~~~~~~~

checkpoint는 memory내의 block buffer의 내용과 disk상의 data block간의 내용을 맞추는 database event라고 할 수 있다.
checkpoint가 발생하면, 그때까지 memory내의 block에 가해진 모든 변경 사항을 disk상의 datafile내에 반영하게 된다. database crash에 의해 recovery가 필요 하게 되면 이 checkpoint이후의 변경사항에 대해서만 recovery를 시도하면 되므로, recovery를 쉽고 효율적으로 수행할 수 있다.

checkpoint중에 수행되는 작업은 다음 두 가지로 정리할 수 있다.
- DBWR가 buffer cache내의 모든 변경된 block을 datafile에 기록한다.
- LGWR(혹은 CKPT)가 현재 발생한 checkpoint의 SCN값을 datafile과 controlfile 에 기록한다.

여기에서 중요한 것은 checkpoint event가 발생하여 block buffer에 어느 block이 disk로 씌여져야 하는가를 표시하면 dbwr는 이렇게 표시된 buffer를 순서와 관계없이 disk로 write하게 된다. 즉 redo log group1에 대한 buffer를 checkpoint하고 있는 중에 redo log group2에 대한 checkpoint요청이 들어오면 redo log group1에 대한 checkpoint를 끝내고 group 2에 대한 처리를 하는 것이 아니고, redo log group1에 대한 checkpoint가 진행 중에 redo log group2에 해당하는 buffer도 buffer cache 내에 모두 표시한 후 이것도 모두 group 1과 group 2의 구별이나 순서는 유지하지 못한채 같이 checkpoint가 이루어지는 것이다. 이것은 이후에 설명할 Checkpoint not complete가 발생하는 원인을 밝혀주는 중요한 근거가 된다.

checkpoint가 발생되는 경우는 다음 4가지 경우이다.
(1) log switch
(2) log_checkpoint_interval 만큼의 os block 간격
(3) log_checkpoint_timeout 초 간격
(4) alter system checkpoint명령문 수행시
앞의 세가지 경우에 대해서는 아래 별도의 section으로 자세히 설명하였다.

2. checkpoint와 성능
~~~~~~~~~~~~~~~~~

checkpoint를 자주하면 db crash후 recovery시간을 줄일 수 있는 대신에, 운영 중에서는 속도를 저하시킨다. checkpoint중에는 해당 datafile의 header내용이 고정되어 datafile header에 대한 다른 작업이 진행되지 못한다. 또한 checkpoint간격이 너무 짧으면 뒤에서 설명할 'checkpoint not complete' 현상이 발생할 가능성도 커지게 되어 checkpoint에 대한 tuning이 필요하게 되는 것이다.


3. CKPT process
~~~~~~~~~~~~

checkpoint 수행 중 checkpoint SCN의 값을 controlfile과 datafile header에 반영시키는 작업을 수행하는 background process는 LGWR 혹은 CKPT process이다.
LGWR와 CKPT중 어느 process가 작업하는지는 Oracle version과 checkpoint_process 라는 parameter에 의해 결정된다. CKTP process를 사용하여 LGWR process의 작업을 줄이는 것이 성능 향상에 상당한 도움이 되기 때문에 oracle version이 증가할수록 CKPT process의 사용이 자동으로 유도된다.

(1) Oracle 7.0 - 7.3.2
initSID.ora file에 CHECKPOINT_PROCESS=TRUE 로 지정되어 있는 경우만, CKPT process가 구동된다.

(2) Oracle 7.3.3, 7.3.4
db_files가 50이상이거나, 혹은 db_block_buffers값이 10,000 이상이면, CHECKPOINT_PROCESS parameter에 관계없이 항상 CKPT process가 구동된다. CHECKPOINT_PROCESS를 TRUE로 지정하면 db_files나 db_block_buffers값과 무관하게 CKPT process가 기동된다.

(3) Oracle8 8.0.3이상
CKPT process 는 항상 기동되어, CHECKPOINT_PROCESS를 지정하면, 다음과 같은 오류가 발생한다.

LM-101 "unknown parameter name checkpoint_process"

4. checkpoint가 발생되는 시점
~~~~~~~~~~~~~~~~~~~~

checkpoint가 발생하는 시점은 아래와 같이 4가지 경우가 있다.

(1) log switch
log switch가 발생하면 항상 checkpoint가 발생한다. 단, checkpoint가 발생한다고 하여 log switch가 되는 것은 아니다. log swtich가 되는 경우는 사용 중인 log file이 모두 씌여졌거나, 명시적으로
'alter system switch logfile' 문장을 수행한 경우이다.

online redo log file의 크기는 성능과 복구에 중요한 영향을 미친다.

(2) LOG_CHECKPOINT_INTERVAL
이 parameter의 단위는 OS block size로, 이 크기만큼의 redo log file에 기록한 후에 checkpoint가 발생하는 것이다. 예를 들어 log_checkpoint_interval로 10,000이 지정되어 있고, 대부분의 Unix의 경우처럼 한 os block size가 512 bytes이면, 10,000 * 512 bytes = 5m가 된다.
redo log file의 크기가 20m라고 가정할 때, 5m마다 checkpoint가 발생하므로, redo log file하나를 기록하는 동안 4번의 checkpoint가 발생하는 것이다.

그러나 반대로 log file이 이 크기보다 작은 3m라면 log_checkpoint_interval parameter는 무시되고 log swtich발생마다, 즉 log file에 3m만큼 기록한 후에는 항상 checkpoint가 발생하게 된다.

(3) LOG_CHECKPOINT_TIMEOUT
이 parameter의 단위는 초(second)이며, 이 시간 간격으로 checkpoint가 발생 하게 된다. log swtich전에 checkpoint가 발생하길 원한다면 log_checkpoint_timeout보다는 log_checkpoint_interval을 사용하도록 권장한다. log_checkpoint_timeout은 transaction이 발생하지 않아도 일정 시간이 지나면, 무조건 checkpoint를 발생하게 하여 불필요한 checkpoint를 발생시키기 때문이다.

5. 그 외의 checkpoint관련 parameter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

(1) DB_BLOCK_CHECKPOINT_BATCH
이 parameter는 Oracle7과 Oracle8.0까지만 사용되는 것으로 checkpoint시에 DBWR가 한번에 disk에 write하는 block buffer의 갯수를 지정한다. 이것을 32정도의 큰 값으로 지정하면 checkpoint속도를 향상시킬 수 있다.

(2) LOG_CHECKPOINTS_TO_ALERT
log swtich는 항상 alert.log에 기록이 남는다. log switch보다 자주 checkpoint가 발생하도록 한 경우 checkpoint발생 시간과 간격을 확인하려면 log_checkoiint_to_alert=true로 init.ora file에 지정하면 확인이 가능하다.


6. redo log file과 checkpoint
~~~~~~~~~~~~~~~~~~~~~~~

앞에서 언급한 바와 같이 log switch시에는 항상 checkpoint가 발생한다. 일반적 으로, og_checkpoint_interval이나 log_checkpoint_timeout을 log swtich보다 간격을 크게 하여 checkpoint가 log swtich시에만 발생하도록 사용한다.

redo log file의 크기가 너무 작으면 불필요한 checkpoint작업이 자주 발생할 수 있다. alert.log를 보면 아래와 같은 형태의 log switch에 대한 기록이 남게 되는데, 이것을 통해 log switch 발생 간격을 확인할 수 있다.
Thread 1 advanced to log sequence 248 Current log# 2 seq# 248 mem# 0: /prod1/oradata/logs/redologs02.log
log switch가 자주 발생하게 되면 성능 저하를 가져올 수 있으며, 일반적으로 oracle에서는 한시간에 한번 정도의 log swtich를 권장하고 있다. 오라클 사용자의 경우 log file을 작게 유지하는 이유 중 current redo log file의 corruption시 잃게되는 정보의 양이 log file이 클 수록 비례적으로 큰것을 염려하는 경우가 많은데 이러한 경우는 redo log file에 member를 두고, 그 member를 다른 disk에 위치시킴으로써 위험을 줄일 수 있다.

log file의 크기가 너무 작아 log switch가 자주 발생하게 되는 경우의 또 다른 문제점은 log file의 status가 ACTIVE인 상태로 오래 지속되는 것이다. 이것은 아래 설명할 checkpoint not complete같은 현상에도 직접적인 영향을 미친다.

redo log file은 log swtcih가 발생하고 checkpoint가 진행되는 동안 status가 ACTIVE 상태가 된다. 이것은 해당 redo log에 대한 checkpoint가 완전히 끝나면, INACTIVE 상태로 변경된다.
그런데 log switch에 의해 checkpoint event가 발생된 경우 아직 이전 redo log file에 대한 checkpoint가 끝나지 않았다면 이전 checkpoint와 새로운 checkpoint가 구별되어 수행되는 것이 아니고 이전 checkpoint와 새로운 checkpoint가 하나로 묶여 새로운 checkpoint까지 끝나야만 이전 redo log file도 같이 INACTIVE 상태로 변경이 된다. 이미 1번 checkpoint란 무엇인가에서 이미 언급한 내용이다.

실제 운영중에 redo log file의 대부분이 ACTIVE인 상태로 유지되다가 batch작업이 끝날때쯤 한꺼번에 모두 INACTIVE로 되는 현상이 바로 이러한 원인때문이다.
즉, 이것은 맨 처음 redo log file에 대한 내용도 여전히 checkpoint가 끝나지 않아, 나머지 redo log 들이 모두 기다리고 있는 현상이 아니라, 빈번한 log switch로 인해 바로 직접의 log에 대한 checkpoint가 끝나기 전에 다음 checkpoint가 요청 되고 이러한 일들이 계속 이어짐에 따라 checkpoint작업이 연속적으로 묶여 발생하는 것이다. 이러한 이유로 실제로는 redo log file의 내용의 대부분이 checkpoint가 이루어졌지만, 다수가 ACTIVE인 상태로 존재하는 현상이 발생하는 것이다.

checkpoint중인 datafile header는 다른 작업이 blocking되기 때문에 checkpoint 중인 상태로 오래 지속되는 것은 바람직하지 않으며, 그래서 적정한 redo log file의 크기와 갯수가 중요하게 된다.

7. CHECKPOINT NOT COMPLETE
~~~~~~~~~~~~~~~~~~~~~~~~~

다량의 data를 import하거나 load하는 경우 혹은 batch 작업을 진행하는 경우, database작업이 중간 중간 멈추는 hang현상이 발생하면서, alert.log file에 다음과 같이 'Checkpoint not complete'라는 message가 여러번 적힌 것을 발견 하게 되는 경우가 있다.
Thread 1 advanced to log sequence 248 Current log# 2 seq# 248 mem# 0: /prod1/oradata/logs/redologs02.log Thread 1 cannot allocate new log, sequence 249 Checkpoint not complete
이 message가 나타내는 것은 LGWR가 redo log를 재사용하려고 하는데, 아직 그 redo log file이 checkpoint가 끝나지 않은 상태임을 나타낸다. 예를 들어, LGWR가 1번 redo log group에 내용을 기록하고, 2번으로 switch하면서 1번에 대한 checkpoint가 진행중인 상태에서 2번 3번 redo log도 모두 사용하고 다시 1번을 사용하려고 하면 이러한 메시지가 alert.log에 적히게 되고, 1번을 다시 사용할 수 있을 때까지 db의 모든 변경 작업은 waiting상태가 된다.

이러한 현상은 database에 변경을 가하는 dml이 한꺼번에 빠르게 수행되는 경우 LGWR가 redo log file에 빠른 속도로 많은 내용을 기록하는 경우 발생한다.

redo log file의 크기나 갯수가 작아 log switch가 빈번히 발생하는 경우, LGWR가 redo log에 redo정보를 적으면서 한바퀴 cycle을 도는 시간이 너무 빠르면 이러한 현상은 쉽게 발생 가능하다. 앞 section에서 언급했듯이 oracle에서는 한시간에 한번 정도의 log switch와 log switch시에만 checkpoint가 발생하는 것을 기본적으로 권장한다.

그런데 redo log file의 갯수를 몇개 더 추가하고 size를 증가하였는데도, 마찬가지로 이러한 현상이 발생하는 경우가 있다. redo log group을 추가하여도, redo log group을 추가할때만 사용이 되다가 다시 추가한 redo log group까지 모두 status가 ACTIVE이다가 어느 정도 시간이 지나거나 작업이 끝나면 동시에 모든 redo log group이 INACTIVE상태로 변경되는 것을 볼 수 있다.

이것은 이미 앞에서 여러차례 설명했듯이 이미 수행중인 checkpoint가 끝나지 않은 경우 다음 checkpoint가 들어오면 이것이 하나로 묶이게 되기 때문이며, 아래에 정리한 방법을 이용하여 tuning하여야 한다.

Checkpoint not complete현상을 해결하기 위한 방법을 정리해 보면 다음과 같다.
(1) LGWR가 redo log file을 한바퀴 도는 cycle을 지연시킨다. - redo log group을 추가시킨다. - redo log file의 크기를 증가시킨다. (2) checkpoint를 자주 발생하지 않도록 한다. - LOG_CHECKPOINT_INTERVAL의 값을 증가시킨다. - online redo log file의 크기를 증가시킨다. (3) checkpoint 작업의 효율성을 증가시킨다. - CHECKPOINT_PROCESS=TRUE 로 지정하여 CKPT process를 기동시킨다 (Oracle7) - db_block_checkpoint_batch의 값을 증가시킨다 (7.X ~ 8.0) - db_block_size를 증가시켜 dbwr가 checkpoint시 disk에 write하는 속도를 향상시킨다.
이것은 database를 완전히 다시 만드는 작업이어서 쉽게 조치하기는 어려우나 이 문제가 자주 발생하여 심각한 영향을 미치는 경우 고려해 볼 수 있다.