리두 개요

  • 오라클은 특별한 경우를 제외하면, DML에 의해 생긴 모든 변화에 대해서 리두데이터(Redo Data)를 생성한다.
    리두 데이터를 생성하는 이유는 복구(Recovery)를 위한 것이다.
  • 오라클은 "Write ahead rule"과 "Log force at commit"이라는 두 가지 매커니즘을 이용해 데이터의 복구를 보장한다.
  • Write ahead rule이 의미하는 거은 데이터를 변경시키기 전에 반드시 리두를 먼저 생성한다는 것으로 다음 두 가지
    원칙으로 이루어진다.
    • DML에 의한 변화는 버퍼 캐시에 저장되기 전에 먼저 리두 버퍼에 저장된다.
    • 버퍼 캐시의 더티 버퍼를 데이터파일에 기록하기 전에, 해당되는 리두 레코드를 리두 버퍼로부터 리두 로그(Redo Log)파일에 기록한다.
  • Log force at commit이란 사용자가 커밋을 요청하면, 반드시 관련된 모든 리두 레코드를 리두 로그 파일에 저장해야만 커밋이 종료 된다는 의미이다.
  • 리두 영역에는 다음과 같은 네 가지 영역에 대한 변동 데이터가 저장된다.
    • 언두 세그먼트 헤더(Undo segment header)
    • 언두 블록(Undo Block)
    • 데이터 세그먼트 헤더(Data Segment Header)
    • 데이터 블록(Data Block)
  • 오라클은 완벽한 데이터 복구를 위해 트랜잭션에 의해 변경되는 모든 내용을 리두에 기록한다.
    데이터 자체의 변동사항도 저장되지만 롤백을 수행하기 위한 언두 데이터에 대한 변동사항도 같이 저장된다.
  • 데이터에 대한 리두는 커밋된 데이터를 보호하고, 언두에 대한 리두는 롤백된 데이터를 보호하는 역할을 한다.
  • 다음은 데이터 블록에 대한 리두가 생성되지 않으며, 언두 데이터가 생성되지 않기 때문에 언두 블록에 대한 리두도
    생성되지 않는다
    • Direct load operation (SQL*Loader, insert /*+ append */
    • create table ..., alter table ... with nologging option
    • create index ..., alter index ... with nologging option
    • create materialized view ..., alter materialized view ... with nologging option
    • Nocache + nologging 옵션의 LOB 데이터
  • 데이터를 생성하는 작업에서 Nologging 기능을 사용하면 딕셔너리 정보가 변경되는 경우에만 해당 정보에 한해서 리두가 생성된다.
  • 테이블을 Nologging 옵션으로 생성했다고 하더라도 단순 Insert, Update, Delete에 대해서는 반드시 리두가 생성된다.
    리두 데이터는 데이터복구의 핵심이기 때문에 대량의 데이터를 빠른 속도로 변겨하는 경우에 대해서만 리두 생성을 억제하는 것이 가능하다.
  • 임시 테이블(Temporary Table)에 대해서도 리두가 생성되지 않는다. 하지만, 이것은 데이터 블록에 대해서만 적용된다.
    임시 테이블은 인스턴스 장애에 따른 복구는 불필요하지만 트랜잭션 단위에서의 복구는 가능해야 하기 때문에 언두 데이터와
    언두 데이터에 대한 리두 데이터는 생성된다.

-- 영구 테이블 생성
SQL> create table notemp_test
  2  (id number, name char(1000) default ' ');

테이블이 생성되었습니다.

-- notemp_test에 10000건의 데이터 추가
SQL> insert into notemp_test(id)
  2  select object_id from dba_objects
  3  where rownum <= 10000;

10000 개의 행이 만들어졌습니다.

-- 임시 테이블 생성
SQL> create global temporary table temp_test
  2  (id number, name char(1000) default ' ');

테이블이 생성되었습니다.

case1 : 영구 테이블에 대한 DML
10000건의 데이터를 Insert 하면 언두 = 2500, 리두 = 14622가 생성.

-- 10000건의 데이터를 추가
SQL> insert into notemp_test
  2  select * from notemp_test
  3  where rownum<=10000;

10000 개의 행이 만들어졌습니다.

SQL> select used_ublk, used_urec
  2  from v$transaction t, v$session s
  3  where s.sid = (select sid from v$mystat where rownum=1)
  4  and s.taddr = t.addr;

 USED_UBLK  USED_UREC
---------- ----------
        23       2500   --> 2500개의 Undo record 생성
        
SQL> select n.name, sum(s.value)
  2  from v$sesstat s, v$statname n
  3  where n.name like '%&stat_name%'
  4  and s.statistic# = n.statistic#
  5  and s.sid = (select sid from v$mystat where rownum=1)
  6  group by n.name;
stat_name의 값을 입력하십시오: redo entries
구   3: where n.name like '%&stat_name%'
신   3: where n.name like '%redo entries%'

NAME                                                             SUM(S.VALUE)
---------------------------------------------------------------- ------------
redo entries                                                            14622  --> 14622개의 Redo Entry 생성


case 2 : 영구 테이블에 대한 Direct load operation
10000건의 데이터를 Insert Append 하면 언두 =1 , 리두 = 172 로 실제로는 Undo, Redo가
생성되지 않음을 알 수 있다.

SQL> insert /*+ append */ into notemp_test
  2  select * from notemp_test
  3  where rownum <= 10000;

10000 개의 행이 만들어졌습니다.
        
SQL> select used_ublk, used_urec
  2  from v$transaction t, v$session s
  3  where s.sid = (select sid from v$mystat where rownum = 1)
  4  and s.taddr = t.addr;

 USED_UBLK  USED_UREC
---------- ----------
        23       2500 
        

SQL> alter session enable parallel dml;

세션이 변경되었습니다.

SQL> insert /*+ append */ into notemp_test
  2  select * from notemp_test where rownum <= 10000;

10000 개의 행이 만들어졌습니다.

SQL> select used_ublk, used_urec
  2  from v$transaction t, v$session s
  3  where s.sid = (select sid from v$mystat where rownum=1)
  4  and s.taddr = t.addr;

 USED_UBLK  USED_UREC
---------- ----------
         1          1

SQL> select n.name, sum(s.value)
  2  from v$sesstat s, v$statname n
  3  where n.name like '%&stat_name%'
  4  and s.statistic# = n.statistic#
  5  and s.sid = (select sid from v$mystat where rownum=1)
  6  group by n.name;
stat_name의 값을 입력하십시오: redo entries
구   3: where n.name like '%&stat_name%'
신   3: where n.name like '%redo entries%'

NAME                                                             SUM(S.VALUE)
---------------------------------------------------------------- ------------
redo entries                                                              172

Case 3 : 임시 테이블에 대한 DML 
SQL> insert into temp_test
  2  select * from notemp_test
  3  where rownum<=10000;

10000 개의 행이 만들어졌습니다.

SQL> select used_ublk, used_urec
  2  from v$transaction t, v$session s
  3  where s.sid = (select sid from v$mystat where rownum=1)
  4  and s.taddr = t.addr;

 USED_UBLK  USED_UREC
---------- ----------
        23       2500
        
SQL> select n.name, sum(s.value)
  2  from v$sesstat s, v$statname n
  3  where n.name like '%&stat_name%'
  4  and s.statistic# = n.statistic#
  5  and s.sid = (select sid from v$mystat where rownum=1)
  6  group by n.name
  7  /
stat_name의 값을 입력하십시오: redo entries
구   3: where n.name like '%&stat_name%'
신   3: where n.name like '%redo entries%'

NAME                                                             SUM(S.VALUE)
---------------------------------------------------------------- ------------
redo entries                                                             2678

언두 = 2500로 영구 테이블과 동일하지만 리두는 2678로 영구 테이블에서의 리두 데이터의 
절바도 안돼는 수준이다. 즉 데이터에 대한 리두는 생성되지 않고, 언두에 대한 리두만 생성되었음을
확인 할 수 있다.

리두 버퍼(Redo Buffer)

  • 데이터를 변경하려는 모든 프로세스는 반드시 리두 버퍼에 변동사항을 기록해야 한다.
  • 오라클은 리두 레코드(Redo Record)라는 단위로 데이터의 변동사항을 저장한다.
  • 리두 버퍼는 리두 레코드를 연속된 형태로 저장하며, 버퍼가 꽉 차면 다시 처음부터 레코드를
    저장하는 순환구조 기법을 사용한다.
  • 오라클은 버퍼 캐시의 블록을 변경하기 전에 다음과 같은 과정을 거쳐 리두 레코드를 리두 버퍼에 저장한다.
  1. 데이터 변동사항을 프로세스 메모리 영역(PGA)에 체인지 벡터(Change Vector)로 저장한다.
    체인지 벡터는 하나의 로우 변경에 대한 모든 변경 사항들을 담고 있는 집합체 이다.
    가령 하나의 데이터 블록에 하나의 로우를 Insert하는 경우 보통 총 세개의 체인지 벡터가 생성되면,
    Change #1=언두 헤더 변경 내용, Change #2=언두 블록 변경 내용, Change #3=데이터 블록 변경 내용으로
    구성된다. 체인지 벡터들을 리두 레코드 포매으로 리두 버퍼에 복사된다. 프로세스는 체인지 벡터를 리두 레코드
    포맷으로 리두 버퍼에 복사된다. 프로세스는 체인지 벡터를 리두 버퍼에 복사하기 위해 반드시 redo copy 래치를
    획득해야 하며, 체인지 벡터를 리두 버퍼에 복사하는 작업의 전 과정동안 redo copy래치를 보유해야 한다.
    redo copy 래치를 획득하는 고정에서 경합이 발생하면 latch:redo copy 이벤트를 대기한다.
  2. 현재 자신의 SCN이 모든 프로세스가 지닌 SCN중 가장 높은 SCN인지 확인한다. 리두 버퍼는 SCN순으로 리두 레코드를
    쌓기 때문에 SCN의 순서가 보장되어야 한다.
  3. redo allocatin 래치를 획득한 후, 리두 버퍼 내에 리두 레코들ㄹ 저장할 공간을 확보한다. redo allocation 래치를
    획득하는 과저에서 경합이 발생하면 latch:redo allocation 이벤트를 대기한다. 만일 리두 버퍼에 여유 공간이 부족하면
    redo writing 래치를 획득하고 LGWR에게 리두 버퍼를 리두 로그 파일에 기록하고 여유 공간을 확보해 줄 것을 요청하고
    LGWR에 의해 로그 버퍼에 여유 공간이 확보될때까지 log buffer space 이벤트를 대기한다. 만일 리두 로그 파일이 꽉
    차서 로그 파일 스위치가 발생하는 경우에는 이 작업이 끝나기를 기다려야 하며 log file switch completion 이벤트를
    대기한다. 로그 버퍼 내에 필요한 공간이 확보되면 redo allocation 래치를 해제한다.
  4. PGA에 생성한 체인지 벡터를 리두 레코드 형태로 리두 버퍼로 복사한다. 복사를 시작하는 시점에 로그 파일이 꽉 차면
    로그 파일 스위치가 수행될 때까지 기다려야 하며 그동안 log file switch completion 이벤트를 대기한다. 이 모든
    과정이 끝나면 redo copy 래치를 해지한다. 하나의 리두 레코드가 리두 버퍼에 기록될 때마다 redo entries 통계
    값이 1 증가한다.
  • 오라클 8i까지는 redo allocation 래치가 전체 시스템에서 하나에 불과하다. 이것은 한번에 오직 하나의 프로세스만이
    리두 버퍼 영역에서 메모리를 할당할 수 있다는 것을 의미한다. 따라서 리두 활동이 왕성하고 CPU 개수가 많은 시스템에서
    redo allocation 래치에 대한 경합이 발생할 확률이 높다.
  • 오라클 9i에서는 shared redo strands라는기능을 도입해서 이 문제에 대한 해결책을 제시한다. shared redo strands 혹은
    redo strands란 리두 버퍼의 영역을 일정 개수로 분할해서 사용하는 것을 말한다. 분할된 개개의 영역을 strand(끈, 가닥)
    라고 부른다. _LOG_PARALLELISM 파라미터의 값을 이용해서 REDO STRANDS의 개수를 지정할 수 있다. 오라클은 CPU개수/8
    정도의 값을 사용할 것을 권장한다. 개별 strand마다 개별 redo alloction 래치를 사용하기 때문에 래치 경합을 줄일 수 있다.
  • 오라클 10g부터는 리두 버퍼를 관리하는 기법이 더욱 개선되었다.
    • 첫째, shared redo strands의 개수를 오라클이 동적으로 관리 할 수 있다. _LOG PARALLELISM_DYNAMIC 히든 파라미터를
      TRUE로 설정하면 오라클은 동적으로 REDO STRANDS를 관리한다.
    • 둘째, private redo strands 기능을 도입했다. 이 기능을 사용하면 프로세스가 생성한 리두 데이터를 PGA 영역(체인지 벡터)
      이 아닌 Shared Pool의 private strands 영역에 저장하며, private strands 영역에 저장된 로그 데이터를 리두 버퍼를 거치지
      않고 곧바로 로그 파일에 기록된다. PGA 영역에서 리두 버퍼로의 복사과정이 불필요하기 때문에 이 기능을 "zero copy redo"
      라고 부른다. _LOG_PRIVATE_PARALLELISM 히든 파라미터의 값을 TRUE로 설정하면 private strands 기능이 활성화 된다.

리두 로그(Redo log)

  • LGWR 구동 시점
    • 3초 마다
    • 리두 버퍼의 1/3 또는 1MB가 찼을때
    • 사용자가 커밋 또는 롤백을 수행할 때
    • DBWR이 LGWR에게 쓰기 요청을 할 때 (Write ahead rle)
  • 리두 로그 파일은 리두로그블록(Redo log block)단위로 기록된다. 리두 로그 블록의 크기는 OS 마다 다르다.
  • 사용자가 커밋이나 롤백을 수행할 때마다 가장 최근에 기록된 이후 시점부터 현재 까지의 리두 내용이 리두 로그에 쓰여
    진것이 일반적이지만, 오라클은 가능하면 여러개의 커밋 요청을 모아서 한번에 기록한다. 이것을 그룹커밋(Group Commits)
    라고 부른다. 동시에 여러 세션이 커밋 요청을 하는 경우, PL/SQL 블록에서 반복적으로 커밋을 수행하는 경우 등에서 그룹
    커밋이 사용된다.
  • 리두 로그 파일과 관련된 통계 값
    • user commits : 사용자가 수행한 커밋 회수
    • user rollbacks : 사용자가 수행한 롤백 회수
    • redo synch writes : 커밋 또는 롤백에 의해 수행된 리두 기록(Redo Write)의 회수를 가리킨다. 백그라운드 프로세스들에
      의해서도 많은 수의 커밋이 이루어지기 때문에 시스템 레벨에서느 user commits < redo synch writes의 분포르 보이는
      것이 일반적이다. 그룹 커밋이 이루어지는 경우에는 user commits 값은 커밋을 요청한 회수만큼 증가하지만 redo sync
      writes는 한번만 증가하므로 이 값들을 해석할 때는 주의할 필요가 있다.
    • redo synch time : redo sync writes에 의해 소요된 시간 (1/100초)
    • redo writes : LGWR이 redo writes를 수행한 전체 회수
    • redo size : 시스템에서 생성된 리두 데이터의 크기(bytes)
    • redo blocks written : 리두 로그 파일에 기록된 리두 로그 블록의 수
    • redo entries : 리두 에트리가 리두 버퍼에 복사된 회수
    • redo log space requests : 프로세스가 로그 버퍼에 리두 레코드를 기록하려는 시점에 로그 파일이 꽉차서 로그 스위치를 요청한 회수
    • redo log pace wait time : redo log space requests에 의해 소요된 시간 (1/100초)
  • LGWR은 리두 버퍼가 전체 크기의 1/3 또는 1MB 만큼 쓰여지면 가장 마지막 기록 시점 이후의 리두 버퍼 변경 내요을
    리두 로그로 기록한다 .이것을 백그라운드 기록 (Background writes)이라고 부른다.
  • 백그라운드 기록의 기준이 되는 크기는 _LOG_IO_SIZE라는 히든 파라미터에 의해 결정된다. _LOG_IO_SIZE의 기본값은
    오라클 9i 까지는 리두 버퍼의 1/3이지만, 오라클 10g 부터는 1/6dlsep, _LOG_PARALLELISM_MAX 파라미터의 기본값이
    2인것과 관련이 있다.