SRM 152 DIV2
: 문제해석이 어려운 문제;
class FixedPointTheorem
{
public double cycleRange(double R)
{
double ret = 0;
double X = 0.25;
for(int i = 0; i < 200000; i++)
{
X = R * X * (1-X);
}
double max_val = X;
double min_val = X;
for(int i = 0; i < 1000; i++)
{
X = R * X * (1-X);
max_val = Math.max(X, max_val);
min_val = Math.min(X, min_val);
}
ret = max_val - min_val;
return ret;
}
}
SRM 153 DIV2
: 평이한 문제
class MostProfitable
{
public String bestItem(int[] c, int[] p, int[] s, String[] items)
{
int size = c.length;
int[] v = new int[size];
int max1 = 0;
for(int i = 0; i < size ; i++)
{
v[i] = (p[i] - c[i]) * s[i];
max1 = Math.max(max1, v[i]);
}
if(max1 == 0) return "";
for(int i =0; i< size; i++)
{
if( max1 == v[i]) return items[i];
}
return "";
}
}
SRM 154 DIV2
: 자바에서 substring이 C++과 좀 다르다.. begin은 0부터 시작하고 end는 자리수+1을 넣어줘야함.
총 길이는 end-begin
class ProfitCalculator
{
public int percent(String[] items)
{
double price = 0;
double cost = 0;
for(int i = 0; i < items.length; i++)
{
price += Double.valueOf(items[i].substring(0, 6));
cost += Double.valueOf(items[i].substring(7, 13));
}
double margin = (price - cost)/price * 100 ;
return Double.valueOf(margin).intValue();
}
}
SRM 155 DIV2
: 자바가 C++보다 좀더 쉬운 문제. toCharArray로 char[]로 전환해서 사용해도 된다.
나는 String으로 해서 처리했는데 보통은 기존수*10 + 붙일 수 형식으로 했네..
class Quipu
{
public int readKnots(String knots)
{
String ret = "";
int count = 0;
for(int i = 1; i < knots.length(); i++)
{
if(knots.substring(i, i+1).equals("-"))
{
if(count != 0)
ret += String.valueOf(count);
else
ret += "0";
count = 0;
}
else if(knots.substring(i, i+1).equals("X"))
{
count += 1;
}
}
return Integer.valueOf(ret);
}
}
SRM 156 DIV2
class DiskSpace
{
public int minDrives(int[] used, int[] total)
{
int s = used.length;
int remain = 0;
for( int i=0; i < s; i++)
{
remain += used[i];
}
for( int i=0; i< s; i++)
{
remain -= total[i];
if( remain <= 0 ) return i+1;
}
return 0;
}
}
SRM 149 DIV2
: 전에 C++로 짤 때는 크기별로 분기로 처리했었네;
class FormatAmt
{
public String amount(int d, int c)
{
String ret = "";
int m = d;
while(true)
{
if( m / 1000 == 0)
{
ret = (m % 1000) + ret;
break;
}
else
{
String s = String.valueOf(m % 1000);
while( s.length() < 3) s = "0"+s;
ret = ","+ s + ret;
m = m / 1000;
}
}
String str = "$" + ret + ".";
if( c > 9) str += c;
else str = str + "0" + c;
return str;
}
}
SRM 150 DIV2
: 예전과 똑같이 풀었네.. 마지막날 남은일 계산을 (remain + n -1) / n 와 같이 짤 수 있도록
class WidgetRepairs
{
public int days(int[] a, int n)
{
int ret = 0;
int remain = 0;
for(int i = 0; i < a.length; i++)
{
remain += a[i];
if( remain > 0 ) ret++;
remain -= n;
if(remain < 0) remain = 0;
}
while( remain > 0 )
{
ret++;
remain -= n;
}
return ret;
}
}
SRM 151 DIV2
: 문제해석이 좀 걸렸다. 예전에는 허프만알고리즘을 최근에 읽어서 바로 풀었던거 같은데;
역시 알고리즘 책을 자주 봐야..
class PrefixCode
{
public String isOne(String[] w)
{
for(int i=0; i< w.length; i++)
{
String w1 = w[i];
for(int j=0; j < w.length; j++)
{
if( i == j ) continue;
if( w[j].startsWith(w1) )
{
return "No, "+ i;
}
}
}
return "Yes";
}
}
1년이상 쉬었더니 기억이 가물가물.. 이제는 JAVA로 연습하기로 함.
SRM145 DIV2
class ImageDithering
{
public int count(String d, String[] s)
{
int ret = 0;
for( int i = 0; i < s.length; i++)
{
String str = s[i];
for( int j = 0; j < str.length(); j++)
{
String s1 = str.substring(j, j+1);
for(int k =0; k < d.length(); k++)
{
if(s1.indexOf(d.substring(k, k+1)) != -1 )
{
ret++;
}
}
}
}
return ret;
}
}
SRM146 DIV2
Math.max(max, point[i]) 와 같이 쓸 수도 있다.
class YahtzeeScore
{
public int maxPoints(int[] toss)
{
int[] point = new int[6];
for( int i = 0; i< toss.length; i++)
{
point[toss[i]-1] += toss[i];
}
int max = 0;
for(int i= 0; i < point.length; i++)
{
if( max < point[i]) max = point[i];
}
return max;
}
}
SRM147 DIV2
: java에서 String 을 char로 전환해서 처리하는 걸 몰라 그냥 substring으로 처리했음;
char []d = c.toCharArray(); 와 같이 변환해서 처리한 후에 String(d)와 같이 사용하면 됨.
class CCipher
{
public String decode(String c, int s)
{
String alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
String ret = "";
for(int i=0; i < c.length(); i++)
{
String a = c.substring(i, i+1);
for(int j=0; j<alpha.length(); j++)
{
if(alpha.substring(j, j+1).equals(a))
{
int k = j - s;
if( k < 0 ) k += 26;
ret += alpha.substring(k, k+1);
}
}
}
return ret;
}
}
SRM148 DIV2
: 나는 String으로 해서 하나씩 돌렸으나.. 다른사람들 보면 10으로 나누면서 10으로 나눈 나머지로 처리..
class DivisorDigits
{
public int howMany(int number)
{
int ret = 0;
String s = Integer.valueOf(number).toString();
for(int i = 0; i< s.length(); i++)
{
String s0 =s.substring(i, i+1);
int k = Integer.valueOf(s0);
if( k != 0 && number % k == 0) ret++;
}
return ret;
}
}
출처 : http://www.oracleclub.com/article/23893
[ 출처 ]
1. 10g Optimizer개념 및 통계치 생성 방법,SQL Tuning 방법
http://www.dbguide.net/club/board/download.jsp?maskname=274&fileName=10g+Optimizer+to+public.pdf
2. 10g 자동화 통계정보 수집에 관하여... [2008/05/11 Update]
http://blog.naver.com/sungeunn/120051268815
3. 어떤 STATISTICS_LEVEL 을 사용할 것인가 ?
http://cafe.naver.com/prodba/9293
4. 메타링크
4.1 Two types of automatic statistics collected in 10g [
559029.1 ]
4.2 How to Disable Automatic Statistics Collection in 10G ? [
311836.1 ]
5. 10G References [ STATISTICS_LEVEL ]
http://download.oracle.com/docs/cd/B19306_01/server.102/b14237/initparams211.htm#REFRN10214
[ 결론 ]
1. System 통계정보는 따로 생성 하지 않는다.
어느 시간이 최적인지
모른다. DBA 가 알수 있으면 정기적으로 생성해서 활용한다.
2. 자동 통계정보 수집(CBO) 는 disable 하고, 수동으로
통계정보를 생성한다.
운영 중에 통계정보 생성으로 인해 업무의 성능 저하를 사전에 예방하자.
3. STATISTICS_LEVEL
= TYPICAL 로 유지 하자
4. 자동 통계정보 수집 대상은 User Objects, Sys/System Object 가 대상이다.
[ 요약 ]
| 대상 | 설명 | TATISTICS_LEVEL=BASIC | STATISTICS_LEVEL=TYPICAL |
| System Statistics | System 성능 ( CPU, DISK ) | ||
| Fixed Objects Statistics | DBMS 성능 ( X$, V$) | ||
| Dictionary Statistics | SYS/SYSTEM USER TABLE | 자동 수집되지 않는다. | 자동 수집된다. |
| User Table Statistics |
일반 유저 TABLE | 자동 수집되지 않는다 | 자동 수집된다. |
통계정보의 종류
: 크게 4가지로 구분할 수 있다.
[ 1.System
Statistics ]
: 개요 - System Statistics 는 System
Hardware 의 I/O, CPU 의 특성을 분석하여
Optimizer 가 CPU Costing 을 계산할 때 사용하는
정보로써,
이를 기반으로 Optimizer 가 임의의 SQL에 대한 실행 계획을 수립할 때
이를
기반으로 계산하게 된다.
수행주기 - 초기 1회
시스템 자원의 변경이 생겼을 경우 ( Memory,
CPU, I/O 등 )
9i - 처음 소개된 개념이고, DBA가 수동으로 수집 하지 않으면
기본적으로
존재하지 않는 정보이다. 기본적으로 I/O Model로 비용산정
System Statistics 정보가 있으면 Optimizer 가 비용 산정을 CPU Model 로 하고,
System
Statistics 정보가 없다면 Optimizer 가 비용 산정을 I/O Model 로 한다.
10G
- System Statistics 정보를 수집 하지 않는다면 Noworkload System Statistics 가
사용된다. 10G 에서는 Optimizer 가 비용 산정을 CPU Model 로 한다. [ Default ]
[ CPUSPEEDNW, IOSEEKTIM, IOTFRSPEED 로 구성된다. ]
수동으로 System
Statistics 수집 시에는 Workload System Statistics 라고 한다.
결론적으로 운영 시스템의 최적의
System Statistics 를 생성하여, 적절히
적용 하여 사용하는 것이 최선이나, 보통은
NoWorkload System Statistics 를
그대로 사용한다.
주의사항 : RAC 에서
NODE 가 서로 같은 시스템 사양을 같지 않을 경우에는
System Statistics 를 Node 별로 나누어 관리 되지
않으므로 전체 System 의
대표성을 가지는 Node 에서 수행을 한다.
특히 위와 같은 결정을 하기
위해서는, 각각의 Node 별로 통계치를 생성해
보고 비교해 본 후에 결정할 수 있다.
노드의
사양이 동일한 경우 가장 일반적인 Node 에서 수행한다.
실습
: 시스템 통계정보는 Optimizer 가 실행 계획 세움에 있어서 지대한 영향을
미치므로, 항상 기존의 시스템 통계자료를 백업 후 진행 하자.
OLTP 와 OLAP 성 통계정보를 생성하고, IMPORT 해보자
-- 1. 시스템 통계정보 확인
SELECT * FROM SYS.AUX_STATS$
;
SNAME PNAME PVAL1
PVAL2
------------------------------ -------------------- ---------
------------------------------
SYSSTATS_INFO
STATUS COMPLETED
SYSSTATS_INFO
DSTART 02-13-2009
13:33
SYSSTATS_INFO DSTOP
02-13-2009 13:33
SYSSTATS_INFO FLAGS
0
SYSSTATS_MAIN CPUSPEEDNW
1489
SYSSTATS_MAIN IOSEEKTIM
10
SYSSTATS_MAIN IOTFRSPEED
4096
SYSSTATS_MAIN
SREADTIM
SYSSTATS_MAIN
MREADTIM
SYSSTATS_MAIN
CPUSPEED
SYSSTATS_MAIN
MBRC
SYSSTATS_MAIN MAXTHR
SYSSTATS_MAIN
SLAVETHR
13 rows selected.
-- 2. 기존 통계정보 백업 받을 테이블 생성
SQL> execute
DBMS_STATS.CREATE_STAT_TABLE(’SYS’,’ORIGIN’,’USERS’);
PL/SQL procedure
successfully completed.
-- 기존 통계정보 백업
SQL> execute
DBMS_STATS.EXPORT_SYSTEM_STATS(stattab =>’ORIGIN’, STATID
=>’ORIGIN’,STATOWN =>’SYS
PL/SQL procedure successfully
completed.
-- 백업 받은 시스템 통계정보 데이타 확인
SQL> SELECT STATID, C1, C2, C3
FROM ORIGIN ;
-- C1 = COMPLETED --> 수집 완료
-- 3. 시스템 통계정보 수집
-- 사전에 JOB PROCESS 확인 ( 1보다 커야
한다.)
SQL> show parameters job_queue
NAME TYPE
VALUE
------------------------------------ -----------
------------------------------
job_queue_processes
integer 0
SQL> alter system set job_queue_processes = 5 ;
System
altered.
SQL> show parameters job_queue
NAME
TYPE VALUE
------------------------------------ -----------
------------------------------
job_queue_processes
integer 5
-- 4. OLTP 성 시스템 통계정보 수집하기
-- OTLP용 시스템 통계정보 생성을 위한
통계정보 테이블 생성
-- OWNER, TABLE 이름, TABLESPACE 이름 순
SQL> execute
DBMS_STATS.CREATE_STAT_TABLE(’SYS’,’OLTP’,’USERS’);
PL/SQL procedure
successfully completed.
-- 지금 부터 INTERVAL 로 지정된 시간 동안 시스템 통계정보를 생성
하라..
-- 2분동안 시스템 통계정보를 수집 하라
SQL> execute
DBMS_STATS.GATHER_SYSTEM_STATS(GATHERING_MODE =>’INTERVAL’,INTERVAL=> 2,
STATTAB => ’OLTP’, STATID => ’OLTP’);
PL/SQL procedure successfully
completed.
-- 시스템 통계정보 수집 확인
SQL> select STATID, C1, C2, C3 from
OLTP;
STATID C1
C2 C3
--------------------
------------------------------ ------------------------------
------------------------------
OLTP
AUTOGATHERING 02-24-2009 08:54 02-24-2009
08:56
OLTP
-- C1 = AUTOGATHERING --> 수집 중
-- C1 = COMPLETED
--> 수집 완료 로 변경된다. [ 2분 경과 후 ]
-- 10분동안 시스템 통계정보를 수집 하라 [앞서 수행한 2분동안 통계정보는 Update 된다. ]
execute
DBMS_STATS.GATHER_SYSTEM_STATS(GATHERING_MODE =>’INTERVAL’,INTERVAL=> 10,
STATTAB => ’OLTP’, STATID => ’OLTP’);
-- 시스템 통계정보 수집 확인
SQL>
select STATID, C1, C2, C3 from OLTP;
STATID
C1 C2
C3
-------------------- ------------------------------
------------------------------
------------------------------
OLTP
AUTOGATHERING 02-24-2009 08:56 02-24-2009
08:58
OLTP
-- C1 = AUTOGATHERING --> 수집 중
-- 시스템 통계정보 수집 중지 하기 [ gathering_mode=>’STOP’ ]
SQL> execute
DBMS_STATS.GATHER_SYSTEM_STATS(GATHERING_MODE =>’STOP’,INTERVAL=> 10,
STATTAB => ’OLTP’, STATID => ’OLTP’);
-- 시스템 통계정보 수집 확인
SQL> select STATID, C1, C2, C3 from OLTP;
-- C1
= AUTOGATHERING --> 수집 중
-- C1 = COMPLETED --> 수집 완료 [ 강제로 중지 하여도
COMPLETED 로 나온다. ]
-- 5. OLAP 성 시스템 통계정보 수집하기
SQL> execute
DBMS_STATS.CREATE_STAT_TABLE(’SYS’,’OLAP’,’USERS’);
PL/SQL procedure
successfully completed.
execute DBMS_STATS.GATHER_SYSTEM_STATS(GATHERING_MODE
=>’INTERVAL’,INTERVAL=> 2, STATTAB => ’OLAP’, STATID =>
’OLAP’);
SQL> SELECT STATID, C1, C2, C3 FROM OLAP ;
STATID C1
C2 C3
--------------------
------------------------------ ------------------------------
------------------------------
OLAP
COMPLETED 02-24-2009 09:04 02-24-2009
09:06
OLAP
-- C1 => AUTOGATHERING -- 수집 중
-- C1 => COMPLETED
-- 수집 완료 로 변경된다. [ 2분 경과 후 ]
-- 6. 생성한 OLAP_STATS 시스템 통계정보 IMPORT 하기
-- 기존 통계정보 삭제
execute DBMS_STATS.DELETE_SYSTEM_STATS ;
-- 기존 통계정보를 삭제 하면,
--
SYSSTATS_INFO.DSTART 와 SYSSTATS_INFO.DSTOP 값만 DELETE 시점으로 변경된다.
SQL>
select * from sys.aux_stats$;
SNAME
PNAME PVAL1 PVAL2
------------------------------
-------------------- ---------
------------------------------
SYSSTATS_INFO
STATUS COMPLETED
SYSSTATS_INFO
DSTART 02-24-2009
09:08
SYSSTATS_INFO DSTOP
02-24-2009 09:08
-- OLTP 시스템 통계정보 IMPORT 하기
execute DBMS_STATS.IMPORT_SYSTEM_STATS(stattab
=>’OLTP’, STATID =>’OLTP’,STATOWN =>’SYS’);
-- Import 된 시스템 통계정보
확인
-- OLTP 시스템 통계정보를 수행한 시간으로 SYSSTATS_INFO.DSTART 와 SYSSTATS_INFO.DSTOP
-- 값이 변경되어 진다.
SQL> select * from sys.aux_stats$
;
SNAME PNAME PVAL1
PVAL2
------------------------------ -------------------- ---------
------------------------------
SYSSTATS_INFO
STATUS COMPLETED
SYSSTATS_INFO
DSTART 02-24-2009
08:56
SYSSTATS_INFO DSTOP
02-24-2009 08:58
-- 7. 다시 원복 하기
SQL> execute
DBMS_STATS.DELETE_SYSTEM_STATS ;
PL/SQL procedure successfully completed.
SQL> execute DBMS_STATS.IMPORT_SYSTEM_STATS(stattab =>’ORIGIN’, STATID
=>’ORIGIN’,STATOWN =>’SYS’);
PL/SQL procedure successfully
completed.
SQL> select * from sys.aux_stats$ ;
SNAME
PNAME PVAL1 PVAL2
------------------------------
-------------------- ---------
------------------------------
SYSSTATS_INFO
STATUS COMPLETED
SYSSTATS_INFO
DSTART 02-13-2009
13:33
SYSSTATS_INFO DSTOP
02-13-2009 13:33
-- SYSSTATS_INFO.DSTART 와 SYSSTATS_INFO.DSTOP 로 ORIGIN 으로
변경됨
-- 8. 수동으로 시스템 통계정보 생성하기 [ 파라미터 개별 설정 ]
-- 기존 시스템
통계정보 삭제 하기
execute DBMS_STATS.DELETE_SYSTEM_STATS ;
-- CPUSPEED 설정
--
CPUSPEED : Wait time to read single block, in milliseconds
SQL> execute
DBMS_STATS.SET_SYSTEM_STATS(PNAME =>’CPUSPEED’, PVALUE=>400);
PL/SQL
procedure successfully completed.
-- CPUSPEED 값 변경 확인
SQL> select *
from sys.aux_stats$ WHERE PNAME =’CPUSPEED’;
SNAME
PNAME PVAL1 PVAL2
------------------------------
-------------------- ---------
------------------------------
SYSSTATS_MAIN
CPUSPEED 400
-- SREADTIM 설정
-- SREADTIM : wait time to
read single block, in milliseconds
SQL> execute
DBMS_STATS.SET_SYSTEM_STATS(PNAME =>’SREADTIM’, PVALUE=>100);
PL/SQL
procedure successfully completed.
-- SREADTIM 값 변경 확인
SQL> select *
from sys.aux_stats$ WHERE PNAME =’SREADTIM’;
SNAME
PNAME PVAL1 PVAL2
------------------------------
-------------------- ---------
------------------------------
SYSSTATS_MAIN
SREADTIM 100
-- 9. 원복 하기
SQL> execute
DBMS_STATS.DELETE_SYSTEM_STATS ;
PL/SQL procedure successfully completed.
SQL> execute DBMS_STATS.IMPORT_SYSTEM_STATS(stattab =>’ORIGIN’, STATID
=>’ORIGIN’,STATOWN =>’SYS’);
PL/SQL procedure successfully
completed.
-- 원복 결과 확인
SQL> select * from sys.aux_stats$ ;
SNAME PNAME PVAL1
PVAL2
------------------------------ -------------------- ---------
------------------------------
SYSSTATS_INFO
STATUS COMPLETED
SYSSTATS_INFO
DSTART 02-13-2009
13:33
SYSSTATS_INFO DSTOP
02-13-2009 13:33
[ 2.Fixed Objects Statistics ]
개요 - Dynamic
Performance View와 같이 fixed view(x$ tables)에 대한 통계치는
Manual 한
Gathering 이 필요하다. 이 Fixed Objects Statistics 는
Database 의 Activity
를 기록하게 되므로 database 가 일반적인 운영 상태
일때 gathering 하여야
한다.
일반적으로 Fixed Object Statistics 통계치는 V$ view 를 조회하는
사용자 Query에 필요하다.
수행주기 - 초기 1회
추가적인
Application 이나 변경으로 동시 사용자 등의 변경 발생시
주의사항 - 자동화 대상이 아니다. [
자동으로 Fixed Object의 통계정보가 생성되지 않는다. ]
RAC 에서는 아직 Fixed Objects
Statistics 를 Instance 별로 구분하지
않기 때문에, 가장 부하가 많은 Node(Instance)에서
통계치를 조사한다.
실습
: Fixed Object 통계정보는 DBMS 성능에 지대한 영향을 미치므로,
항상 기존의 Fixed Object 통계정보를 백업 후 진행 하자.
OLTP 와 OLAP 성 Fixed Object 통계정보를
생성하고, IMPORT 해보자
-- 1. 기존의 Fixed Object 통계정보 백업 테이블 생성
SQL> execute
DBMS_STATS.CREATE_STAT_TABLE(’SYS’,’FIX_ORIGIN’,’USERS’);
--
EXPORT_FIXED_OBJECTS_STATS 를 통한 백업 수행
SQL> execute
DBMS_STATS.EXPORT_FIXED_OBJECTS_STATS(stattab =>’FIX_ORIGIN’,STATID
=>’FIX_ORIGIN’,STATOWN =>’SYS’);
PL/SQL procedure successfully
completed.
-- 2. 신규로 Fixed Object 통계정보 테이블 생성
SQL> execute
DBMS_STATS.CREATE_STAT_TABLE(’SYS’,’FIX_NEW’,’USERS’);
-- 신규 Fixed Object
통계정보 수집
SQL> execute
DBMS_STATS.GATHER_FIXED_OBJECTS_STATS(’SYS’,’FIX_NEW’,’SYS’);
BEGIN
DBMS_STATS.GATHER_FIXED_OBJECTS_STATS(’SYS’,’FIX_NEW’,’SYS’); END;
*
ERROR at line 1:
ORA-20000: Insufficient privileges to analyze an
object in Fixed Ob
ORA-06512: at "SYS.DBMS_STATS", line 13578
ORA-06512:
at "SYS.DBMS_STATS", line 13892
ORA-06512: at "SYS.DBMS_STATS", line
14420
ORA-06512: at line 1
SQL> execute DBMS_STATS.GATHER_FIXED_OBJECTS_STATS(’FIX_NEW’);
PL/SQL
procedure successfully completed.
-- 3. 기존 Fixed Object 통계정보 확인 하기
SQL> select
table_name, to_char(last_analyzed, ’yyyymmdd hh24:mi:ss hh24:mi:ss’)
from dba_tab_statistics
where table_name like ’X$%’ ;
TABLE_NAME
TO_CHAR(LAST_ANALYZED,’YYY
------------------------------
--------------------------
X$KQFTA
X$KQFVI
X$KQFVT
X$KQFDT
-- 4. 신규 Fixed Object 통계정보 IMPORT
-- FIXED TABLE 의
통계정보 삭제
SQL> execute DBMS_STATS.DELETE_FIXED_OBJECTS_STATS ;
PL/SQL
procedure successfully completed.
-- 신규 Fixed Object 통계정보 Import
SQL>
execute DBMS_STATS.IMPORT_FIXED_OBJECTS_STATS(stattab =>’FIX_NEW’, STATID
=>’FIX_NEW’,STATOWN =>’SYS’);
PL/SQL procedure successfully
completed.
-- 5. 신규 Fixed Object 통계정보 확인 하기
SQL> select
table_name, to_char(last_analyzed, ’yyyymmdd hh24:mi:ss hh24:mi:ss’)
from dba_tab_statistics
where table_name like ’X$%’ ;
TABLE_NAME
TO_CHAR(LAST_ANALYZED,’YYY
------------------------------
--------------------------
X$KQFTA 20090224 09:50:34
09:50:34
X$KQFVI 20090224 09:50:34
09:50:34
X$KQFVT 20090224 09:50:34
09:50:34
X$KQFDT 20090224 09:50:34
09:50:34
...
일부는 last_analyzed 가 Update 되지 않는다.
Note that the database
can decide not to collect stats for objects
that were either never used or
are totally volatile.
6. Fixed Object 통계정보 원복 하기
SQL> execute
DBMS_STATS.DELETE_FIXED_OBJECTS_STATS ;
PL/SQL procedure successfully
completed.
SQL> execute DBMS_STATS.IMPORT_FIXED_OBJECTS_STATS(stattab
=>’FIX_ORIGIN’, STATID =>’FIX_ORIGIN’,STATOWN =>’SYS’);
PL/SQL
procedure successfully completed.
-- 복원된 정보 확인 하기
SQL > select table_name, to_char(last_analyzed,
’yyyymmdd hh24:mi:ss hh24:mi:ss’)
from dba_tab_statistics
where table_name like ’X$%’ ;
[ 3.Dictionary Statistics ]
개요 -
DBMS_STATS.GATHER_DICTIONARY_STATS 를 이용하여 Sys, System Schema 의
Object를 Gathering 한다. 이 procedure 는 또한 DRSYS 나 CTX user Schema의
Object 도 함께 Gathering 한다.
수행주기 - 초기
1회
Database Object(사용자 Table, PL/SQl, User생성) 의 변경이 있는
경우
실습
: Dictionary 통계정보는 DBMS 성능에 지대한
영향을 미치므로,
항상 기존의 Dictionary 통계정보를 백업 후 진행 하자.
OLTP 와 OLAP 성
Dictionary 통계정보를 생성하고, IMPORT 해보자
-- 1. 기존의 Dictionary 통계정보 통계정보 백업 테이블 생성
SQL>
execute DBMS_STATS.CREATE_STAT_TABLE(’SYS’,’SYS_ORIGIN’,’USERS’);
PL/SQL
procedure successfully completed.
-- EXPORT_DICTIONARY_STATS 를 통한 백업 수행
SQL> execute DBMS_STATS.EXPORT_DICTIONARY_STATS(stattab
=>’SYS_ORIGIN’,STATID =>’SYS_ORIGIN’,STATOWN =>’SYS’);
PL/SQL
procedure successfully completed.
-- 2. 신규로 Dictionary 통계정보 테이블 생성
SQL> execute
DBMS_STATS.CREATE_STAT_TABLE(’SYS’,’SYS_NEW’,’USERS’);
PL/SQL procedure
successfully completed.
-- 신규 Dictionary 통계정보 수집
SQL> execute
DBMS_STATS.GATHER_DICTIONARY_STATS(’SYS_NEW’);
PL/SQL procedure successfully
completed.
3. 신규 Dictionary 통계정보 확인 하기
SQL> select table_name,
to_char(last_analyzed, ’yyyymmdd hh24:mi:ss hh24:mi:ss’)
from
dba_tab_statistics
where table_name like ’%$’
order by
2
....
X$LOGMNR_OBJ$ 20090224 10:07:20
10:07:20
X$LOGMNR_TABCOMPART$ 20090224 10:07:20
10:07:20
X$LOGMNR_USER$ 20090224 10:07:20
10:07:20
SUMDELTA$
SDO_TOPO_DATA$
...
일부는 last_analyzed 가 Update 되지
않는다.
Note that the database can decide not to collect stats for objects
that were either never used or are totally volatile.
4. Dictionary 통계정보 IMPORT 하기
SQL> execute
DBMS_STATS.DELETE_DICTIONARY_STATS ;
PL/SQL procedure successfully
completed.
-- 신규 Fixed Object 통계정보 Import
SQL> execute
DBMS_STATS.IMPORT_DICTIONARY_STATS(stattab =>’SYS_NEW’, STATID
=>’SYS_NEW’,STATOWN =>’SYS’);
PL/SQL procedure successfully
completed.
-- IMPORT 된 Dictionary 통계정보 확인 하기
SQL> select table_name,
to_char(last_analyzed, ’yyyymmdd hh24:mi:ss hh24:mi:ss’)
from
dba_tab_statistics
where table_name like ’%$’
order by 2;
SRS$ 20081229 22:00:05
22:00:05
X$LOGMNR_ATTRIBUTE$ 20090224 10:07:19
10:07:19
X$LOGMNR_COLTYPE$ 20090224 10:07:19
10:07:19
X$LOGMNR_IND$ 20090224 10:07:19
10:07:19
X$LOGMNR_COL$ 20090224 10:07:19
10:07:19
X$LOGMNR_DICT$ 20090224 10:07:19 10:07:19
==>
테스트 에서는 실제로 Dictionary 통계정보를 가진 Table Count 가 오히려 줄어들었다.
5. Fixed Object 통계정보 원복 하기
SQL> execute
DBMS_STATS.DELETE_DICTIONARY_STATS ;
PL/SQL procedure successfully
completed.
SQL> execute DBMS_STATS.IMPORT_DICTIONARY_STATS(stattab =>’SYS_ORIGIN’,
STATID =>’SYS_ORIGIN’,STATOWN =>’SYS’);
PL/SQL procedure successfully
completed.
-- 복원된 정보 확인 하기
SQL> select table_name, to_char(last_analyzed,
’yyyymmdd hh24:mi:ss hh24:mi:ss’)
from dba_tab_statistics
where table_name like ’%$’
order by 2;
[ 4.User Table Statistics ]
: 사용자의 Object 에 대한 통계치 수집은
10G 에서 기본적인 GATHER_STATS_JOB 을
이용한다. 이 JOB 은 기존 DATA의 10% 이상의 변경이 있거나, 오랫동안
통계치가
변경되지 않거나, 통계치가 없거나 한 Object 에 대한 Gathering 을 수행한다.
또한 분석하는 순서 역시 우선순위
순으로 수행한다.
만약 Object 에 많은 Data 가 Load 되거나 변경이 있는 경우에는 Manual 하게
수행한다.
- 10G 에서 말하는 자동 통계정보 수집 기능(Automatic Statistics) 의 대상은
1. AWR(Automactic Workload Repository)
- 분석, Self-Tuing 및 일반적인
Tuning 을 목적으로 하는 자료로
특정 시간 동안 데이타 베이스에서 발생한 여러 가지 상황 정보를 이른다.
Wait Events, Latces, Enqueues, Cpu Consumption, SGA Compoenent, PGA 등에
대한 자료 이다.
’STATISTICS_LEVEL’ 에 의해서 수집되는 자료의 LEVEL 이
결정된다.
AWR 정보는 디폴트로 7일간 보관한다. [ SYSAUX TABLESPACE 에 ]
2. CBO(Cost-Based Optimizer)
-
Database 의 Object 즉, Application 및 Oracle Internal (Sys/System) 유저의
Table, Index 에 대한 통계정보 수집
이 수집된 정보는 Optimizer 가 수행계획 수립 시에 의해 사용된다.
GATHER_STATS_JOB 에 의해서 수집 된다. [DB 생성시 자동
생성됨]
Optimizer historical 통계정보는 디폴트로 31일간 보관한다.
STATISTICS_LEVEL=BASIC 이면 CBO 통계정보가 수집되지
않는다.
- Automatic Optimizer Statistics Collection
- Object level Statistics
[ SYSAUX TABLESPACE 에 ]
-- SYSAUX 사용 현황 파악 하기
SELECT occupant_name,
space_usage_kbytes FROM V$SYSAUX_OCCUPANTS;
1. SM/AWR
- AWR 정보 수집
- AWR 정보 수집 옵션 확인
SQL> SELECT DBID,
RETENTION, TOPNSQL FROM DBA_HIST_WR_CONTROL ;
DBID
RETENTION TOPNSQL
----------
------------------------------ ----------
2466823093 +00007
00:00:00.0 DEFAULT
보존 변경은
dbms_workload_repository.modify_snapshot_settings 를 통해서 가능
-- AWR
정보 한달 보관주기로 변경 [ 60*24*31 = 44640 분 ]
SQL> EXECUTE
DBMS_WORKLOAD_REPOSITORY.MODIFY_SNAPSHOT_SETTINGS(retention => 44640
);
PL/SQL procedure successfully completed.
-- 변경되 AWR 정보 확인
SQL> SELECT DBID, RETENTION, TOPNSQL FROM DBA_HIST_WR_CONTROL
;
DBID RETENTION TOPNSQL
---------- ------------------------------ ----------
2466823093 +00031
00:00:00.0 DEFAULT
2. SM/ADVISOR
SQL Tuning Advisor, SQL Access
Advisor, ADDM 이 사용하는 정보 저장소
3. SM/OPTSTAT
- 구버전(Old) Optimizer 통계정보
저장소
- SM/OPTSTAT 저장 기간 확인
SQL> select
dbms_stats.get_stats_history_retention from dual;
GET_STATS_HISTORY_RETENTION
---------------------------
31
==>
Default 로 31일
-- 10일로 조절
SQL> exec
dbms_stats.alter_stats_history_retention(10);
PL/SQL procedure
successfully completed.
SQL> select dbms_stats.get_stats_history_retention from dual;
GET_STATS_HISTORY_RETENTION
---------------------------
10
-- 원복
SQL> exec dbms_stats.alter_stats_history_retention(31);
PL/SQL
procedure successfully completed.
4. SM/OTHER
- Alert History 등의 저장소
-- STATISTICS_LEVEL PARAMETER
: Database 와 OS 의
통계정보의 수집 Level 을 제어 하는 파라미터
1.Typical
-
Default, 일반적인 환경에 가장 적합
2. ALL
- typical +
Timed OS Statistics + Plan Execution Statistics
3. BASIC
- 아래 기능을에 필요한 중요한 통계정보를 수집 할 수 없다.
- AWR
- ADDM
- All Server-Generated
Alerts
- Automatic SGA Memory Management
- Automatic
Optimizer Statistics Collection
- Object level
Statistics
등...
실습
: 여기서 말하는 자동 통계수집 이란 CBO 에 대한 것을 이른다.
- 1. 자동 통계수집일정을
확인하고
- 2. 자동 통계수집을 Disable 해보자
- 3. 통계정보 백업 / 복구 하기
-
4. 특정 테이블 통계수집 중지 하기
-- 1.1 자동통계정보 수집 확인 하기
SQL
> select job_name, job_type, program_name, schedule_name, job_class
from dba_scheduler_jobs
where job_name =’GATHER_STATS_JOB’;
JOB_NAME JOB_TYPE PROGRAM_NAME
SCHEDULE_NAME JOB_CLASS
--------------------
---------------- -------------------- ------------------------------
------------------------------
GATHER_STATS_JOB
GATHER_STATS_PROG MAINTENANCE_WINDOW_GROUP AUTO_TASKS_JOB_CLASS
-- 1.2 자동통계정보 수집 시 실행 되는 Program 확인
SQL> select
program_Action from dba_scheduler_programs where program_name
=’GATHER_STATS_PROG’;
PROGRAM_ACTION
--------------------------------------------------
dbms_stats.gather_database_stats_job_proc
-- 1.3 자동통계정보 수집 시 스케줄 확인
SQL> select * from
dba_scheduler_wingroup_members where window_group_name
=’MAINTENANCE_WINDOW_GROUP’;
WINDOW_GROUP_NAME WINDOW_NAME
------------------------------
------------------------------
MAINTENANCE_WINDOW_GROUP
WEEKNIGHT_WINDOW
MAINTENANCE_WINDOW_GROUP WEEKEND_WINDOW
-- 1.4 자동통계정보 수집 시 스케줄 상세 확인
SQL> select
window_name, repeat_interval, duration
from
dba_scheduler_windows
where window_name in
(’WEEKNIGHT_WINDOW’,’WEEKEND_WINDOW’);
WINDOW_NAME
REPEAT_INTERVAL
DURATION
--------------------
--------------------------------------------------------------------------------
--------------------
WEEKNIGHT_WINDOW
freq=daily;byday=MON,TUE,WED,THU,FRI;byhour=22;byminute=0; bysecond=0
+000 08:00:00
WEEKEND_WINDOW
freq=daily;byday=SAT;byhour=0;byminute=0;bysecond=0
+002 00:00:00
-- 매주 월,화,수,목,금요일은 밤 10시에 8시간 동안 수행 된다.
-- 토요일 0시에 수행되어 이틀 동안 수행된다.
-- 2.1 자동통계정보 수집 중지
-- STATISTICS_LEVEL=BASIC 이면 자동통계정보 수집(CBO)
SQL> select job_name, state from dba_scheduler_jobs where
job_name =’GATHER_STATS_JOB’;
JOB_NAME STATE
--------------------
---------------
GATHER_STATS_JOB SCHEDULED
SQL> exec dbms_scheduler.disable(’GATHER_STATS_JOB’);
PL/SQL procedure successfully completed.
SQL> select job_name, state from dba_scheduler_jobs where job_name =’GATHER_STATS_JOB’;
JOB_NAME STATE
--------------------
---------------
GATHER_STATS_JOB DISABLED
-- 2.2 자동통계정보 수집 재설정
SQL> exec
dbms_scheduler.enable(’GATHER_STATS_JOB’);
PL/SQL procedure successfully completed.
SQL> select job_name, state from dba_scheduler_jobs where job_name =’GATHER_STATS_JOB’;
JOB_NAME STATE
--------------------
---------------
GATHER_STATS_JOB SCHEDULED
-- 3.1 통계정보 백업 / 복구 하기
-- 유저 테이블 통계정보 백업 받을 테이블 생성하기
SQL> execute
dbms_stats.create_stat_table(’SYS’,’USER_STATS’,’USERS’);
PL/SQL procedure
successfully completed.
-- SCOTT 유저 테이블 통계정보 백업 받기
SQL> execute
dbms_stats.export_schema_stats(’SCOTT’,’USER_STATS’,’SCOTT’,’SYS’);
PL/SQL
procedure successfully completed.
-- 백업된 SCOTT 유저의 통계정보 확인
SQL> select
STATID, C1, C2, C4, D1 from USER_STATS ;
-- 3.2 신규로 유저 테이블 통계정보 생성
SQL> execute
dbms_stats.gather_schema_stats(ownname=>’SCOTT’, ESTIMATE_PERCENT =>
DBMS_STATS.AUTO_SAMPLE_SIZE,-
GRANULARITY => ’AUTO’, DEGREE =>
null, METHOD_OPT => ’FOR ALL COLUMNS SIZE 1’, -
CASCADE => TRUE
);
-- 신규로 유저 테이블 통계정보 확인
select
OWNER,TABLE_NAME,PARTITION_NAME,LAST_ANALYZED
from dba_tab_statistics
WHERE OWNER=’SCOTT’
ORDER BY LAST_ANALYZED DESC ;
-- 날짜에 주목하자
-- 3.3 유저 테이블 통계정보 원복하기
SQL> exec
dbms_stats.delete_schema_stats(’SCOTT’);
PL/SQL procedure successfully
completed.
SQL> exec
dbms_stats.import_schema_stats(’SCOTT’,’USER_STATS’,’USER_STATS’,’SYS’);
PL/SQL
procedure successfully completed.
-- 3.4 신규로 유저 테이블 통계정보 확인
select OWNER,TABLE_NAME,PARTITION_NAME,LAST_ANALYZED
from
dba_tab_statistics
WHERE OWNER=’SCOTT’
ORDER BY LAST_ANALYZED DESC ;
-- 날짜에 주목하자
-- 4.1 특정 테이블 통계수집 중지 하기
-- 수동으로 통계정보 수집 하여
LAST_ANALYZED Update 하기
SQL> execute
dbms_stats.gather_schema_stats(ownname=>’SCOTT’, ESTIMATE_PERCENT =>
DBMS_STATS.AUTO_SAMPLE_SIZE,-
GRANULARITY => ’AUTO’, DEGREE =>
null, METHOD_OPT => ’FOR ALL COLUMNS SIZE 1’, -
CASCADE => TRUE
);
PL/SQL procedure successfully completed.
-- 4.2 신규로 유저 테이블 통계정보
확인
select OWNER,TABLE_NAME,PARTITION_NAME,LAST_ANALYZED
from
dba_tab_statistics
WHERE OWNER=’SCOTT’
ORDER BY LAST_ANALYZED DESC ;
-- 날짜에 주목하자
-- 특정 테이블 통계정보 수집 막기
SQL> execute
dbms_stats.lock_table_stats(’SCOTT’,’T1’);
PL/SQL procedure successfully
completed.
-- 특정 테이블 통계정보 수집 막음 확인
SQL> SELECT owner, table_name,
stattype_locked
FROM dba_tab_statistics
WHERE
OWNER=’SCOTT’
and stattype_locked is not null;
OWNER TABLE_NAME
STATT
------------------------------ ------------------------------
-----
SCOTT T1 ALL
-- 확인을 위해서 수동으로 통계정보 수집 하여 LAST_ANALYZED Update 하기
SQL> execute
dbms_stats.gather_schema_stats(ownname=>’SCOTT’, ESTIMATE_PERCENT =>
DBMS_STATS.AUTO_SAMPLE_SIZE,-
> GRANULARITY =>’AUTO’, DEGREE =>
null, METHOD_OPT => ’FOR ALL COLUMNS SIZE 1’, -
> CASCADE => TRUE
);
PL/SQL procedure successfully completed.
SQL> select OWNER,TABLE_NAME, to_char(last_analyzed, ’yyyymmdd hh24:mi:ss
hh24:mi:ss’)
from dba_tab_statistics
WHERE
OWNER=’SCOTT’
and table_name in (’T1’,’EMP’,’DEPT’);
OWNER TABLE_NAME
TO_CHAR(LAST_ANALYZED,’YYY
------------------------------
------------------------------
--------------------------
SCOTT
DEPT 20090224 11:37:57
11:37:57
SCOTT EMP
20090224 11:37:57 11:37:57
SCOTT
T1 20090224 11:32:53 11:32:53
==> Lock 되어진 T1 에 대해서는 테이블 통계정보가 생성되지 않았음을 확인
- 외국인과의 혼인비율이 11.1%나 차지한다. 2005년에는 13.6%까지 올라갔었다.
- 평균 초혼연령은 남성 31.1세, 여성 28.1세로 계속 증가추세
- 사망률 순위 : 자살로 인한 사망률이 1997년에 비해 거의 1.7배정도 는데 비해 운수사고로 인한 사망률이 반으로 떨어졌다. 사망원인 순위는 암-> 뇌혈관 질환 -> 심장질환 -> 자살 -> 당뇨병 순
- 한국영화 관객점유율은 2006년 63.8을 피크로 감소추세. 2008년에는 42.1%
- 범죄발생건수는 2008년 196만 6천건으로 전년에 비해 7.5%증가(절도, 상해의 증가가 큼)
http://wiki.oracleclub.com/pages/viewpage.action?pageId=4325453
* DBMS_XPLAN
1) dbms_xplan.display
- explain plan for select * from t1;
- select * from table(dbms_xplan.display('plan_table', null, 'typical', null ));
-- 디폴트(dbms_xplan.display) 와 동일한 결과
- select * from table(dbms_xplan.display('plan_table', null, 'all'));
-- Query Block 정보, 추출하는 컬럼정보, /*+ qb_name(x) */ 힌트로 query block 명 조작가능
- select * from table(dbms_xplan.display('plan_table', null, 'outline')); -- 필요한(사용된) 힌트를 나열
- select * from table(dbms_xplan.display('plan_table', null, 'advanced')); -- all, outline 정보 같이 보여줌
2) dbms_xplan.display_cursor
- 통계정보 생성(t1테이블)
exec dbms_stats.gather_table_stats(user, 't1', cascade=>true, no_invalidate=>false);
: 인덱스가 있는 컬럼에 대해 histogram 생성하면서 통계생성
exec dbms_stats.gather_table_stats(user, 't1', method_opt=>'for all indexed columns size skewonly');
- gather_plan_statistics 힌트 : 쿼리 수행시 예측 Row수와 실제 Row수를 기록한다.
select /*+ gather _plan_statistics */ count(*) from t1 where c1 = 'Many2';
- select * from table(dbms_xplan.display_cursor(null, null, 'allstats last'));
-- 현재 세션에 대한 최근 쿼리 결과조회 ( SQL ID, Child number, 옵션)
- 테이블 정보조회
select a.table_name, a.num_rows, a.blocks, a.sample_size, a.last_analyzed,
s.column_name, s.num_distinct, s.num_nulls, s.density, s.low_value, s.high_value, s.histogram
from user_tab_statistics a, user_tab_cols s
where a.table_name = 'T1'
and a.table_name = s.table_name;
select * from user_histograms
where table_name = 'T1'
order by column_name, endpoint_value;
* Index and CBO
1) Clustering Factor
- Index Scan의 Cost 계산
Cost = Blevel + Leaf Blocks * index Selectivity + Clustering Factor * Table Selectivity + Adjusted CPU
- index 생성시 sequence key 값을 사용하거나, 시간순으로 date형을 사용하면 clustering factor가 좋게(낮게) 나온다. clustering factor를 개선시키려면 테이블을 다시 생성(order by 로 reinsert)해야 함.
PLAN TABLE같은데 적용해놓으면 편할 듯 하다.
CREATE GLOBAL TEMPORARY TABLE PLAN_TABLE
(
STATEMENT_ID VARCHAR2(30 BYTE),
PLAN_ID NUMBER,
TIMESTAMP DATE,
REMARKS VARCHAR2(4000 BYTE),
OPERATION VARCHAR2(30 BYTE),
OPTIONS VARCHAR2(255 BYTE),
OBJECT_NODE VARCHAR2(128 BYTE),
OBJECT_OWNER VARCHAR2(30 BYTE),
OBJECT_NAME VARCHAR2(30 BYTE),
OBJECT_ALIAS VARCHAR2(65 BYTE),
OBJECT_INSTANCE INTEGER,
OBJECT_TYPE VARCHAR2(30 BYTE),
OPTIMIZER VARCHAR2(255 BYTE),
SEARCH_COLUMNS NUMBER,
ID INTEGER,
PARENT_ID INTEGER,
DEPTH INTEGER,
POSITION INTEGER,
COST INTEGER,
CARDINALITY INTEGER,
BYTES INTEGER,
OTHER_TAG VARCHAR2(255 BYTE),
PARTITION_START VARCHAR2(255 BYTE),
PARTITION_STOP VARCHAR2(255 BYTE),
PARTITION_ID INTEGER,
OTHER LONG,
DISTRIBUTION VARCHAR2(30 BYTE),
CPU_COST INTEGER,
IO_COST INTEGER,
TEMP_SPACE INTEGER,
ACCESS_PREDICATES VARCHAR2(4000 BYTE),
FILTER_PREDICATES VARCHAR2(4000 BYTE),
PROJECTION VARCHAR2(4000 BYTE),
TIME INTEGER,
QBLOCK_NAME VARCHAR2(30 BYTE),
OTHER_XML CLOB
)
ON COMMIT PRESERVE ROWS
NOCACHE;
GRANT DELETE, INSERT, SELECT, UPDATE ON PLAN_TABLE TO PUBLIC;
Oracle8.1에서는 session내에서 임시로 사용할 data들을 영구적인 segment형태가
아닌 temporary structure에서 관리할 수 있다.
이러한 Temporary Table들은 그 생성문장에 의해서 definition이 dictionary에
저장되고, 각 session에서 해당 table을 사용할 때마다 definition을 이용하여
memory에 table 구조를 생성하게 된다.
CHARACTERISTICS
---------------
1. data는 session private하다. (특정 session에서 사용하는 temporary table
data는 다른 session에서 access할 수 없다.)
2. CREATE GLOBAL TEMPORARY TABLE ...
ON COMMIT [DELETE|PRESERVE] ROWS ; 문을 이용하여 생성한다.
3. data의 유지기간은 transaction단위 또는 session단위이다.
'ON COMMIT DELETE ROWS'로 생성되었다면 transaction단위이며 이때 data는
commit이 되는 시점에 자동으로 제거된다.
'ON COMMIT PRESERVE ROWS'로 생성되었다면 session단위이며 이때 data는
해당 session이 종료되면서 사라진다.
default는 'ON COMMIT DELETE ROWS'이다.
4. table의 definition은 dictionary에 permanently 저장된다.
*_tables의 TEMPORARY, DURATION column이 temporary table과 관련이 있다.
TEMPORARY - 'Y' : temporary type table
'N' : permanent type table
DURATION - 'SYS$SESSION' : data의 유지기간이 session단위
'SYS$TRANSACTION' : data의 유지기간이 transaction단위
NULL : 해당 table은 temporary type이 아님
5. session간에 data에 대한 contention이 발생되지 않기 때문에 DML문에 대한
lock이 필요하지 않다.
6. 임시적으로만 관리되는 data이므로 DML문에 대해서 redo log를 발생시키지
않는다.
7. index, view, trigger를 생성하여 사용할 수 있다. Temporary Table의
column에 생성되는 index도 temporary type이다.
8. table의 definition은 export utility를 이용하여 export할 수 있다.
그러나 그 row들은 export의 대상이 될 수 없다.
RESTRICTIONS
------------
1. partitioned, index-organized, clustered table로 생성할 수 없다.
2. foreign key constraint를 설정할 수 없다.
3. nested table이나 varray type의 column은 포함할 수 없다.
4. 다음과 같은 LOB_storage_clause들은 지정할 수 없다. : TABLESPACE,
storage_clause, LOGGING 또는 NOLOGGING, MONITORING 또는 NOMONITORING,
또는 LOB_index_clause.
5. parallel DML이나 parallel query는 지원되지 않는다. (parallel hint는
무시될 것이며, table생성시 parallel clause를 지정하면 error를
return한다.)
6. storage나 tablespace는 지정할 수 없다.
7. 분산 transaction은 지원되지 않는다.
SAMPLE
------
----------------------------------------------------------------------
-- temporary type table을 생성하되 duration은 transaction단위로 한다.
----------------------------------------------------------------------
SQL> create global temporary table temp_tab
2 (col1 number, col2 char(10))
3 on commit delete rows ;
-----------------------------------------------------------------------
-- 생성된 table에 대한 정보를 조회한다. *_tables의 TEMPORARY, DURATION
-- column이 temporary type table과 관련이 있다.
-----------------------------------------------------------------------
SQL> select temporary, duration
2 from user_tables
3 where table_name = 'TEMP_TAB' ;
T DURATION
- ---------------
Y SYS$TRANSACTION
-----------------------------------------------------------------------
-- temporary table에 row insert
-----------------------------------------------------------------------
SQL> insert into temp_tab values (1, 'wookpark') ;
SQL> select * from temp_tab ;
COL1 COL2
--------- ----------
1 wookpark
-----------------------------------------------------------------------
-- transction을 commit한다.
-----------------------------------------------------------------------
SQL> commit ;
Commit complete.
-----------------------------------------------------------------------
-- duration이 transaction단위이기 때문에 commit을 수행하면 모든 data가
-- 사라진다.
-----------------------------------------------------------------------
SQL> select * from temp_tab ;
no rows selected
from oracle
ppt형식으로 되어있는데, 생각보다 내용설명이 간결하면서 이해하기 쉽게 되어있다.
- RAC환경에서 주로 발생하는 gc cr/current 류의 wait event 는 주로 Global Cache 동기화(Cache Fusion)와 관련하여 발생
gc 는 read시, current는 변경시 발생
- Sequence 의 nocache : nextval 호출시마다 Dictionary 변경필요함.
cf) RAC의 경우 cache를 주면 cache를 소진할 동안은 상호 통신이 필요없다. 단 ordered 속성을 주면 SV락을 통한 동기화가 필요하다. 특별한 필요가 없으면 cache와 noorder 속성으로 만들자.
- db file scattered read 이벤트관련(full scan)
: Insert, Delete 가 빈번한 interface성, temp성 테이블의 경우 delete 이후 통계정보가 생성되었다면 full scan이 발생할 수 있다.(주의)
- latch: cache buffers chains 이벤트
: 주로 hot block이나 bad SQL에 의해 동일 블록에 대한 접근이 동시에 일어날때 발생
cf) Bad SQL의 문제일 경우 parallel query 로 변경하는것도 검토필요하다. parallel query의 경우 SGA를 거치지 않기 때문에 버퍼캐쉬 경합 자체가 없다.(parallel query실행시 더티버퍼를 디스크에 쓰도록 Segement 체크포인트가 수행됨으로 성능문제 고려)
- 오라클의 physical I/O
: OS상에서의 캐쉬, 스토리지 캐쉬가 있기 때문에 모두가 디스크 I/O 라고 보기는 힘들다.
- Keep 버퍼풀 사용하기
1) DB_KEEP_CACHE_SIZE = 352321536 또는 alter system set DB_KEEP_CACHE_SIZE = 300M;
2) 인덱스를 keep 버퍼에 올리기
alter index schema.index_name storage(buffer_pool keep);
- 다이렉트 로그 인서트 ( insert /*+ append */ into t1 select * from t2)
: high water 마크 이후부터 insert 작업을 함에 따라 테이블에 X모드의 TM락을 획득하고 작업함.
(insert, update, delete 동시 실행 못함)
카네기 멜론의 랜디 포시 교수의 마지막 강의..
- 아이들의 상상력, 자신감, 성취감을 위해 방안을 마음대로 그리게 하고, 자신의 새차 뒷자석에 콜라를 쏟어버리는 그의 행동
- 어릴적 꿈을 향해 끊임 없이 노력하고, 다른 사람들에게 베풀고자 함
- 헤드 페이크 : 쉬는 시간에 물을 향해 뛰는 아이들을 마구 야단치던 축구코치, 힘들때마다 생각하며 노력
과정에 푹 빠져들때까지 배우는 사람으로 하여금 자신이 진정 배우는 것이 무엇인지 모르게 하는 속임수
- 장벽은 나를 막기 위해 있는 것이 아니라, 나보다 그것을 덜 갈망하는 사람들을 막기위해 있는 것이라고..
* v$event_name
: wait event의 종류를 볼 수 있다. 10g에서는 878개의 event가 있다.
oracle 버전간 값을 일치시키기 위해 event_id, wait_class_id 같은 hash값을 보관한다.
* v$system_event
: 인스턴스 구동 이후 대기이벤트의 누적 값으로 total_waits는 대기횟수, time_waited는 대기시간으로 1/100초(centisecond) 단위이다.
select b.wait_class, a.*, c.startup_time
from v$system_event a, v$event_name b, v$instance c
where a.event = b.name
order by b.wait_class, a.time_waited desc
* v$session_event
: v$system_event와 칼럼구성이 동일하며 세션 생성이후의 누적 값이다.
select b.sid, b.username, b.program,
a.event,
a.total_waits,
a.total_timeouts,
a.time_waited,
a.average_wait,
a.max_wait,
a.time_waited_micro
from v$session_event a, v$session b
where b.sid = a.sid
order by 1, 7 desc;
* v$active_session_history 에 v$session_event의 매초마다의 이력을 보관(30분간)
* v$session_wait
: 현재시점에서의 sid별 wait event정보를 보관하며, 10g에서는 v$session 뷰에 통합되었다.
v$session_wait_history에 최근 10개 대기이벤트의 히스토리가 저장되어 있다.
* v$system_wait_class, v$session_wait_class
: 시스템과 세션에 대해 이벤트 class별로 wait횟수와 시간을 저장해 둔다. system-> session순으로 문제되는 wait 종류를 확인할 수 있다.
* v$event_histogram
: event별로 < 1ms, < 2ms, <4ms, <8ms, < 16ms 등으로 누적 횟수를 히스토그램으로 보관. ms(1/1000초)
* 기타 중요 다이나믹 뷰
v$session : 세션정보
v$active_session_history : 세션 히스토리정보
v$process : 프로세스 정보
v$transaction : 트랜잭션 정보
v$latch, v$latch_parent, v$latch_children, v$latch_holder : 래치정보
v$lock, v$locked_object, v$enqueue_lock : 락정보
v$sql : SQL정보
v$librarycache, x$kgllk, x$kglpn : Library Cache 정보
v$rowcache, v$rowcache_parent : Row Cache 정보
v$sgastat : SGA정보
v$segment_statistics : 세그먼트 레벨 통계정보
v$sess_time_model, v$sys_time_model : Time Model 정보
v$bh, x$bh : 버퍼 캐시 정보
* trace
--alter session set timed_statistics=true;
alter session set sql_trace=true;
alter session set sql_trace=false;
EXECUTE DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION(123,1234,TRUE);
EXECUTE DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION(123,1234,FALSE);
--EXEC DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION(sid=>123, serial#=>1234, sql_trace=>TRUE);
--EXEC DBMS_SYSTEM.SET_SQL_TRACE_IN_SESSION(sid=>123, serial#=>1234, sql_trace=>FALSE);
* 10046 trace( 0: 트레이스 안함, 1: 일반 trace, 4: 바인드변수값, 8: 대기이벤트, 12: 바인드변수 & 대기이벤트)
ALTER SESSION SET EVENTS '10046 trace name context forever, level 8';
ALTER SESSION SET EVENTS '10046 trace name context off';
EXECUTE DBMS_SYSTEM.SET_EV(123,1234,10046,8,'');
EXECUTE DBMS_SYSTEM.SET_EV(123,1234,10046,0,'');
--EXEC DBMS_SYSTEM.SET_EV(si=>123, se=>1234, ev=>10046, le=>8, nm=>’ ’);
--EXEC DBMS_SYSTEM.SET_EV(si=>123, se=>1234, ev=>10046, le=>0, nm=>’ ’);
* tkprof
tkprof ora_11111.trc result.txt explain=scott/tiger aggregate=no sys=no sort=fchela
1) EXPLAIN : TKPROF 유틸리티 실행시의 실행 계획을 출력하기 위한 유저명/패스워드를 지정한다
2) SORT옵션 (지정한 옵션에 의해서 내림차순으로 sql가 출력, 이 외에도 다수의 옵션)
exeela:실행시의 경과시간순서
exedsk:실행시의 디스크 액세스 블록수순
exeqry:실행시의 액세스 블록수순
fchela:페치시의 경과시간순서
fchdsk:페치시의 디스크 액세스 블록수순
fchqry:페치시의 액세스 블록수순
로우/컬럼 |
설 명 |
|
Parse |
SQL문이 파싱되는 단계에 대한 통계. |
|
Execute |
SQL문의 실행 단계에 대한 통계. Update, Insert, Delete문장들은 여기에 수행한 결과만 나옵니다. |
|
Fetch |
SQL문이 실행되면서 페치된 통계. |
|
count |
SQL문이 파싱/실행/페치가 수행된 횟수 |
|
cpu |
parse, execute, fetch가 실제로 사용한 CPU시간(1/100초 단위) |
|
elapsed |
작업의 시작에서 종료시까지 실제 소요된 시간 |
|
disk |
디스크에서 읽혀진 데이터 블럭의 수 |
|
query |
메모리내에서 변경되지 않은 블럭을 읽거나 다른 세션에 의해 변경되었으나 |
|
current |
현 세선에서 작업한 내용을 커밋하지 않아 오로지 자신에게만 유효한 |
|
rows |
SQL문을 수행한 결과에 의해 최종적으로 액세스된 로우의 수 |
* awr report
cd $ORACLE_HOME/rdbms/admin
SQL> @awrrpt
* sid로 수행시간 조회
select event, time_waited as time_spent
from v$session_event
where sid = &sid
and event not in (
'Null event',
'client message',
'KXFX: Execution Message Dequeue - Slave',
'PX Deq: Execution Msg',
'KXFQ: kxfqdeq - normal deqeue',
'PX Deq: Table Q Normal',
'Wait for credit - send blocked',
'PX Deq Credit: send blkd',
'Wait for credit - need buffer to send',
'PX Deq Credit: need buffer',
'Wait for credit - free buffer',
'PX Deq Credit: free buffer',
'parallel query dequeue wait',
'PX Deque wait',
'Parallel Query Idle Wait - Slaves',
'PX Idle Wait',
'slave wait',
'dispatcher timer',
'virtual circuit status',
'pipe get',
'rdbms ipc message',
'rdbms ipc reply',
'pmon timer',
'smon timer',
'PL/SQL lock timer',
'SQL*Net message from client',
'WMON goes to sleep')
union all
select b.name, a.value
from v$sesstat a, v$statname b
where a.statistic# = b.statistic#
and b.name = 'CPU used when call started'
and a.sid = &sid;
* buffer cache hit ratio
SELECT 1 - (phy.value / (cur.value + con.value) ) "CACHE HIT RATIO"
FROM v$sysstat cur, v$sysstat con, v$sysstat phy
WHERE cur.name = 'db block gets'
AND con.name = 'consistent gets'
AND phy.name = 'physical reads' ;
출처: http://www.oracle.com/technology/global/kr/pub/columns/dbtuning.html
글: 안진철 (jcahn@warevalley.com)
[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 )
latch free ( library cache load lock) |
library cache lock, library cache pin
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의 적절한 크기와 관련 파라미터 값을 찾아가는 것, 그리고 무엇보다 애플리케이션 측면에서 튜닝 및 수정 작업을 진행함으로써 성능문제를 사전에 예방하는 것이 최선이다.
[4] 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 이벤트별로 좀더 친절한 설명과 사례를 제시하지 못한
점이 아쉬움으로 남지만, 누구든 아주 작은 것 하나라도 이 글을 통해 새로이 얻을 수 있었다면 그 이상 바랄 것은 없다.
출처: http://www.oracle.com/technology/global/kr/pub/columns/dbtuning.html
웹 페이지의 '중요성'은 본질적으로 주관적인 문제여서 읽는 사람의 관심사나 지식 그리고 태도 등에 의존한다. 하지만 웹 페이지의 상대적 중요성에 관해서는 객관적으로 얘기할 수 있는 부분이 많다. 이 논문은 객관적이고 기계적으로 웹 페이지를 랭킹해서 읽는 사람의 관심이나 기울이는 주의를 효과적으로 측정할 수 있는 수단인 "PageRank"를 소개한다. 우리는 페이지랭크(PageRank)를 이상적인 랜덤 웹 써퍼(random web surfer)에 비교해 볼 것이며, 어떻게 많은 웹 페이지를 대상으로 PageRank를 능률적으로 계산할 수 있는지를 설명할 것이다. 그리고 어떤 방식으로 PageRank를 검색이나 사용자 네비게이션에 응용할 수 있는지도 보여 주고자 한다.
1. 도입과 동기(Introduction and Motivation)
월드와이드웹(World Wide Web)은 정보검색(Information Retrieval)에 새로운 과제를 안겨 주었다. 웹은 매우 거대하며 이질적(heterogenous)이다. 현재 추산으로도 약 1억5천만 페이지 이상이 웹에 존재하며, 이 숫자는 매년 적어도 두 배씩 커지고 있다. 더욱 중요한 점은, 웹 페이지들이 극단적으로 다양하다는 것이다. 예를 들면 "Joe가 오늘 점심 때 뭘 먹었지?"와 같은 질문이 있는가하면 정보검색에 관한 전문적 논문집이 있기도 하다. 이런 주된 도전 과제들 외에도 웹에는 익숙하지 못한 초보 사용자들과 검색 엔진의 랭킹 기능(ranking function)을 교묘히 이용하려는 많은 웹 페이지들로부터 비롯되는 문제점이 있다.
그러나 웹은 다른 "평면적"인 문서 컬렉션(flat document collections)과 달리 하이퍼텍스트가 있다. 하이퍼텍스트(hypertext)는 웹 페이지 자체의 텍스트 외에 링크 구조(link structure)나 링크 텍스트(link text)같은 상당한 수준의 부가적인 정보를 제공한다.
이 논문에서 우리는, 모든 웹 페이지를 보편적 "중요도" 순으로 순위를 매기기 위해 웹의 링크 구조를 사용하였다. 이 랭킹은 페이지랭크(PageRank)라 하며, 검색엔진 사용자나 웹 사용자가 거대한 이질적 세계인 월드와이드웹을 빠르게 이해할 수 있게 도와준다.
1.1 웹 페이지의 다양성(Diversity of Web Pages)
학술적 인용(academic citation)을 분석한 문헌은 이미 많이 존재하지만 웹 페이지와 학술 출판물 사이에는 많은 중요한 차이가 있다. 학술 논문은 철두철미하게 리뷰되지만 웹 페이지는 '품질 관리'나 '출판 비용' 없이 늘어난다. 단순한 프로그램 하나만으로도 아주 많은 페이지를 손쉽게 만들어 낼 수 있으며 인위적으로 인용 횟수를 쉽게 부풀릴 수 있다. 웹 환경 속에는 그 안에서 이익을 찾는 많은 벤쳐들이 있기 때문에 이들이 사용자의 주의를 끌어오는 전략 역시 검색엔진 알고리듬의 발달에 맞춰서 함께 진화되어 왔다. 이러한 이유로 복제가능한 특징을 세는 방식으로 웹 페이지를 평가하려는 전략은 손쉽게 악용될 수 있다. 게다가, 학술 논문의 경우는 그 갯수를 정확히 셀 수 있을 뿐만 아니라 사실상 질적인 면 및 인용 횟수 등에서 유사하고 그 목적 역시 비슷하다.(대개 '지식의 몸체'를 키우기 위한 목적으로 만들어진다.) 하지만 웹 페이지는 질적인 면에서나 사용적인 측면, 인용, 길이 등에 있어서 학술 논문보다 훨씬 더 다양하다. IBM 컴퓨터에 관한 애매한 질문들을 모아놓은 것은 IBM 홈페이지와 매우 다르다. 운전자에게 휴대폰이 미치는 영향에 관해서 연구한 논문은 특정 휴대폰 회사의 광고와 매우 다르다. 사용자가 읽은 웹 페이지의 평균적인 질은 평균적인 웹 페이지의 질보다 높다. 그것은 웹 페이지를 만들고 퍼블리슁하는 것이 매우 쉽기 때문에 웹에는 사용자들이 읽지 않으려 하는 많은 저품질의 웹 페이지가 있기 때문이다.
웹 페이지에는 여러 가지 분화될 수 있는 요소가 많이 있다. 이 논문에서 우리는 그 중의 하나 - 웹 페이지의 상대적 중요성을 어떻게 추산할 것인가 - 라는 문제를 주로 다룬다.
1.2 페이지랭크(PageRank)
웹 페이지의 상대적 중요성을 측정하기 위해 우리는 웹 그래프를 기반으로 웹 페이지를 랭킹(ranking)하는 방식인 페이지랭크를 제안한다. 페이지랭크는 검색이나 브라우징, 트래픽 추산에 적용될 수 있다. 섹션 2에서는 페이지랭크의 수학적 기술을 다룰 것이며 직관적인 정당화(intuitive justification)를 얘기할 것이다. 섹션 3에서는 5억1800만 개에 이르는 하이퍼링크에 대해 어떻게 효율적으로 페이지랭크를 계산할 수 있는지 보여주고자 한다. 페이지랭크의 유용성을 테스트하기 위해 우리는 구글(Google)이라는 웹 검색 엔진을 구축했다. 이것은 섹션 5에서 다룬다. 섹션 7.3에서는 페이지랭크가 어떻게 브라우징을 도울 수 있는지 보여주고자 한다.
2. 웹 상의 모든 페이지의 순위 매기기(A ranking for every page on the Web)
2.1 관련 자료
학술 인용 분석에 관해서는 매우 많은 연구가 이미 존재한다. Goofman은 과학 커뮤니티에서 일종의 유행병처럼 정보 흐름이 퍼져 나간다는 것을 주장한 흥미로운 이론을 발표하기도 했다.
웹 같은 대형 하이퍼텍스트 시스템에서 어떤 방식으로 링크 구조(link structure)를 이용할 수 있는지에 관해서도 최근들어 상당한 연구가 이뤄지고 있다. Pitkow는 얼마 전에 다양한 방식의 링크 기반 분석을 설명한 "월드와이드웹 생태계의 특징"이라는 제목의 박사 학위 논문을 완성했다. Weiss는 링크 구조를 감안한 클러스터링 방식에 대해서 논했다. Spertus는 여러 가지 적용사례에 있어서 링크 구조로 부터 얻어낼 수 있는 정보에 관해 발표했다. 좋은 시각화를 위해서는 하이퍼텍스트에 부가적인 구조가 필요하다는 연구도 있었다. Kleinberg는 웹을 서로 인용하는 행렬로 보고 그 고유벡터(eigenvector)를 계산하는 방식을 기반으로 "헙 & 오쏘리티(Hubs and Authorities) 모델"이라는 흥미로운 모델을 개발했다. 마지막으로, 도서관 커뮤니티 쪽에서는 웹의 "질"이라는 것에 대해 관심을 갖고 있기도 하다.
일반적인 인용 분석 테크닉을 웹의 하이퍼텍스트적 인용 구조에 적용하고자 시도하는 것은 너무도 자연스러운 것이다. 단순하게 웹 페이지에 있는 각각의 링크를 학술적 인용처럼 생각해 볼 수 있는 것이다. 야후! 같은 메이져 페이지는 야후!를 가리키는 수 만, 수십 만 개의 백 링크(backlinks) 또는 인용을 갖고 있는 것이다.
야후! 홈페이지가 아주 많은 백 링크를 갖고 있다는 사실은 야후! 홈페이지가 매우 중요하다는 사실을 함축한다. 사실 많은 웹 검색엔진들은 어떤 페이지가 얼마나 중요한가 내지는 높은 질을 갖고 있는가를 판단할 때 백 링크 숫자를 바탕으로 가중치를 주고 있다. 하지만 단순히 백 링크의 갯수를 세는 것은 많은 문제점을 갖는다. 이 문제점들은 학술 인용 데이타베이스에는 존재하지 않는 웹만의 특징과 관련이 있는 것들이다.
2.2 웹의 링크 구조(Link Structure of the Web)
약간의 편차는 있지만 현재 크롤링 가능한 웹 그래프는 약 1억5천만개의 노드(node;페이지)와 17억 개의 엣지(edge;링크)가 있다고 알려져 있다. 각각의 웹 페이지는 그 페이지로부터 밖으로 나가는 포워드 링크(forward link; outedges)와 그 페이지를 가리키는 백 링크(backlink; inedges)를 갖는다. 어떤 페이지의 모든 백 링크를 다 찾아낸다는 것은 불가능하지만 페이지를 다운로드하고 나면, 포워드 링크가 무엇인지는 알 수 있다.

웹 페이지가 백 링크를 몇 개나 갖느냐는 매우 다양하다. 우리가 갖고 있는 데이타베이스에 따르면 넷스케잎 홈페이지는 62804개의 백 링크를 갖는데 반해 대개의 페이지들은 단지 몇 개의 백 링크만을 갖는다. 일반적으로 링크가 많이 된 페이지일수록 그렇지 못한 페이지보다 더 "중요하다". 학술 인용에서도 인용의 횟수를 세는 단순한 방법이 미래의 노벨상 수상자를 예측하는 데 사용될 수 있다. 페이지랭크는 인용 횟수를 세는 방식 이상의 훨씬 더 정교화된 방법을 제시한다.
페이지랭크가 흥미로운 이유는 인용 횟수가 일반적인 의미의 중요성과 일치하지 않는 경우가 매우 많기 때문이다. 어떤 웹 페이지가 야후!에 링크가 되어 있다면 그 페이지는 오직 하나의 백 링크밖에 없지만 그 링크는 매우 중요한 링크다. 야후!에 링크된 그 페이지는 다른 별 볼 일 없는 여러 곳에서 링크된 페이지보다 더 높은 순위를 가져야 한다. 페이지랭크는 링크 구조만을 사용해서 어떻게 "중요성"을 정확히 추정해낼 수 있을까를 시도한 것이다.
2.3 링크를 통한 랭킹의 전파(Propagation of Ranking through Links)
위의 논의를 기초로, 우리는 페이지랭크에 대한 직관적 기술을 다음과 같이 할 수 있다: 어떤 페이지가 높은 랭크의 백 링크를 많이 가질수록 그 페이지의 랭크도 올라간다. 이것은 어떤 페이지가 많은 백 링크를 갖는 경우와 몇 개의 높은 랭크값 백 링크를 갖는 경우 모두를 포괄하는 것이다.
2.4 페이지랭크(PageRank)의 정의
어떤 웹 페이지를 u라고 하고 u 페이지가 가리키는 페이지들의 집합을 Fu, u 페이지를 가리키는 페이지의 집합을 Bu라 하자. Nu = |Fu|라 하고, 이것은 u 페이지로부터 나가는 링크의 갯수, 즉 Fu의 갯수다. 그리고 노멀라이제이션에 사용되는 팩터를 c라고 하자.(노멀라이제이션은 전체 웹 페이지의 랭크 총합을 일정하게 하기 위해서다.)
일단, 단순 랭킹 R을 정의하는 것에서 출발해 보자. 단순 랭킹 R은 페이지랭크(PageRank)를 약간 단순화시킨 버전이다.

위 식은 전 섹션에서 얘기한 직관을 공식화한 것이다. 어떤 페이지가 가리키는 페이지들의 랭크에 균일하게 기여하기 위해, 링크가 나가는 페이지의 랭크를 그 페이지의 포워드 링크 갯수로 나누고 있다는 점에 주의하자. 그리고 c는 1보다 작아야 하는데, c < 1인 이유는 포워드 링크가 없는 페이지도 많이 있기 때문에 그런 페이지들의 가중치는 시스템 속에서 사라질 수 있기 때문이다.(섹션 2.7을 참조하라.) 위 등식은 재귀적(recursive)인 식이지만 초기 랭크 집합을 주고 수렴할 때까지 연산을 함으로써 계산할 수 있다.

그림 2 단순화된 페이지랭크의 계산

그림 3 정상상태를 이루고 있는 페이지들
그림 2는 한 쌍의 페이지로부터 다른 한 쌍의 페이지로 랭크가 전파되어 나가는 것을 보여주고 있다. 그림 3에서는 일군의 페이지들 사이에서 일정한 정상상태(steady state)의 솔루션을 이루고 있는 것을 볼 수 있다.
이것을 다른 방식으로 표현해 보자. 각 행과 열이 웹 페이지에 대응하는 정방행렬을 A라 하자. 그리고, 페이지 u에서 v로 가는 연결(edge)이 있다면 Au,v = 1/Nu이라 하고, 엣지가 없다면 Au,v는 0이라 하자. R을 웹 페이지들의 벡터로 생각해 보면, R = cAR이 된다. 그러므로 R은 A의 아이겐벡터(eigenvector; 고유벡터)가 되고 아이겐밸류는 c이다. 사실, 우리가 원하는 것은 A의 지배적 고유벡터(dominant eigenvector)다. 이것은 A를 여러 비퇴화적 시작 벡터(nondegenerate start vector)에 반복적으로 적용함으로써 계산될 수 있을 것이다.

그림 4 랭크 싱크
위에서 살펴 본 단순화된 랭킹 함수는 약간의 문제가 있다. 두 페이지가 서로서로 가리키고 있으며 다른 페이지로는 연결되어 있지 않은 경우를 생각해 보자. 다른 웹 페이지가 그 두 페이지 중 하나를 가리키고 있는 경우, 반복연산(iteration)이 진행되면서 그 루프에서는 랭크가 계속 축적될 뿐 외부로 전혀 분산하지 못 한다.(왜냐하면 외부로 나가는 엣지가 전혀 없으므로.) 루프는 일종의 함정을 형성하게 된다. 우린 이것을 랭크 싱크(rank sink)라 부른다. 랭크 싱크로부터 초래되는 문제를 해결하기 위해, 우리는 랭크 소스를 도입한다.
정의 1:
랭크의 소스에 해당하는 웹 페이지의 벡터 중 하나를 E(u)라 하자. 그러면 일군의 웹 페이지들의 페이지랭크는 다음 식을 만족하며 ||R||1 = 1(||R||1은 R'의 L1 norm)이고 c가 최대값을 가질 때의 R'이다.역자주

E가 모두 다 양수이면 등식의 균형을 위해서 c 값이 줄어들어야만 한다는 것에 주의하자. 그러므로 이 테크닉은 소멸계수(decay factor)에 해당한다. 위 식을 행렬 용어로 표현하면 R' = c(AR' + E)이고, ||R||1 = 1이므로 이 식은 R' = c(A + E x 1)R'로 다시 쓸 수 있다. 여기서 1은 1로만 구성된 벡터다. 그러므로, R'은 (A + Ex1)의 아이겐벡터라고 할 수 있다.
2.5 랜덤 써퍼 모델(Random Surfer Model)
위와 같은 페이지랭크의 정의는 그래프 상의 랜덤 워크(random walks)라는 또 하나의 직관적 기반을 갖고 있는 것으로 볼 수 있다. 단순화된 버전의 페이지랭크는 웹 그래프 상에서 랜덤 워크의 확률분포에 해당한다. 직관적으로 생각해 보면 이것은 "랜덤 써퍼"의 행동을 모델링한 것으로 볼 수 있다. "랜덤 써퍼"는 무작위로 일련의 링크들을 클릭해 나간다. 하지만 실제 웹 써퍼가 작은 루프에 빠져 들었을 때도 계속해서 그 루프 내를 맴돌 가능성은 거의 없다. 대신, 그 써퍼는 다른 페이지로 점프하려 할 것이다. 부가적 팩터인 E는 바로 그 행동을 모델링한 것으로 볼 수 있다. 즉, 써퍼는 주기적으로 "지루해지고" E의 분포에 기반해서 선택된 무작위 페이지로 점프하는 것이다.
지금까지 우리는 E를 사용자 정의 퍼래미터로 생각했다. 대부분의 테스트에서 우리는 E를 모든 웹 페이지에 걸쳐 동일하게 α값을 갖는 것으로 했다. 하지만, 섹션 6에서는, E 값이 달라짐에 따라 페이지의 랭크가 어떻게 "사용자화"될 수 있는지를 보여줄 것이다.
2.6 페이지랭크 계산(Computing PageRank)
페이지랭크를 계산하는 것은 규모의 문제를 무시한다면 아주 간단명료하다. S를 웹 페이지의 벡터(E 같은)라 하자. 그러면 페이지랭크는 다음과 같이 계산될 수 있을 것이다.
R0 <--- S
loop:
Ri+1 <--- ARi
d <--- ||Ri||1 - ||Ri+1||1
Ri+1 <--- Ri+1 +dE
δ<--- ||Ri+1 - Ri||1
while δ> ε
d 팩터가 수렴속도를 빠르게 하고 ||R||1을 유지하는 것에 주목하자. 또 다른 노멀라이제이션 방법은 적절한 팩터를 R에 곱하는 것이다. d의 사용은 E의 영향에 작은 효과만 미칠 것이다.
2.7 댕글링 링크(Dangling Links)
이 모델과 관계되는 이슈 중 하나는 댕글링 링크 문제다. 댕글링 링크란 외부로 나가는 링크가 없는 페이지를 가리키는 링크를 뜻한다. 댕글링 링크가 모델에 영향을 주는 이유는, 이것의 가중치가 어디로 분산되고 있는지가 불분명하고 또 아주 많은 댕글링 링크가 존재하기 때문이다. 종종 댕글링 링크가 가리키고 있는 페이지는 다운로드되지 않은 페이지일 수도 있다. 웹을 통째로 샘플링하는 것은 어렵기 때문이다.(현재 우리가 다운로드한 2400만 페이지에는 아직 URL이 가리키는 문서가 다운로드되지 않은 5100만 개의 URL이 있는 상태다. 즉, 5100만 개의 댕글링 링크가 있는 것이다.) 댕글링 링크는 다른 페이지의 순위에 직접적으로 영향을 주지는 않기 때문에, 우리는 모든 페이지랭크가 계산될 때까지 댕글링 링크를 그냥 제거했다. 페이지랭크 계산이 다 끝난 뒤에, 즉 큰 문제를 일으키지 않게 되었을 때, 댕글링 링크를 다시 첨가할 수 있다. 링크가 제거됨으로써 그 페이지에 있는 다른 링크의 노멀라이제이션이 영향받을 수는 있지만 크게 변화되는 건 아니다.
3 임플리멘테이션(Implementation)
스탠포드 웹 베이스 프로젝트(Stanford WebBase project)의 일환으로, 우리는 현재 총 2400만 페이지의 리파지터리(repository)를 갖고 있는 완전한 크롤링 & 인덱싱 시스템을 구축했다. 웹에 있는 모든 URL을 찾을 수 있게 하기 위해서는 모든 웹 크롤러가 URL 데이타베이스를 유지하고 있어야 한다. 웹 크롤러는 페이지랭크의 임플리멘테이션을 위해, 크롤링하면서 만나는 링크의 인덱스만 구축하면 된다. 작업 자체는 단순한 것이지만 볼륨이 거대하기 때문에 쉽지 않다. 예를 들어, 현재 우리가 구축한 2400만 페이지의 데이타베이스를 5일 안에 인덱싱하기 위해서는, 초당 50 페이지를 프로세싱해야 한다. 보통의 페이지 하나에 통상 11개의 링크가 있으므로(무엇을 링크로 셀 것인가에 따라 달라질 수는 있다.) 초당 550개의 링크를 프로세싱해야 하는 것이다. 또한, 우리가 구축한 2400만 페이지의 데이타베이스는 7500만 개의 각기 다른 URL을 참조하고 있으며, 각각의 링크는 반드시 서로 비교되어야만 하는 것이다.
웹에 깊숙히 그리고 미묘하게 존재하는 결함들에 유연하게 대응할 수 있는 시스템을 구축하기 위해서 많은 시간이 걸렸다. 웹에는 한없이 커다란 싸이트, 페이지, 심지어 끝없이 계속되는 긴 URL들이 있다. 상당수의 웹 페이지가 잘못된 HTML을 담고 있기 때문에 파서(parser)를 디자인하는 것이 까다로왔다. 예를 들어, 우리는 URL에 /cgi-bin/이 들어 있는 경우 크롤링하지 않았다. 물론, 웹은 계속해서 변하고 있기 때문에 "전체 웹"을 정확하게 샘플링한다는 것은 불가능하다. 싸이트가 다운되는 경우도 있고, 어떤 경우는 자신의 싸이트를 인덱싱되지 않도록 해 놓기도 한다. 이런 모든 점에도 불구하고, 우리는 공개적으로 접근가능한 웹의 실제 링크 구조를 상당한 수준으로 표현했다고 생각하고 있다.
3.1 페이지랭크 임플리멘테이션
우리는 각각의 URL을 각기 유니크한 정수로 변환하고, 하이퍼링크의 페이지를 구분할 수 있도록 모든 하이퍼링크를 페이지의 정수 ID를 이용해서 데이타베이스에 저장했다. 임플리멘테이션에 관한 자세한 사항은 구글 검색엔진의 해부학 논문에서 밝혔다. 보통, 페이지랭크는 다음과 같은 식으로 임플리멘테이션했다.
먼저, 부모 ID(Parent ID)를 이용해서 링크 구조를 정렬한다. 그 다음, 앞에서 말한 것과 같은 이유로 링크 데이타베이스에서 댕글링 링크를 제거한다. (몇 번의 반복 작업만으로도 대부분의 댕글링 링크를 제거할 수 있다.) 그 다음, 랭크 값을 초기화한다. 초기화 값을 어떻게 할 것인가는 어떤 전략을 갖고 있느냐에 따라 달라진다. 수렴할 때까지 반복작업을 계속할 생각이라면, 일반적으로 초기값은 최종값에 영향을 미치지 않는다. 단지 수렴 속도만 빠르게 할 뿐이다. 하지만 초기값을 잘 선택하면 수렴과정의 속도를 높일 수 있다. 우리는 초기 할당값을 신중하게 선택하면 제한적 횟수의 많지 않은 반복 작업만으로도 아주 좋은 결과를 얻거나 더 나은 퍼포먼스를 만들어 낼 수 있다고 믿고 있다.
각 페이지의 가중치에 메모리를 할당한다. 우리는 단정도 부동소수점(single precision floating point) 값을 사용했고, 각각은 4바이트씩 할당했으므로 7500만 개의 URL은 곧 300메가바이트의 크기가 된다. 만약 모든 가중치를 담고 있을 만큼 램이 충분치 않으면 여러 번의 패쓰(pass)를 사용해도 된다.(우리가 임플리멘테이션한 것은 메모리의 절반과 2개의 패쓰를 사용했다.) 현재 진행 중인 단계의 가중치는 메모리에 저장되고, 전단계의 가중치는 디스크를 통해 리니어(linear)하게 억세스한다. 또한, 링크 데이타베이스 - 즉 알고리듬 정의에서의 A - 로의 모든 억세스도 정렬되어 있기 때문에 리니어하다. 그러므로 A 역시 디스크에 저장될 수 있다. 이러한 데이타 구조들이 매우 큰 크기임에도 불구하고, 리니어 디스크 억세스를 통한 각 반복작업에 걸리는 시간은 보통의 웍스테이션상에서 약 6분 정도면 된다. 가중치들이 수렴하고 나면, 다시 댕글링 링크를 추가하고, 랭킹을 재연산한다. 댕글링 링크를 되돌려 넣은 뒤에 필요한 반복 작업 횟수는 댕글링 링크를 제거하는 데 요구되었던 횟수와 똑같다는 점에 주의하라. 그렇지 않으면, 댕글링 링크의 일부는 가중치가 0이 될 것이다.
이상의 모든 과정은 현재의 임플리멘테이션의 경우 총 5시간이 소요된다. 수렴 조건을 덜 엄격하게 하고, 더 최적화를 한다면 계산속도는 더 빨라질 수 있을 것이다. 또는, 아이겐벡터를 추산하는 보다 더 효율적인 테크닉이 사용되어도 퍼포먼스가 더 좋아질 것이다. 어쨌든, 페이지랭크를 계산하는 데 필요한 코스트는 풀 텍스트 인덱스를 구축하는 데 필요한 것에 비하자면 아주 사소한 것이라 할 수 있다.
4 수렴 특성(Convergence Properties)
그림 5에서 볼 수 있는 것처럼, 3억 2200만개라는 큰 링크 데이타베이스를 합리적으로 감내할 만한 수준으로 수렴시키는 데 필요한 반복작업은 약 52회다. 데이타의 크기가 반이라면 대략 45회면 된다. 이 그래프를 통해서, 극단적으로 큰 크기의 컬렉션에서도 페이지랭크가 아주 쉽게 확장될 수 있다는 것을 알 수 있다. 스케일링 팩터가 대략 logn과 거의 선형관계를 이룬다.

그림 5 페이지랭크 연산의 수렴
페이지랭크 연산이 아주 빠르게 수렴한다는 사실로부터 파생되는 한 가지 흥미로운 점은 웹이 익스팬더양 그래프(expander-like graph)라는 점이다. 이 부분의 이해를 돕기 위해서 그래프 상의 랜덤 워크 이론의 간단한 개론을 살펴 보자. 자세한 것은 Motwani-Raghavan이 쓴 페이퍼를 참조하라.
그래프 상의 랜덤 워크는 스토캐스틱(stochastic)한 과정이다. 즉, 임의의 한 타임스텝에 우리는 그래프 상의 특정 노드에 서 있고, 다음 타임스텝에 어떤 노드로 이동할 것인지는 균일하게 무작위적으로 분포하는 아웃엣지 중 하나를 선택해서 결정하는 것이다. 만약 모든 (너무 크지는 않은) 서브셋 노드 S가 이웃(neighborhood; S에 속한 노드들로부터 아웃엣지를 통해 접근가능한 꼭지점들의 집합)을 가지고 있고, 그 이웃 노드의 크기가 |S|보다 α배 이상 크다면 그 그래프는 하나의 익스팬더(expander)라고 할 수 있다. 그리고 만약, 특히 가장 큰 아이겐벨류가 두 번째로 큰 아이겐벨류보다 충분히 더 큰 경우, 그 그래프는 좋은 익스팬젼 팩터를 갖고 있다고 볼 수 있다. 랜덤 워크가 빠른 속도로 그래프 상의 노드들의 제한된 분포로 수렴해 가면 그 그래프는 래피들리-믹싱(rapidly-mixing)하다. 또한 그래프가 익스팬더이고 아이겐벨류 분리(eigenvalue separation)를 갖고 있다면 그 랜덤 워크는 래피들리-믹싱인 경우라 할 수 있다.
이상의 내용을 페이지랭크 연산과 관련 지어 보자. 페이지랭크란 본질적으로, 웹 그래프 상의 랜덤 워크의 제한된 분포로 결정짓는 것이다. 어떤 노드의 중요도 랭킹이란, 본질적으로 충분히 시간이 흐른 뒤에 랜덤 워크가 그 노드에 있을 확률인 것이다. 페이지랭크 연산이 로그 시간 내에 종결될 수 있다는 사실은 랜덤 워크가 래피들리 믹싱이거나 그래프가 좋은 익스팬젼 팩터를 갖고 있다는 말이 된다. 익스팬더 그래프는 여러 가지 바람직한 특성을 많이 갖고 있기 때문에 웹 그래프와 관계된 연산을 함에 있어서 앞으로 다양하게 활용할 수 있을 것이다.
5 페이지랭크를 이용한 검색
페이지랭크의 주된 적용처는 검색이다. 우리는 페이지랭크를 활용한 두 가지 검색엔진을 임플리멘테이션했다. 하나는 단순한 타이틀 기반의 검색엔진이고 다른 하나는 풀 텍스트 검색엔진이다. 후자의 이름은 구글이다. 구글은 표준적인 IR 측정치, 근접성(proximity), 앵커 텍스트(웹 페이지를 가리키는 링크의 텍스트), 그리고 페이지랭크 등의 많은 요소를 바탕으로 검색 결과를 랭킹한다. 페이지랭크가 어떤 이점을 갖는지에 관한 포괄적인 유져 스터디는 이 논문의 범위를 벗어나지만, 몇 가지 비교 실험과 검색 결과 샘플을 이 논문을 통해 얘기해 보려 한다.
페이지랭크의 이점이 가장 크게 활용될 수 있는 부분은 덜 특화된 질의어(underspecified queries)를 처리하는 것이다. 예를 들어, "스탠포드 대학"이라는 질의어를 넣으면, 일반적인 검색엔진은 스탠포드가 들어 간 많은 페이지들을 결과로 보여 줄 뿐이다. 하지만 페이지랭크를 활용하면 스탠포드 대학 홈 페이지가 순위의 가장 위로 올라 오는 것이다.
5.1 타이틀 검색
페이지랭크가 검색에 활용되면 얼마나 유용한지를 시험해 보기 위해 우리는 1600만 페이지의 제목만을 사용하는 검색엔진을 만들어 보았다. 그 검색엔진은 질의어를 넣으면 문서 제목에 질의어가 들어 있는 모든 웹 페이지를 찾은 다음, 그 결과를 페이지랭크를 이용해서 정렬한다. 이 검색엔진은 아주 단순한 것이고 간단하게 구축할 수 있는데, 비공식적인 시험을 해 본 결과 놀랄 만큼 훌륭한 성능을 보여 주었다. 그림 6에서 볼 수 있는 것처럼 "University"라는 검색어에 대해 페이지랭크를 이용한 제목 검색엔진은 대표적인 대학들의 목록을 보여 준 것이다. 이 그림은 우리가 만든 MultiQuery 시스템으로, 두 개의 검색엔진에 동시에 질의를 할 수 있는 시스템이다. 그림의 왼 편에 있는 것이 페이지랭크를 기반으로 한 타이틀 검색엔진이다. 검색 결과에 있는 바 그래프와 퍼센티지는 탑 페이지를 100%로 잡고 페이지의 실제 페이지랭크 값에 로그를 취한 다음 노멀라이징한 값이다. 이 논문의 다른 곳에서는 계속 퍼센타일(percentile)을 사용했지만 여기서는 아닌 것이다. 그림의 오른 쪽에 있는 것은 알타비스타 검색엔진이다. 알타비스타의 검색 결과를 보면 "University"라는 질의어에 맷칭되는 무작위적으로 보이는 웹 페이지들 그리고 여러 써버의 루트 페이지가 보인다. (알타비스타는 퀄리티 휴리스틱으로 URL의 길이를 사용하는 것 같다.)

그림 6 "University" 검색어에 대한 결과 비교
5.2 랭크 머징(Rank Merging)
타이틀에 기반한 페이지랭크 시스템이 아주 훌륭한 결과를 보여 주는 이유는, 제목을 맷칭하는 것이 페이지의 높은 프리시젼을 보장하고, 페이지랭크가 페이지의 높은 품질을 보장하기 때문이다. 웹 검색에서 "University" 같은 질의를 하는 경우, 사용자가 살펴 볼 페이지보다 훨씬 많은 페이지들이 존재하기 때문에 리콜은 그다지 중요하지 않다. 리콜이 중요하게 요구되는 특화된 검색의 경우는 풀 텍스트에 대한 전통적인 정보검색 점수와 페이지랭크를 함께 적용할 수 있을 것이다. 구글 시스템은 그런 형태의 랭크 머징을 사용한다. 랭크 머징은 아주 까다로운 문제로 알려져 있어서 우리는 그런 형태의 질의어를 합리적으로 평가할 수 있게 구축하기 위해 상당한 노력을 부가적으로 기울여야만 했다. 하지만, 그런 형태의 질의어에 있어서도 페이지랭크가 상당히 도움이 된다고 생각된다.
5.3 몇 가지 샘플 결과
우리는 페이지랭크를 이용한 풀 텍스트 검색엔진인 구글을 이용해서 상당히 많은 테스트를 해 보았다. 완전한 형태의 유져 스터디는 이 논문의 범위를 벗어나지만, 몇 개의 샘플을 이 논문의 부록 A에 수록했다. 더 많은 질의어에 대한 결과를 원하는 분은 직접 구글을 테스트 해보면 된다.
표 1은 페이지랭크에 기반한 탑 15위 페이지 목록이다. 이 리스트는 1996년 7월 시점의 결과다. 최근 다시 페이지랭크를 계산해 보았을 때는 마이크로소프트가 넷스케잎보다 조금 더 큰 페이지랭크를 보여 주었다.

표 1
5.4 커먼 케이스(Common Case)
페이지랭크의 디자인 목표 중 하나가 질의어의 커먼 케이스를 잘 처리하게 한다는 것이었다. 예를 들어, 미시건 대학의 학생 관리자 기능 시스템의 이름에 "wolverine"이라는 게 들어 있었던 것으로 기억하고 있는 사람이 "wolverine"이라는 검색을 한다고 하자. 우리의 페이지랭크 기반 타이틀 검색엔진은 "Wolverine Access"를 검색 결과의 첫 번째로 보여 준다. 이것은 대단히 합리적이다. 왜냐하면 모든 학생들은 Wolverine Access 시스템을 사용하고 있고, "wolverine"이라는 질의를 한 사람이라면 Wolverine Access 페이지를 살펴 보려 할 가능성이 매우 크기 때문이다. 그런데 Wolverine Access 싸이트가 좋은 커먼 케이스라는 사실은 그 페이지의 HTML에는 전혀 담겨 있지 않다. 심지어 이런 형태의 메타 정보를 페이지 내에 담을 수 있는 방법이 있다고 하더라도, 이런 류의 평가에 있어서는 페이지를 만든 사람을 신뢰하기 힘들다는 게 문제가 된다. 웹 페이지를 만드는 많은 사람들이 자신이 만든 페이지가 웹에서 가장 훌륭하고 가장 자주 읽힌다고 주장할 것이기 때문이다.
wolverine이라는 것에 관해서 가장 많은 정보를 담고 있는 페이지를 찾는 것과 wolverine 싸이트의 커먼 케이스를 찾는 것은 전혀 다른 일이라는 사실이 중요하다. 웹의 링크구조를 통해 텍스트의 매칭 점수를 전파해 나감으로써 어떤 주제를 자세하게 다룬 싸이트를 찾아내는 흥미로운 시스템이 있다.(Massimo Marchiori. The quest for correct information on the web: Hyper search engines.) 그 시스템은 그런 과정을 통해 가장 중심적인 경로에 포함되는 페이지들을 결과로 보여주는 것이다. 이런 방식은 "flower" 같은 질의어의 경우 좋은 결과를 보여 준다. 즉, 그 시스템은 '꽃'이라는 주제를 자세히 다루고 있는 싸이트에 도달할 수 있는 경로 중 가장 좋은 것을 보여주는 것이다. 이것과 커먼 케이스를 찾는 접근법을 비교해 보자. 커먼 케이스 접근법은 꽃에 관한 정보대신 꽃을 구입하는 방법만이 담긴, 사람들이 가장 많이 찾는 꽃 판매 싸이트를 보여줄 지도 모른다. 두 가지 방식 모두 중요하다는 게 우리의 생각이고, 일반적 목적의 웹 검색엔진이라면 마땅히 위의 두 가지 태스크 모두에 있어서 만족스러운 결과를 보여줘야 한다고 생각한다. 이 논문에서는, 우리는 커먼 케이스적 접근법에만 집중하고 있다.
5.5 커먼 케이스의 하부 구성요소(Subcomponents of Common Case)
페이지랭크가 도움이 될 수 있는 커먼 케이스 시나리오가 어떤 성격을 갖는가를 생각해 보는 것은 무척 유익하다. Wolverine Access 싸이트처럼 가장 자주 사용되는 페이지 외에도, 페이지랭크는 협동적 오쏘리티 또는 신뢰할 수 있는 싸이트 역시 표현해 준다. 예를 들어, 사용자는 어떤 뉴스가 단지 뉴욕 타임즈 홈 페이지로부터 직접 링크되어 있다는 이유만으로 더 선호할 수 있다. 물론, 그 뉴스 페이지는 뉴욕 타임즈처럼 중요도가 높은 페이지로부터 링크되어 있다는 사실만으로 매우 높은 페이지랭크 값을 갖게 된다. 이것은 일종의 협동적 신뢰(collaborative trust)의 특성을 잡아내고 있는 것처럼 보인다. 왜냐하면, 신뢰도가 높고 권위 있는 소스로부터 언급된 페이지일수록 그 페이지의 신뢰도와 권위가 올라가기 때문이다. 유사하게, 페이지의 품질이나 중요도 역시 이런 류의 순환적 정의에 잘 부합되는 것 같다.
6 개인화된 페이지랭크(Personalized PageRank)
페이지랭크 연산의 중요한 요소 중 하나가 E이다. E는, 랭크 싱크처럼 아웃엣지가 없는 싸이클을 보충하기 위한 랭크 소스 웹 페이지의 벡터다. 한편, E는 랭크 싱크 문제에 대한 해결책으로써뿐만 아니라, 페이지랭크 값을 조정할 수 있는 강력한 퍼래미터이기도 하다. 직관적으로 보자면, E 벡터는 랜덤 써퍼가 주기적으로 점프해 가는 웹 페이지의 분포에 해당한다. 밑에서 살펴 보겠지만, 이것은 웹을 거시적으로 관찰하거나 특정 부분에 대해 집중적이고 개인화된 관찰을 하는 데 사용될 수 있다.
우리가 수행한 실험은 대부분 E 벡터를 모든 웹 페이지에 걸쳐 균일하게 ||E||1 = 0.15로 가정했다. 즉, 랜덤 써퍼가 주기적으로 또 다른 랜덤 웹 페이지로 점프하는 것을 가정한 것이다. 이것은 모든 웹 페이지가 단지 존재하고 있다는 이유만으로 똑같이 가치를 부여받는 것이므로 E를 무척 민주적으로 선택한 것이다. 이런 테크닉이 상당히 성공적이었기는 했지만 중요한 문제점도 갖고 있다. 관련 링크가 많은 어떤 웹 페이지들이 지나치게 높은 랭킹을 받을 수 있는 문제다. 예컨데, 저작권 관련 페이지나 상호간에 링크가 많이 된 메일링 리스트 모음 등이 여기에 해당한다.
또 하나의 극단적 형태로, E를 오직 하나의 웹 페이지로만 구성할 수 있다. 우리는 두 가지로 실험해 보았다. 하나는 넷스케잎 홈 페이지로 해 보았고 다른 하나는 유명한 컴퓨터 과학자인 존 맥카씨(John McCarthy)의 홈 페이지로 했다. 넷스케잎의 홈 페이지로 한 실험은, 넷스케잎을 기본 홈 페이지로 하고 있는 초보 사용자의 시각에서 페이지의 랭크를 만들어 내려는 시도를 한 것이다. 존 맥카씨의 홈 페이지를 이용한 실험은, 그의 홈 페이지에 있는 링크를 바탕으로 우리에게 상당한 문맥적 정보를 제공한 개인의 시각에서 페이지랭크를 계산한 것이다.
두 경우 모두, 위에서 말한 메일링 리스트 문제가 나타나지 않았다. 그리고 두 경우 모두에서, 각각의 홈 페이지가 가장 높은 페이지랭크 값을 나타냈으며 그 페이지로부터 직접 연결된 페이지들이 그 뒤를 이엇다. 그 다음 시점부터는 불균형은 줄어 들었다. 표 2는 각각의 경우에서 여러 페이지들의 페이지랭크 퍼센타일을 나타낸 것이다. 컴퓨터 사이언스에 관계된 페이지일수록 넷스케잎 쪽 랭크보다 매카씨 랭 크쪽이 더 높은 값을 갖고, 특히 스탠포드 대학의 컴퓨터 사이언스 학과와 관계되는 페이지는 더욱 높은 매카씨 랭크 값을 갖는 것을 볼 수 있다. 예를 들어 스탠포드 컴퓨터 사이언스 학과의 또 다른 교수의 웹 페이지는 매카씨 쪽 랭크가 넷스케잎의 경우보다 6 퍼센타일 더 높다. 그리고 페이지랭크 값을 퍼센타일로 표시한 것에 주의하자. 이렇게 한 것은 상위 순위에서 나타나는 페이지랭크 값의 큰 차이를 줄여서 표현하기 위해서다.

표 2
위와 같은 개인화된 페이지랭크는 개인화된 검색엔진처럼 다양하게 응용할 수 있을 것이다. 개인화된 검색엔진은, 단순히 북마크나 홈 페이지를 입력하는 것만으로 사용자의 취향을 상당 부분 효과적으로 추측해내서 사용자의 수고를 대폭 덜어줄 수 있을 것이다. "Mitchell"이라는 질의어로 시행한 예를 부록 A에 수록했다. 그 예를 보면, 웹 상에 "Mitchell"이라는 이름을 가진 사람이 아주 많음에도, 존 매카씨 교수의 동료인 존 밋첼 교수의 홈 페이지가 결과의 1위로 나타난 것을 볼 수 있다.
6.1 상업적 이익을 위한 조작
이런 형태의 개인화된 페이지랭크는 상업적 이익을 위해 조작하는 것을 사실상 완전히 차단할 수 있다. 높은 페이지랭크 값을 갖기 위해서는 중요한 페이지로부터 언급되거나 중요하지 않은 많은 페이지로부터 링크되어야만 한다. 최악의 경우, 중요한 싸이트에서 광고(링크)를 구입하는 형태의 조작이 있을 수 있겠지만 그건 비용이 소요되므로 충분히 조절 가능하다. 조작에 대한 이런 저항성은 정말로 중요한 특성 중 하나다. 왜냐하면 상업적 조작 때문에 많은 검색엔진이 골머리를 앓고 있으며 훌륭한 기능을 임플리멘테이션하는 것이 조작 때문에 매우 어려워지기 때문이다. 예컨데, 문서가 자주 업데잇되는 것은 매우 바람직한 특징임에도 검색 결과를 조작하고자 하는 사람에 의해 이런 특징이 남용되고 있는 것이다.
균일한 E로 할 것인지 아니면 단일 페이지 E로 할 것인지의 절충안으로 E를 모든 웹 써버의 루트 수준 페이지로 구성할 수도 있다. `이 경우, 어느 정도의 페이지랭크 조작이 가능하다는 것에 주의해야 한다. 많은 루트 레벨 써버를 확보해서 특정 싸이트로 링크하면 간단히 조작되기 때문이다.
7 적용(Applicaitons)
7.1 웹 트래픽의 추산
페이지랭크는 대략 랜덤 웹 써퍼에 해당하기 때문에(섹션 2.5 참조), 페이지랭크가 실제 사용도와 어떻게 대응되는지 알아 보는 것은 아주 흥미로운 일이다. 우리는 NLANR(NLANR) 의 프록시 캐쉬로부터 얻은 웹 페이지의 접근 횟수를 페이지랭크와 비교해 보았다. NLANR 데이타는 미 전역에 있는 프록시 캐쉬에 있는 여러 달에 걸쳐진 기록으로, 11,817,665개의 각기 다른 URL에 관한 자료이다. 그 데이타에 따르면 가장 히트가 높은 것은 알타비스타로 638,657 히트다. 그 데이타베이스는 우리가 갖고 있는 7500만 개 URL 데이타베이스와 260만 페이지가 중복된다. 이들 데이타셋을 분석, 비교하는 것은 몇 가지 이유에서 대단히 까다로운데, 우선 캐쉬 억세스 데이타에 있는 많은 URL들이 무료 이메일 서비스에 있는 개인 메일을 읽기 위해 접근한 것들이라는 점이 있다. 그리고 중복된 써버 이름과 페이지 이름도 심각한 문제점이다. 불완전성과 편향됨은 페이지랭크 데이타와 사용도 데이타 모두에서 문제가 된다. 하지만, 몇 가지 흥미로운 트렌드를 볼 수 있었다. 캐쉬 데이타에서는 포르노그래프 싸이트들이 높은 사용도를 보였음에도 이들의 페이지랭크 값은 대부분 낮았다. 이것은, 사람들이 자신의 웹 페이지에 포르토그래피 싸이트로 링크하는 것을 원하지 않기 때문인 것으로 생각된다. 그러므로 페이지랭크와 사용도 데이타 사이의 차이점을 살펴 보는 것을 통해, 사람들이 보고는 싶어하는데 자신의 웹 페이지에서는 언급하고 싶어 하지 않는 게 어떤 것이 있는지를 알아낼 수 있을 것이다. 어떤 싸이트는 매우 높은 사용도를 갖는데 페이지랭크가 낮은 경우가 있다. netscape.yahoo.com 같은 게 그렇다. 이것은 아마도 우리 데이타베이스에 중요한 백 링크가 누락되어 있기 때문인 것 같다. 우리 데이타베이스는 웹 링크 구조이 일부분만을 담고 있기 때문이다. 사용도 데이타를 페이지랭크 계산의 시작 벡터로 사용하고 페이지랭크 연산을 몇 차례 반복하는 것도 가능할 것이다. 이렇게 하면 사용도 데이타가 채워주지 못 한 부분을 메꿀 수 있을 것이다. 어떤 식이 되었든, 이런 형태의 비교는 향후 연구를 위한 흥미로운 주제이다.
7.2 백 링크 예측자로써의 페이지랭크
페이지랭크는 백 링크의 예측자로써의 의미가 있다. "Efficient crawling through url ordering"(Junghoo Cho, Hector Garcia Molina, and Lawrence Page) 논문에서, 우리는 어떻게 웹을 효율적으로 크롤링할 수 있는지, 더 좋은 문서들을 먼저 크롤링할 수 있는지의 문제를 탐색했다. 우리는 스탠포드 웹에서 시행한 테스트를 통해서 페이지랭크가 단순히 인용 횟수를 세는 것보다 훨씬 더 좋은 미래 인용 횟수의 예측자라는 것을 알게 되었다.
실험은, 다른 정보 없이 단일 URL에서 출발해서 가급적 최적의 순서에 가깝게 페이지를 크롤링하는 것을 목표로 하고 있다. 최적의 순서란 평가함수에 따른 랭크 순서와 정확히 일치하는 순서로 페이지를 크롤링하는 것이다. 이 실험에서의 평가함수는 완전한 정보가 주어졌을 때의 인용 횟수 순서로 했다. 문제는, 평가함수를 계산할 정보를 완전히 알게 되는 것은 모든 문서를 크롤링한 다음이라는 점이다. 이렇게 불완전한 데이타를 사용해서 크롤링 순서를 정할 때, 단순히 이미 알고 있는 인용 횟수를 활용하는 것보다 페이지랭크를 이용하는 쪽이 더 효과적인 것으로 드러났다. 바꿔 얘기하자면, 심지어 측정 기준이 인용횟수를 세는 것일지라도, 페이지랭크가 인용회수를 직접 세는 것보다 더 좋은 예측자라는 얘기다! 이것의 이유는 페이지랭크의 경우 인용 횟수를 세는 것이 국소적으로만 최대화되는 것을 피할 수 있기 때문인 것 같다. 예를 들어, 인용 횟수를 직접 세는 경우에는 스탠포드 CS 웹 페이지들의 컬렉션 속에만 빠져 드는 경향이 있어서 그곳을 벗어나 다른 곳에 있는 인용 회수가 높은 페이지를 찾아 나서는 데 오랜 시간이 걸린다. 하지만 페이지랭크는 스탠포드 홈 페이지가 중요하다는 것을 금방 알게 되고, 그 하부 페이지들에게 우호적인 점수를 주기 때문에 더 효율적이고 광범위한 검색이 가능한 것이다.
이러한 인용 횟수의 예측자로써의 페이지랭크의 능력은 페이지랭크를 사용해야 하는 아주 설득력 있는 이유가 된다. 웹의 인용구조를 완전히 매핑하는 것은 아주 까다롭기 때문에, 인용 횟수를 직접 세는 것보다 페이지랭크가 훨씬 더 좋은 인용 횟수 근사치가 될 수 있는 것이다.
7.3 사용자 네비게이션: 페이지랭크 프락시(User Navigation: The PageRank Proxy)
우리는 사용자가 각 링크의 페이지랭크와 함께 부가적인 설명을 볼 수 있는 웹 프락시 애플리케이션을 개발했다. 그 애플리케이션은 아주 유용했는데, 사용자가 링크를 클릭하기 전에 관련 정보를 미리 알 수 있기 때문이다. 그림 7은 프록시 프로그램의 스크린샷이다.빨간 바의 길이는 URL의 페이지랭크 값에 로그를 취한 값이다. 그림을 보면 스탠포드 대학 같은 메이져 조직은 매우 높은 랭킹을 받게 되고, 뒤이어 리서치 그룹이, 그 다음으로 개인이 -개인의 경우 스케일의 최상단에는 교수가 위치한다 - 나타남을 알 수 있다. 또한 ACM이 스탠포드 대학보다는 높지 않지만 매우 높은 페이지랭크를 갖는 것도 볼 수 있다. 재미있는 것은, 아주 지명도가 높은 교수의 페이지가 심할 정도로 낮은 페이지랭크 값을 갖고 있는 것을 찾으면 URL이 잘못 되어 있는 것을 찾아낼 수 있다는 점이다. 결과적으로, 프락시 툴은 네비게이션 뿐만 아니라 페이지 제작에도 도움이 될 수 있는 것 같다. 이 프락시 애플리케이션은 다른 검색엔진의 결과를 살펴 보는 데에도 아주 유용하다. 그리고 야후의 리스팅 같이 링크가 매우 많은 페이지를 파악하는 데도 큰 도움이 된다. 프락시를 통해서 많은 링크 중 어떤 것이 더 흥미로운 것인지를 짐작할 수 있기 때문이다. 또는 자신이 찾고 있는 링크가 "중요도" 측면에서 어느 정도인지를 알 수도 있고, 훨씬 더 빠르게 페이지를 전체적으로 살펴볼 수 있다.

그림 7 페이지랭크 프락시
7.4 페이지랭크의 다른 용도
페이지랭크의 최초 목표는 백 링크를 정렬해서, 만약 어떤 문서가 많은 백 링크를 갖는다면 그 중 어떤 것이 "최상"의 것인지를 찾아서 그걸 가장 먼저 보여주려는 것이었다. 그리고 우리는 그런 시스템을 임플리멘테이션했다. 페이지랭크를 기준으로 정렬된 백 링크를 살펴 보는 것은 경쟁을 파악하는 측면에서도 아주 흥미롭다. 예를 들어, 뉴스 싸이트를 운영하는 사람이라면 경쟁자가 확보한 중요한 백 링크가 어떤 것인지 지속적으로 관찰하고자 할 것이다. 또한, 페이지랭크는 사용자가 어떤 싸이트가 신뢰할 수 있는 것인지 아닌지 판단하는 데 도움을 준다. 예를 들어, 스탠포드 홈 페이지에서 직접 인용된 정보라면 아무래도 사용자가 더 신뢰하려 할 것이다.
8 결론
이 논문에서, 우리는 웰드 와이드 웹 상의 모든 페이지를 페이지랭크라는 단일한 숫자로 압축하고자 하는 담대한 작업을 다루어 보았다. 페이지랭크는 페이지의 컨텐트와 상관없이, 오직 웹의 그래프 구조 상의 위치에만 의존하는 모든 웹 페이지의 글로벌 랭킹이다.
페이지랭크를 사용함으로써 우리는 더 중요하고 중심적인 웹 페이지들을 더욱 선호하는 식으로 검색 결과를 정렬할 수 있다. 여러 실험을 통해서, 페이지랭크는 고품질의 검색 결과를 보여줌을 알 수 있었다. 페이지랭크의 기반이 되는 직관은 웹 페이지 외부에 있는 정보, 즉 일종의 피어 리뷰인, 백 링크를 사용한다는 것이다. 게다가, "중요한" 페이지들로부터의 백 링크는 평균적인 페이지들로부터의 백 링크보다 더 중요하며, 이것은 페이지랭크의 재귀적인 정의(섹션 2.4 참조)를 통해 확실히 구현되어 있다.
페이지랭크는 대부분의 질의어에 대한 답변이 될 수 있는 소수의 자주 사용되는 문서들을 분리해 내는 데에도 사용될 수 있다. 풀 데이타베이스는 작은 데이타베이스가 질의어에 적절히 응답할 수 없을 때만 참조되면 된다. 마지막으로, 페이지랭크는 클러스터의 센터로 사용할 수 있는 대표 페이지를 찾아내는 좋은 방법이 될 수도 있을 것이다.
페이지랭크는 검색 외에도 트래픽 추산이나 사용자 네비게이션 같은 다른 많은 곳에 적용될 수 있다. 또한, 웹을 특정 시점에서 바라볼 수 있는 사용자화된 페이지랭크 역시 만들어 낼 수 있다.
종합하자면, 페이지랭크를 이용한 실험은 웹 그래프의 구조가 다양한 정보검색 작업에서 매우 유용하게 사용될 수 있음을 보여 준다고 할 수 있다.
문서출처: 이명헌 경영스쿨 http://www.emh.co.kr/xhtml/google_pagerank_citation_ranking.html
참고: http://www.emh.co.kr/xhtml/google_search_engine.html → 구글의 검색엔진 해부
읽으면서, 어디선가 본 글인데 해서 봤더니 김창준님이 쓴 글이었다.
알고리즘에 대한 내용을 보면서 이전에 봤을 때보다 더 공감가는 구절이 있었으니, 바로 반복이었다.
동일한 문제를 반복해서 풀어보는 것.. 이 반복의 중요성을 요즘들어 더 깨우치는 거 같다.
그 계기가 된 것들을 생각나는데로 적어보면..
* Topcoder에서 SnapDragon이 동일한 문제에 대해 10번이상 짜보면 정말 견고하고 우아한 소스가 나올 것이라고 한 얘기
* 동일한 내용에 대한 여러 책들을 읽다보면 이전에 깨우치지 못한 많은 것들을 느낄 수가 있을 것이다.(ex. C++문법책 등)
* Oracle 어드민, 대용량 데이터베이스 등 교육을 받다보니 반복되거나 겹치는 내용은 강사가 얘기하는 것 외에 좀더 깊은 부분을 생각할 수가 있었다.
그외에도 나에게 지나친 여러 매개체들이 반복이 중요하다는 것을 자꾸 일깨워주는 거 같다.
꾸준한 학습과 반복을 통한 일보전진은 그 자체로 엄청난 가치가 있는 것이리니..
-김창준
우리 프로그래머들은 항상 공부해야 합니다. 우리는 지식을 중요하게 여깁니다. 하지만 지식에 대한 지식, 즉 내가 그 지식을 얻은 과정이나 방법 같은 것은 소홀히 여기기 쉽습니다. 따라서 지식의 축적과 공유는 있어도 방법론의 축적과 공유는 매우 드문 편입니다. 저는 평소에 이런 생각에서 학교 후배들을 위해 제 자신의 공부 경험을 짬짬이 글로 옮겨놓았고, 이번 기회에 그 글들을 취합, 정리하게 되었습니다. 그 결실이 바로 이 글입니다.
이 글은 공부하는 방법과 과정에 관한 글입니다. 이 글은 제가 공부한 성공/실패 경험을 기본 토대로 했고, 지난 몇 년간 주변에서 저보다 먼저 공부한 사람들의 경험을 관찰, 분석한 것에 제가 다시 직접 실험한 것과 그밖에 오랫동안 꾸준히 모아온 자료들을 더했습니다. '만약 다시 공부한다면' 저는 이렇게 공부할 것입니다.
부디 독자 제현께서 이 글을 씨앗으로 삼아 자신만의 나무를 키우고 거기서 열매를 얻고, 또 그 열매의 씨앗이 다시 누군가에게 전해질 수 있다면 더 이상 바랄 것이 없겠습니다.
이 글은 특정 주제들의 학습/교수법에 대한 문제점과 제가 경험한 좋은 공부법을 소개하는 식으로 구성됐습니다. 여기에 선택된 과목은 리팩토링, 알고리즘·자료구조, 디자인패턴, 익스트림 프로그래밍(Extreme Programming 혹은 XP) 네 가지입니다.
이 네 가지가 선택된 이유는 필자가 관심있게 공부했던 것이기 때문만은 아니고, 모든 프로그래머에게 어떻게든 널리 도움이 될만한 교양과목이라 생각하여 선택한 것입니다. 그런데 이 네 가지의 순서가 겉보기와는 달리 어떤 단계적 발전을 함의하는 것은 아닙니다. 수신(修身)이 끝나면 더 이상 수신은 하지 않고 제가(齊家)를 한다는 것이 어불성설인 것과 같습니다.
원래는 글 후미에 일반론으로서의 공부 패턴들을 쓰려고 했습니다. 하지만 지면의 제약도 있고, 독자 스스로 이 글에서 그런 패턴을 추출하는 것도 의미가 있을 것이기에 생략했습니다. 그런 일반론이 여기 저기 숨어있기 때문에 알고리즘 공부에 나온 방법 대부분이 리팩토링 공부에도 적용할 수 있고, 적용되어야 한다는 점을 꼭 기억해 주셨으면 합니다. 다음에 기회가 닿는다면 제가 평소 사용하는 (컴퓨터) 공부패턴들을 소개하겠습니다.
알고리즘·자료구조 학습에서의 문제
우리는 알고리즘 카탈로그를 배웁니다. 이미 그러한 해법이 존재하고, 그것이 최고이며, 따라서 그것을 달달 외우고 이해해야 합니다. 좀 똑똑한 친구들은 종종 "이야 이거 정말 기가 막힌 해법이군!"하고 감탄할지도 모릅니다. 대부분의 나머지 학생들은 그 해법을 이해하려고 머리를 쥐어짜고 한참을 씨름한 후에야 어렴풋이 왜 이 해법이 그 문제를 해결하는지 납득하게 됩니다.
그리고는 그 '증명'은 책 속에 덮어두고 까맣게 사라져버립니다. 앞으로는 그냥 '사용'하면 되는 것입니다. 더 많은 대다수의 학생은 이 과정이 무의미하다는 것을 알기 때문에 왜 이 해법이 이 문제를 문제없이 해결하는지의 증명은 간단히 건너뜁니다.
이런 학생들은 이미 주어진 알고리즘을 사용하는 일종의 객관식 혹은 문제 출제자가 존재하는 시험장 상황에서는 뛰어난 성적을 보일 것임은 자명합니다. 하지만 스스로가 문제와 해답을 모두 만들어내야 하는 상황이라면, 또는 해답이 존재하지 않을 가능성이 있는 상황이라면, 혹은 최적해를 구하는 것이 불가능에 가깝다면, 혹은 알고리즘을 완전히 새로 고안해내야 하거나 기존 알고리즘을 변형해야 하는 상황이라면 어떨까요?
교육은 물고기를 잡는 방법을 가르쳐야 합니다. 어떤 알고리즘을 배운다면 그 알고리즘을 고안해낸 사람이 어떤 사고 과정을 거쳐 그 해법에 도달했는지를 구경할 수 있어야 하고, 학생은 각자 스스로만의 해법을 차근차근 '구성'(construct)할 수 있어야 합니다(이를 교육철학에서 구성주의라고 합니다. 교육철학자 삐아제(Jean Piaget)의 제자이자, 마빈 민스키와 함께 MIT 미디어랩의 선구자인 세이머 페퍼트 박사가 주창했습니다). 전문가가 하는 것을 배우지 말고, 그들이 어떻게 전문가가 되었는지를 배우고 흉내 내야 합니다.
결국은 소크라테스적인 대화법입니다. 해답을 가르쳐 주지 않으면서도 초등학교 학생이 자신이 가진 지식만으로 스스로 퀵소트를 유도할 수 있도록 옆에서 도와줄 수 있습니까? 이것이 우리 스스로와 교사들에게 물어야 할 질문입니다.
왜 우리는 학교에서 '프로그래밍을 하는 과정'이나 '디자인 과정'(소프트웨어 공학에서 말하는 개발 프로세스가 아니라 몇 시간이나 몇 십 분 단위의, 개인적인 차원의 사고 과정 등을 일컫습니다)을 명시적으로 배운 적이 없을까요? 왜 해답에 이르는 과정을 가르쳐주는 사람이 없나요? 우리가 보는 것은 모조리 이미 훌륭히 완성된, 종적 상태의 결과물로서의 프로그램뿐입니다. 어느 날 문득 하늘에서 완성된 프로그램이 뚝 떨어지는 경우는 없는데 말입니다.
교수가 어떤 알고리즘 문제의 해답을 가르칠 때, "교수님, 교수님께서는 어떤 사고 과정을 거쳐, 그리고 어떤 디자인 과정과 프로그래밍 과정을 거쳐서 그 프로그램을 만드셨습니까?"하고 물어봅시다. 만약 여기에 어떤 체계적인 답변도 할 수 없는 분이라면 그 분은 자신의 사고에 대해 '사고'해 본 적이 없거나 문제 해결에 어떤 효율적 체계를 갖추지 못한 분이며, 따라서 아직 남을 가르칠 준비가 되어있지 않은 분일 것입니다. 만약 정말 그렇다면 우리는 어떻게 해야 할까요?
자료구조와 알고리즘 공부
제가 생각건대, 교육적인 목적에서는 자료구조나 알고리즘을 처음 공부할 때 우선은 특정 언어로 구현된 것을 보지 않는 것이 좋을 때가 많습니다. 대신 말로 된 설명이나 의사코드(pseudo-code) 등으로 그 개념까지만 이해하는 것이죠. 그 아이디어를 절차형(C, 어셈블리어)이나 함수형(LISP, Scheme, Haskell), 객체지향(자바, 스몰토크) 언어 등으로 직접 구현해 보는 겁니다. 그 다음에는 다른 사람이나 다른 책의 코드와 비교합니다. 이 경험을 애초에 박탈당한 사람은 귀중한 배움과 깨달음의 기회를 잃은 셈입니다.
만약 여러 사람이 함께 공부한다면 각자 동일한 아이디어를 같은 언어로 혹은 다른 언어로 어떻게 다르게 표현했는지를 서로 비교해 보면 배우는 것이 무척 많습니다.
우리가 자료구조나 알고리즘을 공부하는 이유는, 특정 '실세계의 문제'를 어떠한 '수학적 아이디어'로 매핑시켜 해결할 수 있는지, 그것이 효율적인지, 또 이를 컴퓨터에 어떻게 효율적으로 구현할 수 있는지 따지고, 그것을 실제로 구현하기 위해서입니다. 따라서 이 과정에 있어 실세계의 문제를 수학 문제로, 그리고 수학적 개념을 프로그래밍 언어로 효율적으로 표현해내는 것은 아주 중요한 능력이 됩니다.
알고리즘 공부에서 중요한 것
개별 알고리즘의 목록을 이해, 암기하며 익히는 것도 중요하지만 더 중요한 것은 다음 네 가지입니다.
①알고리즘을 스스로 생각해낼 수 있는 능력
②다른 알고리즘과 효율을 비교할 수 있는 능력
③알고리즘을 컴퓨터와 다른 사람이 이해할 수 있는 언어로 표현해낼 수 있는 능력
④이것의 정상작동(correctness) 여부를 검증해 내는 능력
첫 번째가 제대로 훈련되지 못한 사람은 알고리즘 목록의 스테레오 타입에만 길들여져 있어서 모든 문제를 자신이 아는 알고리즘 목록에 끼워 맞추려고 합니다. 디자인패턴을 잘못 공부한 사람과 비슷합니다. 이런 사람들은 마치 과거에 수학 정석만 수십 번 공부해 문제를 하나 던져주기만 하면, 생각해보지도 않고 자신이 풀었던 문제들의 패턴 중 가장 비슷한 것 하나를 기계적·무의식적으로 풀어제끼는 문제풀이기계와 비슷합니다. 그들에게 도중에 물어보십시오. "너 지금 무슨 문제 풀고 있는 거니?" 열심히 연습장에 뭔가 풀어나가고는 있지만 그들은 자신이 뭘 풀고 있는지도 제대로 인식하지 못 하는 경우가 많습니다.
머리가 푸는 게 아니고 손이 푸는 것이죠. 이렇게 되면 도구에 종속되는 '망치의 오류'에 빠지기 쉽습니다. 새로운 알고리즘을 고안해야 하는 상황에서도 기존 알고리즘에 계속 매달릴 뿐입니다. 알고리즘을 새로 고안해 내건 혹은 기존의 것을 조합하건 스스로 생각해 내는 훈련이 필요합니다.
두 번째가 제대로 훈련되지 못한 사람은 일일이 구현해 보고 실험해 봐야만 알고리즘 간의 효율을 비교할 수 있습니다. 특히 자신이 가진 카탈로그를 벗어난 알고리즘을 만나면 이 문제가 생깁니다. 이건 상당한 대가를 치르게 합니다.
세 번째가 제대로 훈련되지 못한 사람은, 문제를 보면 "아, 이건 이렇게 저렇게 해결하면 됩니다"하는 말은 곧잘 할 수 있지만 막상 컴퓨터 앞에 앉혀 놓으면 아무 것도 하지 못합니다. 심지어 자신이 생각해낸 그 구체적 알고리즘을 남에게 설명해 줄 수는 있지만, 그걸 '컴퓨터에게' 설명하는 데는 실패합니다. 뭔가 생각해낼 수 있다는 것과 그걸 컴퓨터가 이해할 수 있게 설명할 수 있다는 것은 다른 차원의 능력을 필요로 합니다.
네 번째가 제대로 훈련되지 못한 사람은, 알고리즘을 특정 언어로 구현해도, 그것이 옳다는 확신을 할 수 없습니다. 임시변통(ad hoc)의 아슬아슬한 코드가 되거나 이것저것 덧붙인 누더기 코드가 되기 쉽습니다. 이걸 피하려면 두 가지 훈련이 필요합니다.
하나는 수학적·논리학적 증명의 훈련이고, 다른 하나는 테스트 훈련입니다. 전자가 이론적이라면 후자는 실용적인 면이 강합니다. 양자는 상보적인 관계입니다. 특수한 경우들을 개별적으로 테스트해서는 검증해야 할 것이 너무 많고, 또 모든 경우에 대해 확신할 수 없습니다. 테스트가 버그의 부재를 보장할 수는 없습니다. 하지만 수학적 증명을 통하면 그것이 가능합니다. 또, 어떤 경우에는 수학적 증명을 굳이 할 필요 없이 단순히 테스트 케이스 몇 개만으로도 충분히 안정성이 보장되는 경우가 있습니다. 이럴 때는 그냥 테스트만으로 만족할 수 있습니다.
실질적이고 구체적인 문제를 함께 다루라
자료구조와 알고리즘 공부를 할 때에는 가능하면 실질적이고 구체적인 실세계의 문제를 함께 다루는 것이 큰 도움이 됩니다. 모든 학습에 있어 이는 똑같이 적용됩니다. 인류의 지성사를 봐도, 구상(concrete) 다음에 추상(abstract)이 옵니다. 인간 개체 하나의 성장을 봐도 그러합니다. 'be-동사 더하기 to-부정사'가 예정으로 해석될 수 있다는 룰만 외우는 것보다 다양한 예문을 실제 문맥 속에서 여러 번 보는 것이 훨씬 나을 것은 자명합니다. 알고리즘과 자료구조를 공부할 때 여러 친구들과 함께 연습문제(특히 우리가 경험하는 실세계의 대상들과 관련이 있는 것)를 풀어보기도 하고, ACM의 ICPC(International Collegiate Programming Contest: 세계 대학생 프로그래밍 경진 대회) 등의 프로그래밍 경진 대회 문제 중 해당 알고리즘·자료구조가 사용될 수 있는 문제를 같이 풀어보는 것도 아주 좋습니다. 이게 가능하려면 "이 알고리즘이 쓰이는 문제는 이거다"하고 가이드를 해줄 사람이 있으면 좋겠죠. 이것은 그 구체적 알고리즘·자료구조를 훈련하는 것이고, 이와 동시에 어떤 알고리즘을 써야할지 선택, 조합하는 것과 새로운 알고리즘을 만들어내는 훈련도 무척 중요합니다.
알고리즘 디자인 과정의 중요성
알고리즘을 좀더 수월하게, 또 잘 만들려면 알고리즘 디자인 과정에 대해 생각해 봐야 합니다. 그냥 밑도 끝도 없이 문제를 쳐다본다고 해서 알고리즘이 튀어나오진 않습니다. 체계적이고 효율적인 접근법을 사용해야 합니다. 대표적인 것으로 다익스트라(E. W. Dijkstra)와 워스(N. Wirth)의 '조금씩 개선하기'(Stepwise Refinement)가 있습니다. 워스의 「Program Development by Stepwise Refinement」(1971, CACM 14.4, http://www.acm.org/classics/dec95)를 꼭 읽어보길 바랍니다. 여기 소개된 조금씩 개선하기는 구조적 프로그래밍에서 핵심적 역할을 했습니다(구조적 프로그래밍을 'goto 문 제거' 정도로 생각하면 안 됩니다). 다익스트라의 「Stepwise Program Construction」(Selected Writings on Computing: A Personal Perspective, Springer-Verlag, 1982, http://www.cs.utexas.edu/users/EWD/ewd02xx/EWD227.PDF)도 추천합니다.
알고리즘 검증은 알고리즘 디자인과 함께 갑니다. 새로운 알고리즘을 고안할 때 검증해 가면서 디자인하기 때문입니다. 물론 가장 큰 역할은 고안이 끝났을 때의 검증입니다. 알고리즘 검증에는 루프 불변식(loop invariant) 같은 것이 아주 유용합니다. 아주 막강한 무기입니다. 익혀 두면 두고두고 가치를 발휘할 것입니다. 맨버(Udi Manber)의 알고리즘 서적(『Introduction to Algorithms: A Creative Approach』)이 알고리즘 검증과 디자인이 함께 진행해 가는 예로 자주 추천됩니다. 많은 계발을 얻을 것입니다. 고전으로는 다익스트라의 『A Discipline of Programming』과 그라이스(Gries)의 『The Science of Programming』이 있습니다. 특히 전자를 추천합니다. 프로그래밍에 대한 관을 뒤흔들어 놓을 것입니다.
알고리즘과 패러다임
알고리즘을 공부하면 큰 줄기들을 알아야 합니다. 개별 테크닉도 중요하지만 '패러다임'이라고 할만한 것들을 알아야 합니다. 이것에 대해서는 튜링상을 수상한 로버트 플로이드(Robert Floyd)의 튜링상 수상 강연(The Paradigms of Programming, 1978)을 추천합니다. 패러다임을 알아야 알고리즘을 상황에 맞게 마음대로 변통할 수 있습니다. 그리고 자신만의 분류법을 만들어야 합니다. 구체적인 문제들을 케이스 바이 케이스로 여럿 접하는 동안 그냥 지나쳐 버리면 개별자는 영원히 개별자로 남을 뿐입니다. 비슷한 문제들을 서로 묶어서 일반화해야 합니다.
이런 패러다임을 발견하려면 '다시 하기'가 아주 좋습니다. 다른 것들과 마찬가지로, 이 다시 하기는 알고리즘에서만이 아니고 모든 공부에 적용할 수 있습니다. 같은 것을 다시 해보는 것에서 우리는 얼마나 많은 것을 배울 수 있을까요. 대단히 많습니다. 왜 동일한 문제를 여러 번 풀고, 왜 같은 내용의 세미나에 또 다시 참석하고, 같은 프로그램을 거듭 작성할까요? 훨씬 더 많이 배울 수 있기 때문입니다. 화술 교육에서는 같은 주제에 대해 한 번 말해본 연사와 두 번 말해본 연사는 천지 차이가 있다고 말합니다. 같은 일에 대해 두 번의 기회가 주어지면 두 번째에는 첫 번째보다 잘 할 기회가 있습니다. 게다가 첫 번째 경험했던 것을 '터널을 벗어나서' 다소 객관적으로 볼 수 있게 됩니다. 왜 자신이 저번에 이걸 잘 못 했고, 저걸 잘 했는지 알게 되고, 어떻게 하면 그걸 더 잘할 수 있을는지 깨닫게 됩니다. 저는 똑같은 문제를 여러 번 풀더라도 매번 조금씩 다른 해답을 얻습니다. 그러면서 정말 엄청나게 많은 것을 배웁니다. '비슷한 문제'를 모두 풀 능력이 생깁니다.
제가 개인적으로 존경하는 전산학자 로버트 플로이드(Robert W. Floyd)는 1978년도 튜링상 강연에서 다음과 같은 말을 합니다.
제가 어려운 알고리즘을 디자인하는 경험을 생각해 볼 때, 제 능력을 넓히는 데 가장 도움이 되는 특정한 테크닉이 있습니다. 어려운 문제를 푼 후에, 저는 그것을 처음부터 완전히 새로 풉니다. 좀 전에 얻은 해법의 통찰(insight)만을 유지하면서 말이죠. 해법이 제가 희망하는 만큼 명료하고 직접적인 것이 될 때까지 반복합니다. 그런 다음, 비슷한 문제들을 공략할 어떤 일반적인 룰을 찾습니다. 아까 주어진 문제를 아예 처음부터 최고로 효율적인 방향에서 접근하도록 이끌어줬을 그런 룰을 찾는 거죠. 많은 경우에 그런 룰은 불변의 가치가 있습니다. … 포트란의 룰은 몇 시간 내에 배울 수 있습니다. 하지만 관련 패러다임은 훨씬 더 많은 시간이 걸립니다. 배우거나(learn) 배운 것을 잊거나(unlearn) 하는 데 모두.
수학자와 프로그래머를 포함한 모든 문제 해결자들의 고전이 된 죠지 폴리야(George Polya)의 『How to Solve it』에는 이런 말이 있습니다:
심지어는 꽤나 훌륭한 학생들도, 문제의 해법을 얻고 논증을 깨끗하게 적은 다음에는 책을 덮어버리고 뭔가 다른 것을 찾는다. 그렇게 하는 동안 그들은 그 노력의 중요하고도 교육적인 측면을 잃어버리게 된다. … 훌륭한 선생은 어떠한 문제이건 간에 완전히 바닥이 드러나는 경우는 없다는 관점을 스스로 이해하고 또 학생들에게 명심시켜야 한다.
저는 ACM의 ICPC 문제 중에 어떤 문제를 이제까지 열 번도 넘게 풀었습니다. 대부분 짝 프로그래밍이나 세미나를 통해 프로그래밍 시연을 했던 것인데, 제 세미나에 여러 번 참석한 분이 농담조로 웃으며 물었습니다. "신기해요. 창준 씨는 그 문제를 풀 때마다 다른 프로그램을 짜는 것 같아요. 설마 준비를 안 해 와서 그냥 내키는 대로 하는 건 아니죠?" 저는 카오스 시스템과 비슷하게 초기치 민감도가 프로그래밍에도 작용하는 것 같다고 대답했습니다. 저 스스로 다른 해법을 시도하고 싶은 마음이 있으면 출발이 조금 다르고, 또 거기서 나오는 진행 방향도 다르게 됩니다. 그런데 중요한 것은 이렇게 같은 문제를 매번 다르게 풀면서 배우는 것이 엄청나게 많다는 점입니다. 저는 매번, 전보다 개선할 것을 찾아내게 되고, 또 새로운 것을 배웁니다. 마치 마르지 않는 샘물처럼 계속 생각할 거리를 준다는 점이 참 놀랍습니다.
알고리즘 개론 교재로는 CLR(Introduction to Algorithms, Thomas H. Cormen, Charles E. Leiserson, and Ronald L. Rivest)을 추천합니다. 이와 함께 혹은 이 책을 읽기 전에 존 벤틀리(Jon Bentley)의 『Programming Pearls』도 강력 추천합니다. 세계적인 짱짱한 프로그래머와 전산학자들이 함께 꼽은 위대한 책 목록에서 몇 손가락 안에 드는 책입니다. 아직 이 책을 본 적이 없는 사람은 축하합니다. 아마 몇 주간은 감동 속에 하루하루를 보내게 될 겁니다.
리팩토링 학습에서의 문제
먼저, 본지 2001년 11월호에 제가 썼던 마틴 파울러의 책을 추천하는 글을 인용하겠습니다.
OOP를 하건 안 하건 프로그래밍이란 업을 하는 사람이라면 이 책은 자신의 공력을 서너 단계 레벨업시켜줄 수 있다. 자질구레한 술기를 익히는 것이 아니고 기감과 내공을 증강하는 것이다.
혹자는 DP 이전에 RF를 봐야 한다고도 한다. 이 말이 어느 정도 일리가 있는 것이, 효과적인 학습은 문제의식이 선행해야 하기 때문이다. DP는 거시적 차원에서 해결안을 모아놓은 것이다. RF를 보고 나쁜 냄새(Bad Smell)를 맡을 수 있는 후각을 발달시켜야 한다. RF의 목록을 모두 외우는 것은 큰 의미가 없다. 그것보다 냄새나는 코드를 느낄 수 있는 감수성을 키우는 것이 더 중요하다. 필자는 일주일에 한 가지씩 나쁜 냄새를 정해놓고 그 기간 동안에는 자신이 접하는 모든 코드에서 그 냄새만이라도 확실히 맡도록 집중하는 방법을 권한다. 일명 일취집중후각법. 패턴 개념을 만든 건축가 크리스토퍼 알렉산더나 GoF의 랄프 존슨은 좋은 디자인이란 나쁜 것이 없는 상태라고 한다. 무색 무미 무취의 무위(無爲)적 자연(自然) 코드가 되는 그 날을 위해 오늘도 우리는 리팩토링이라는 유위(有爲)를 익힌다.
주변에서 리팩토링을 잘못 공부하는 경우를 종종 접합니다. 어떤 의미에서 잘못 공부한다고 할까요? '실체화'가 문제입니다. 리팩토링은 도구이고 방편일 뿐인데, 그것에 매달리는 것은 달은 보지 않고 손을 보는 것과 같습니다. 저는 리팩토링 책이 또 하나의 (이미 그 병폐가 많이 드러난) GoF 책이 되는 현상이 매우 걱정됩니다.
리팩토링 공부
사람들이 일반적으로 생각하는 바와는 달리 리팩토링 학습에 있어 어떤 리팩토링이 있는지, 구체적으로 무엇인지 등의 리팩토링 목록에 대한 지식과 각각에 대한 메카닉스(Mechanics: 해당 리팩토링을 해나가는 구체적 단계)는 오히려 덜 중요할 수 있습니다. 더 기본적이고 유용한 것은 코드 냄새(Code Smell)와 짧은 테스트-코드 싸이클입니다. 이것만 제대로 되면 리팩토링 책의 모든 리팩토링을 스스로 구성해낼 수 있으며, 다른 종류의 리팩토링까지 직접 발견해낼 수 있습니다.
그 책에서는 테스트의 중요성이 충분히 강조되지 않았습니다. 하지만 테스트 코드 없는 리팩토링은 안전벨트 없는 자동차 경주와 같습니다. 그리고 테스트 코드가 리팩토링의 방향을 결정하기도 합니다. 양자는 음과 양처럼 서로 엮여 있습니다. 이런 의미에서 리팩토링은 TDD(Test Driven Development)와 함께 수련하는 것이 좋습니다. 훨씬 더 빨리, 훨씬 더 많은 것을 배울 수 있을 겁니다.
리팩토링을 공부할 때는 우선 코드 냄새의 종류를 알고, 왜 그것이 나쁜 냄새가 될 수 있는지 이해하고(그게 불가하다면 리팩토링 공부를 미뤄야 합니다) 거기에 동의할 수 있어야 합니다. 그런 다음, 대충 어떤 종류의 리팩토링이 가능한지 죽 훑어봅니다. 그 중 몇 개는 메카닉스를 따라가면서 실험해 봅니다. 이제는 책을 덮습니다. 그리고 실제 코드를 접하고, 만약 거기에서 냄새를 맡는다면 이 냄새를 없애기 위해 어떻게 해야 할지 스스로 고민합니다. 리팩토링 책의 목록은 일단 무시하십시오. 그 냄새를 없애는 것이 목표이지, 어떤 리팩토링을 여기에 '써먹는 것'이 목표가 되어선 안 됩니다. 이 때, 반드시 테스트 코드가 있어야 합니다. 그래야 '좋은' 리팩토링을 할 수 있습니다. 책을 떠나 있으면서도 책에서 떠나지 않는 방법입니다.
리팩토링을 하기 전에 초록색 불(테스트가 모두 통과)이 들어온 시점에서 코드를 일부 고치면 빨간 불(테스트가 하나라도 실패)로 바뀔 겁니다. 이게 다시 초록색 불이 될 때까지 최소한도의 시간이 걸리도록 하십시오. 현 초록색에서 다음 초록색까지 최소한의 시간을 소비하도록 하면서 코드와 테스팅을 오가게 되면 자기도 모르는 사이에 훌륭한 리팩토링이 자발공으로 터져 나옵니다. 여기서 목적지는 우선은 OAOO(Once And Only Once: 모든 것은 오로지 한 번만 말해야 한다)를 지키는 쪽으로 합니다. 그러면 OAOO와 짧은 테스트-코드 싸이클을 지키는 사이 어느새 탁월한 디자인이 튀어나옵니다. 참고로 저는 '모래시계 프로그래밍'이란 걸 가끔 합니다. 모래시계나 알람 프로그램으로 테스트-코드 사이클의 시간을 재는 것입니다. 그래서 가급적이면 한 사이클이 3분 이내(대부분의 모래시계는 단위가 3분입니다)에 끝나도록 노력합니다. 여기서 성공을 하건 실패를 하건 많은 걸 얻습니다.
리팩토링 수련법
제가 고안, 사용한 몇 가지 리팩토링 수련법을 알려드립니다.
①일취집중후각법: 앞에 소개한 본지 2001년 11월호에서 인용된 글 참조
②주석 최소화: 주석을 최소화하되 코드의 가독성이 떨어지지 않도록(혹은 오히려 올라가도록) 노력합니다. 이렇게 하면 자동으로 리팩토링이 이뤄지는 경우가 많습니다.
③OAOO 따르기: OAOO 법칙을 가능하면 최대한, 엄격하게 따르려고 합니다. 역시 자동으로 좋은 리팩토링이 이뤄집니다. 여기서 디자인패턴이 창발하기도 합니다. GoF 책을 한번도 보지 못한 사람이 디자인패턴을 자유자재로 부리는 경우를 보게 됩니다.
④디미터 법칙(Law of Demeter) 따르기: 디미터 법칙을 가능하면 지키려고 합니다. 어떤 리팩토링이 저절로 이뤄지거나 혹은 필요 없어지는가요?
⑤ 짝(Pair) 리팩토링: 함께 리팩토링합니다. 혼자 하는 것보다 더 빨리, 더 많은 걸 배우게 됩니다. 특히, 각자 작성했던 코드를 함께 리팩토링하고, 제3자의 코드를 함께 리팩토링해 봅니다. 사람이 많다면 다른 짝이 리팩토링한 것과 서로 비교하고 토론합니다.
⑥'무엇'과 '어떻게'를 분리: 어떻게에서 무엇을 분리해 내도록 합니다. 어떤 리팩토링이 창발합니까?
여기서 번, 짝 리팩토링은 아주 효과적인 방법입니다. 저는 이것을 협동적 학습(Collaborative Learning)이라고 부릅니다. 상대가 나보다 더 많이 아는 경우만이 아니고, 서로 아는 것이 비슷해도 많은 양의 학습이 발생합니다. 특히, 전문가와 함께 짝 프로그래밍을 하면 무서울 만큼 빠른 학습을 경험할 수 있습니다. 저와 짝 프로그래밍을 한 사람이 학습하는 속도에서 경이감을 느낀 적이 한두 번이 아닙니다. 문화는 사회적으로 학습되는 것입니다. 우리가 지식이라고 하는 것의 상당 부분은 문화처럼 암묵적인 지식(Tacit Knowledge)입니다. 전문가가 문제가 생겼을 때 어떻게 사고하고, 어떤 과정을 거쳐 접근하며, 어떻게 디버깅하고, 키보드를 어떤 식으로 누르는지, 사고 도구로 무엇을 사용하는지, 일 계획과 관리를 어떻게 하는지, 동료와 어떻게 대화하는지 등은 성문화되어 있지 않습니다. 그러나 이런 것들은 아주 중요합니다. 프로페셔널의 하루 일과의 대부분을 이루고 있기 때문입니다. 이런 것들은 전문가 바로 옆에서 조금씩 일을 도와주면서 배워야 합니다. 도제 살이(Apprenticeship)입니다. 진정한 전문가는 모든 동작이 우아합니다. 마치 춤을 추는 것 같습니다. 이 모습을 바로 곁에서 지켜보게 되면, 주니어는 한마디로 엄청난 충격을 받습니다. 그리고 스펀지처럼 빨아들이게 됩니다. 도대체 이 경험을 책이나 공장화한 학교가 어떻게 대신하겠습니까. 이와 관련해서는 레이브와 웽거(Jean Lave, Etienne Wenger)의 『Situated Learning : Legitimate Peripheral Participation』을 강력 추천합니다. 이 글을 보는 모든 교육 종사자들이 꼭 읽어봤으면 합니다. 이 협동적 학습은 두 사람만이 아니고 그룹 단위로도 가능합니다. 스터디에 이용하면 재미와 유익함 일석이조를 얻습니다.
이 외에, 특히(어쩌면 가장) 중요한 것은 일취집중후각법 등을 이용해 자신의 코드 후각의 민감도를 높이는 것입니다. 코드 후각의 메타포 말고도 유사한 메타포가 몇 가지 더 있습니다. 켄트 벡은 코드의 소리를 들으라고 하고, 저는 코드를 향해 대화하라고 합니다. 코드의 소리를 듣는 것은 코드가 원하는 것에 귀를 기울이는 것을 말합니다. 코드는 단순해지려는 욕망이 있습니다. 그걸 이뤄주는 것이 프로그래머입니다. 그리고 짝 프로그래밍을 할 때 두 사람의 대화를 코드에 남기도록 합니다. 주석이 아닙니다. 왜 이것이 중요한가는 본지 2001년 12월호 「허실문답 XP 강화」를 참고하기 바랍니다
기학으로 우리 사상사에 큰 획을 그은 철학자요, '서울서 책만 사다 망한 사람'으로 이름을 날릴 정도로 엄청난 지식욕을 과시하던 조선시대 사상가 혜강 최한기는 그의 저술 『신기통』(神氣通)에서 '눈에 통하는 법(目通), 귀에 통하는 법(耳通), 코에 통하는 법(鼻通)' 등을 이야기하고 있습니다. 어떻게 하면 우리는 코드에 도통할 수 있을까요? 리팩토링을 공부하거나 혹은 했던 사람들에게 많은 영감과 메타포를 주는 책으로 일독을 권합니다. 필자가 기회가 닿는다면 프로그래밍을 혜강의 사상적 측면에서 조망한 글을 써보고 싶습니다.
앞서의 것들이 어느 정도 이뤄지고, 리팩토링에 대한 감이 오게 되면 그 때 비로소 리팩토링 책을 하나 하나 파헤치고 또 거기서 제대로 된 비판을 할 수 있게 됩니다.
디자인패턴 학습에서의 문제
잡지에 연재되거나 서적으로 출간된 혹은 세미나에서 진행되었던 디자인패턴 '강의'를 몇 가지 접했습니다. 훌륭한 강의도 많았지만 그렇지 못한 것도 있었습니다. 몇 가지 문제점을 지적하겠습니다.
◆패턴을 지나치게 실체화, 정형화해 설명한다.
◆컨텍스트와 문제 상황에 대한 설명이 없거나 부족하다. 결과적으로 문제를 해결하기 위해 패턴이 도입된 것이 아니라 패턴을 써먹기 위해 패턴이 도입된 느낌을 준다.
◆문제의식을 먼저 형성하게 하지 않고 답을 먼저 보여준 뒤 그걸 어디에 써먹을지 가르친다. 왜 이걸 쓰는 게 좋은지는 일언반구 언급이 없다. 독자는 '어린아이가 망치를 들고 있는 오류'에 빠질 것이다.
◆패턴이 어떻게 생성되었는지 그 과정을 보여주지 못한다. 즉, 스스로 패턴을 만들어내는 데 도움이 전혀 되지 않는다.
◆해당 패턴이 현실적으로 가장 자주 쓰이는 맥락을 보여주지 못한다. 대부분 장난감 문제(Toy Problem)에서 끝난다.
그런 패턴 강의를 하는 분들이 알렉산더(Christopher Alexander, 패턴언어 창시자)의 저작을 충실히 읽어봤다면 이런 병폐는 없을 것이라 생각합니다. 알렉산더의 저작을 접해보지 못 하고서 패턴을 가르치는 사람은 성경을 읽어보지 않은 전도사와 같을 것입니다. 알렉산더가 『The Timeless Way of Building』의 마지막에서 무슨 말을 하는가요?
이 마지막 단계에는 더 이상 패턴은 중요하지 않다. … 패턴은 당신이 현실적인 것에 대해 수용적이 되는 것을 가르쳐줬다.
패턴 역시 도구요, 방편일 뿐입니다. 패턴은 현실적인 것에 대해 수용적이 되도록 가르친다는 말은 결국 우리가 궁극적으로 추구하는 것은 패턴이 아니라 현실이어야 한다는 이야기입니다. 물론 처음 단계에는 교육적인 목적에서, 어느 정도 패턴에 얽매여도 괜찮다고는 해도, 나중에 패턴을 잊고 패턴에서 자유로워지려면 처음부터 패턴에 대해 도구적·방편적인 인식을 가져야 합니다.
미국 캘리포니아 주립대학의 교수 베티 에드워즈(Betty Edwards)가 쓴 책 중에 『Drawing on the Right Side of the Brain』이라는 유명한 베스트셀러가 있습니다. 사람의 뇌와 그림 그리기의 관계에 대한 탁월한 책입니다. 에드워즈는 자신의 그리기 실력을 향상시키기 위해 우뇌를 적극적으로 사용하는 구체적인 방법들을 가르쳐줍니다. 그 중 대표적인 것 하나가 대상을 뒤집어 놓고 그리는 것입니다. 지금 실험해 보길 바랍니다. 1000원권 지폐를 바로 놓고 그걸 비슷하게 그려보고, 이번에는 그걸 위아래가 거꾸로 되게 놓고 따라 그려보십시오. 아마 무척 놀랐을 겁니다. "아니 내가 이렇게 그림을 잘 그리다니! 그것도 거꾸로!" 그렇습니다. 우리는 자신의 머리 속 패턴에 얽매여 세상을 제대로 보지 못 할 때가 많습니다. 실체가 코에 약간이라도 비슷하게 보이면 우리는 그것을 이미 우리 머리 속에 추상적으로 갖고 있던 기하학적 '코'의 패턴으로 대체해버리는 것입니다.
디자인 패턴 공부
우선은 제 교육철학과 언어교습론, 그리고 공부론 이야기를 잠깐 하겠습니다.
기본적으로 교육은 교육자가 피교육자가에게 지식을 그대로 전달하는 행위가 아닙니다. 진정한 교육은 피교육자의 개인적 체험에 기반한 전폭적 동의에서 출발합니다. 저는 이를 동의에 의한 교육이라고 합니다.
제 가 "주석문을 가능하면 쓰지 않는 것이 더 좋다"는 이야기를 했을 때 이 문장을 하나의 사실로 받아들이고 기억하면 당장 그 시점에는 학습이 일어나지 않는다고 봅니다. 대신 여러분이 차후에 여러 가지 경험을 하면서도 이 화두를 놓치지 않고 있다가 어느 순간엔가, "아! 그래, 주석문을 쓰지 않는 게 좋겠구나!"하고 자각하는 순간, 바로 그 시점에 학습과 교육이 이뤄지는 것입니다. 이는 기본적으로 삐아제와 비갓스키(Lev Vygotsky)의 구성주의를 따르는 것이죠. 지식이란 외부에서 입력받는 것이 아니고, 그것에 대한 모델을 학습자 스스로가 내부에서 구축할 때 획득할 수 있는 것이라는 사상이죠.
권법에서 주먹에 대해 달통한 도사가 '권을 내지르는 법'에 대한 규칙들을 정리해서 애제자의 머리 속에 아무리 쑤셔 넣는 데 성공한들 그 제자가 도사만큼 주먹이 나갈리는 만무합니다. 권을 내지르는 법을 유추해 내기까지 그 스승이 겪은 과정을 제자는 완전히 쏙 빼먹고 있기 때문입니다. 소위 '몸'이 만들어지지 않은 것이지요. 제자는 마당 쓸기부터, 물 긷기 등의 수련 과정을 겪어야만 하고 스승이 정리한 그 일련의 규칙에 손뼉을 치고 춤을 추며 기쁨의 동의를 할 수 있을 정도로 수련 과정이 축적된 이후에야 비로소 진정한 '가르침'이 이뤄지는 것이며, 청출어람의 가능성도 생각해 볼 수 있는 것입니다.
이런 동의라는 것은 학습자 자신만의 컨텍스트와 문제의식을 바탕으로 한 것입니다. 우리는 많은 경우, 어떤 지식과 동시에 그 지식의 필요성까지도 지식화해서 외부에서 주입을 받습니다. 하지만 진정 체화된 지식을 위해서는 스스로가 이미 문제의식을 갖고 있어야 합니다.
패턴도 마찬가지인데, 대부분 그 패턴의 필요성을 체감하지 못한 채 그냥 도식적 구조를 외우기에만 주력하는 사람이 많습니다만, 사실 그렇게 되면 어떤 경우에 이 패턴이 필요하고 어떤 경우에는 사용하면 안 되는지(GoF는 패턴을 정말 안다는 것은 그 패턴을 쓰면 안 될 때를 아는 것이라 했습니다) 등을 알기 힘듭니다. 설령 책에 나온 가이드를 암기했더라도요. 자신의 삶 속에서 문제의식이 구체적으로 실제 경험으로 형성되지 않았기 때문입니다.
GoF 중 한 명인 랄프 존슨(Ralph Johnson)은 다음과 같이 말합니다.
우리[GoF]는 책에서, 정말 그 패턴들이 필요하다는 것을 알만큼 충분한 경험을 갖기 전에는 그것을 [시스템 속에] 집어넣는 것을 피해야 한다고 말할 만큼 대담하진 못했다. 하지만 우리 모두는 그 사실을 믿었다. 패턴은 프로그램의 초기 버전이 아니고 프로그램 생애의 훨씬 나중에 가서야 비로소 등장해야 한다고 나는 늘 생각해 왔다.
결국은 어떤 패턴의 필요성을 자신의 경험 속에서 절감하지 못한다면 그 패턴을 제대로 아는 것이 아니라고 말할 수 있을 겁니다. 따라서 패턴 하나를 공부할 때는 가능한 한 실제 예를 많이 접해야 합니다. 그리고 패턴을 적용하지 않은 경우에서 그 필요를 느끼고 설명할 수 있게끔 다양한 코드를 접해야 합니다.
소프트웨어 개발에 푹 빠지기
패턴 중에 보면 서로 비슷비슷한 것이 상당히 많습니다. 그 구조로는 완전히 동일한 것도 있죠. 초보자를 괴롭히는 것 중 하나입니다. 이것은 외국어를 공부할 때 문법 중심적인 학습을 하면서 부딪치는 문제와 비슷합니다. '주어+동사+목적어'라는 구조로는 동일한 두 개의 문장, 즉 'I love you'와 'I hate you'가 구조적으로는 동일할지라도 의미론적으로는 완전히 반대가 될 수 있는 겁니다. 패턴을 공부할 때는 그 구조보다 의미와 의도를 우선해야 하며, 이는 다양한 실례를 케이스 바이 케이스로 접하면서 추론화 및 자신만의 모델화라는 작업을 통해 하는 것이 최선입니다. 스스로 문법을 발견하고 체득하는 것이라고 할까요.
DP는 사전과 같습니다. 이 책은 순서대로 소설 읽듯이 읽어나가라고 집필된 것이 아니고, 일종의 패턴 레퍼런스로 쓰인 것입니다. 역시 GoF의 한 명인 존 블리스사이즈(John Vlissides)는 다음과 같이 말합니다.
여기서 내가 강조하고 싶은 것은 디자인패턴, 즉 GoF 책을 어떻게 읽느냐는 것이다. 많은 사람들은 그 내용을 온전히 이해하기 위해서는 순서대로 읽어야 한다고 느낀다. 하지만 GoF는 소설이 아니라 레퍼런스 북이다. 독일어를 배우기 위해 독영 사전의 처음부터 끝까지 하나하나 읽으려고 하는 경우를 생각해 보라. 그렇게는 결코 배울 수 없을 것이다! 독일어를 마스터하기 위해서는 독일어 문화에 자기 자신을 푹 담궈야(immerse) 한다. 독일어로 살아야 하는 것이다. 디자인패턴도 똑같다. 그걸 마스터하기 이전에 소프트웨어 개발에 자신을 푹 담궈야 한다. 패턴으로 살아야 하는 것이다.
만약 꼭 그래야 한다면 소설 읽듯이 디자인패턴 책을 읽어라. 하지만 거의 아무도 그 방식으로 유창해지지 못한다. 소프트웨어 개발 프로젝트의 열기 속에서 패턴이 동작하게 하라. 실제 디자인 문제를 직면했을 때 그 패턴들의 통찰을 이용하라. 이것이 GoF 패턴들을 자신의 것으로 만드는 가장 효율적인 방법이다.
어떤 지식을 체화하기 위해선 그 지식으로 살아야 한다는 말을 확인할 수 있습니다. 영어를 배우려면 영어로 살고, DP를 배우려면 DP로 살라는 단순하면서도 아주 강력한 말입니다.
어떤 특정 문장 구조를 학습하는 데 최선은 그 문장 구조를 이용한 실제 문장을 나에게 의미 있는 실제 컨텍스트 속에서 많이 접하고 스스로 나름의 모델을 구축하여 교과서의 법칙에 '기쁨에 찬 동의'를 하는 것입니다.
주변에서 특정 패턴이 구현된 코드를 구하기가 힘들다면 이 패턴을 자신이 만지고 있는 코드에 적용해 보려고 노력해 볼 수 있습니다. 이렇게 해보고 저렇게도 해보고, 그러다가 오히려 복잡도만 증가하면 "아 이 경우에는 이 패턴을 쓰면 안 되겠구나"하는 걸 학습할 수도 있죠. GoF는 패턴을 배울 때는 한결 같이 "이 패턴이 적합한 상황과 동시에 이 패턴이 악용, 오용될 수 있는 상황"을 함께 공부하라고 합니다.
이런 식의 '사례 중심'의 공부를 위해서는 스터디 그룹을 조직하는 것이 좋습니다. 혼자 공부를 하건, 그룹으로 하건 커리프스키(Joshua Kerievsky)의 「A Learning Guide To Design Patterns」(http://www.industriallogic.com/papers/learning.html)를 참고하세요. 그리고 스터디 그룹을 효과적으로 꾸려 나가는 데는 스터디 그룹의 패턴 언어를 서술한 「Knowledge Hydrant」(http://www.industriallogic.com/papers/khdraft.pdf)를 참고하면 많은 도움이 될 겁니다. 이 문서는 뭐든지 간에 그룹 스터디를 한다면 적용할 수 있습니다.
LG2DP(「A Learning Guide To Design Patterns」) 뒷부분에 보면 DP를 공부하는 순서와 각 패턴에서 던질만한 질문이 같이 정리되어 있습니다. DP는 순차적으로 공부해야만 하는 것은 아닙니다. 효과적인 공부의 순서가 있습니다. sorry라는 단어를 모르면서 remorseful이라는 단어를 공부하는 학생을 연상해 보세요. 외국어 공부에서는 자기 몸에 가까운 쉬운 단어부터 공략하는 것이 필수적입니다. 이런 걸 근접 학습(Proximal Learning)이라고도 하죠. 등급별 어휘 목록 같은 게 있으면 좋죠. LG2DP에서 제안하는 순서가 그런 것 중 하나입니다.
랄프 존슨은 이런 순서의 중요성에 관해 다음과 같은 말을 했습니다.
… 하지만 나는 언제나 싱글톤 패턴을 가르치기 전에 콤포짓, 스트래터지, 템플릿 메쏘드, 팩토리 메쏘드 패턴을 가르친다. 이것이 훨씬 더 일반적인 것들이며, 대다수의 사람들은 아마도 이것들 중 마지막 두 가지를 이미 사용하고 있을 것이다.
마이크로패턴
그런데 사실 GoF의 DP에 나온 패턴들보다 더 핵심적인 어휘군이 있습니다. 마이크로패턴이라고도 불리는 것들입니다. DP에도 조금 언급되어 있긴 합니다. 이런 마이크로패턴은 우리가 알게 모르게 매일 사용하는 것들이고 그 활용도가 아주 높습니다. 실제로 써보면 알겠지만, DP의 패턴 하나 쓰는 일이 그리 흔한 게 아닙니다. 마이크로패턴은 켄트 벡의 『Smalltalk Best Practice Patterns』에 잘 나와 있습니다. 영어로 치자면 관사나 조동사 같은 것들입니다.
그리고 이런 마이크로패턴과 함께 리팩토링을 공부하는 게 좋습니다. 리팩토링은 패턴의 필요를 느끼게 해줍니다. 제가 리팩토링 공부에서도 언급했지만 OAOO를 지키면서 리팩토링을 하다보면 자연스레 디자인패턴에 도달하는 경우가 많습니다. 이 때는 지나친 엔지니어링(Overengineering)이 발생하지 않고, 오로지 필요한 만큼만 생깁니다. 이에 관해서는 커리프스키의 「Stop Over-Engineering!」(Software Development Magazine, Apr 2002, http://www.sdmagazine.com/documents/s=7032/sdm0204b/0204b.htm)의 일독을 권합니다. 리팩토링이 디자인패턴을 어떻게 생성해낼 수 있는지 보여주고 있습니다.
1980년대 이후 최근 알렉산더의 향방도 이런 쪽으로 가고 있습니다. 그는 자신이 발표한 기존 패턴들이 너무 크다고 생각해 그런 패턴들을 구성하고, 자동으로 만들어 내며, 또 관통하는 더 작은 원칙들을 발견하는 데 노력해 오고 있습니다. 코플리엔(James Coplien)은 컴퓨터계가 알렉산더의 최근 발전을 쫓아가지 못한다고 늘 이야기합니다.
제대로 된 패턴 구현을 위한 다양한 접근 시도하기
우리의 지식이라는 것은 한 가지 표현양상(representation)으로만 이뤄져 있지 않습니다. 사과라는 대상을 음식으로도, 그림의 대상으로도 이해할 수 있어야 합니다. 실제 패턴이 적용된 '다양한 경우'를 접하도록 하라는 것이 이런 겁니다. 동일 대상에 대한 다양한 접근을 시도하라는 것이죠. 자바로 구현된 코드도 보고, C++로 된 것도 보고, 스몰토크로 된 것도 봐야 합니다. 설령 '오로지 자바족'이라고 할지라도요(전 이런 사람들을 자바리안(Javarian)이라고 부릅니다. 자바와 바바리안(barbarian)을 합성해서 만든 조어지요). 이런 '오로지 하나만 공부하는 것'의 병폐에 대해서는 존 블리스사이즈가 쓴 「Diversify」(http://www.research.ibm.com/people/v/vlis/pubs/gurus-99.pdf)라는 글을 읽어보세요. 이렇게 다양화를 해야 비로소 자바로도 '상황에 맞는' 제대로 된 패턴을 구현할 수 있습니다. 패턴은 그 구현(implementation)보다 의도(intent)가 더 중요하다는 사실을 꼭 잊지 말고, 설명을 위한 방편으로 채용된 한 가지 도식에 자신의 사고를 구속하는 우를 범하지 않기를 빕니다.
이런 맥락에서 저는 DP를 공부할 때 GoF와 동시에 『Design Patterns Smalltalk Companion』을 필수적으로 읽기를 권합니다. 두 권은 말하자면 양날개입니다. 하나는 정적언어로 구현되었고(간간이 스몰토크 구현이 있긴 합니다), 다른 하나는 동적언어로 구현되어 있습니다. 언어와 패턴의 고리를 느슨하게 하고, 패턴을 여러 관점에서 신선하게 볼 수 있는 계기가 될 것입니다. 또, 한 쪽을 보고 이해가 잘 되지 않을 때 다른 쪽을 보면 쉽게 이해됩니다. 서로 상보적인 것이죠.
패턴도 결국 '문제해결'을 위한 한 가지 방편에 지나지 않습니다. 주변에서 "이 경우에는 무조건 이 패턴을 써야 합니다"하고 생떼를 쓰는 사람을 보면 씁쓸한 기분을 감출 수 없습니다.
디자인패턴 추천서
디자인패턴 책 중에 중요한 서적을 몇 권 소개하겠습니다.
◆『Design Patterns Explained』(Shalloway, Trott): 최근 DP 입문서로 급부상하고 있는 명저
◆ 『Design Patterns Java Workbook』(Steven John Metsker): DPE 다음으로 볼만한 책으로 쏟아져 나오는 시중의 조악한 자바 패턴 책들과는 엄연히 다르다. 워크북 형식을 채용해서 연습문제를 풀고 뒷부분의 답과 대조해 볼 수 있는 등 독학자가 공부하기에 좋다.
◆『Refactoring』(Martin Fowler): DP 공부 이전에 봐서 문제의식 형성하기(망치를 들면 모든 것이 못으로 보이는 오류 탈피)
◆『Design Patterns』: 바이블.
◆『Design Patterns Smalltalk Companion』: GoF가 오른쪽 날개라면 DPSC는 왼쪽 날개
◆『Pattern Hatching』(John Vlissides): DP 심화학습. 얇지만 밀도 높은 책.
◆『Smalltalk Best Practice Patterns』(Kent Beck): 마이크로 패턴. 개발자의 탈무드. 감동의 연속.
◆『Pattern Languages of Program Design』 1,2,3,4: 패턴 컨퍼런스 논문 모음집으로 대부분은 인터넷에서 구할 수 있음
◆『Pattern-Oriented Software Architecture』 1,2: 아키텍처 패턴 모음. 2권은 네트워크 애플리케이션의 아키텍처 모음.
◆『Concurrent Programming in Java』(Doug Lea): 컨커런트 프로그래밍에 대한 최고의 서적.
◆『Patterns of Software』(Richard Gabriel): 패턴에 관한 중요한 에세이 모음.
◆『Analysis Patterns』(Martin Fowler): 비즈니스 분석 패턴 목록. 비즈니스 애플리케이션 개발자에게 많은 도움이 됨.
◆ 『A Timeless Way of Building』(Christopher Alexander): 프로그래머들이 가장 많이 본 건축 서적. 패턴의 철학적·이론적 배경. '구약'('신약'은 올해 안에 출간 예정인 동저자의 『The Nature of Order』).
◆『A Pattern Language』(Christopher Alexander): 알렉산더의 건축 패턴 모음집.
◆『Problem Frames』(Michael Jackson): DP의 해결(solution) 지향식의 문제점과 극복 방안으로서의 문제 지향식 방법. 마이클 잭슨은 요구 사항 분석 쪽에서 동명의 가수만큼이나 유명.
DP를 처음 공부한다면, DPE와 DPJW를 RF와 함께 보면서 앞서의 두 권을 RF적으로 독해해 나가기를 권합니다(하버드 대학의 뚜웨이밍 교수는 요즘 칸트를 유교적으로 독해하고 있다고 합니다. 하나의 책을 다른 각도에서 독해하는 것, 여기서 배우는 것이 많습니다). 이게 된 후에는 GoF와 DPSC를 함께 볼 수 있습니다. 양자는 상호 보완적인 면이 강합니다. 이쯤 되어 SBPP를 보면 상당히 충격을 받을 수 있습니다. 스스로가 생각하기에 코딩 경험이 많다면 다른 DP 책 이전에 SBPP를 먼저 봐도 좋습니다.
이 정도의 책을 봤다면 POSA와 PLOPD 등에서 자신이 관심이 가는 패턴들을 찾아 읽는 것이 좋습니다. 그리고 동시에 알렉산더의 원저들을 꼭 읽기를 권합니다. 가브리엘의 책이 알렉산더의 사상 이해에 많은 도움이 될 것입니다.
패턴 공부를 해나가면서 남을 가르치는 것이 공부에 많은 도움이 됩니다(사실 자바 패턴 책 중에 어떤 것은 "내가 패턴을 처음 공부하면서 같이 쓴 것이다"라고 저자가 고백한 것도 있습니다). 보이스카웃에서는 보통 다음 과정을 통해 뭔가를 '학습'하게 한다고 합니다. 처음은 어떻게 하는지를 보여주고, 다음은 스스로 그것을 해보게 하고, 다음으로 그걸 남에게 가르치게 합니다. 이 때 중요한 것은 상대가 이해하지 못 하면 그 이유를 자기 자신에게서 찾는 것이 나에게 더 이득이 된다는 것입니다. "내가 설명을 잘못 했군"하고 생각하는 것이죠. 그러면 다음번에는 설명을 좀더 잘 할 수 있게 되고, 동시에 자기의 이해도 더욱 명료해지게 됩니다. 저는 'OOP 개념을 한 시간 만에 가르치기'나 '특정 언어 문법을 한 시간 만에 가르치기' 등을 하나의 도전으로 생각하고 즐깁니다. 가르치면서 동시에 배운다는 것은 정말 놀라운 경험입니다.
익스트림 프로그래밍 학습에서의 문제
앞의 경우와 비슷합니다. 익스트림 프로그래밍을 공부하는 사람들은 실제로 행해보지 않고 책만 들여다보면 안 됩니다. 그렇다고 책이 중요하지 않다는 말은 아닙니다. 하지만 자전거 타기는 자기 몸으로 직접 굴려봐야 합니다.
게다가 켄트 벡 스스로가 『XP Explained』를 만약 다시 쓴다면 뜯어고치고 싶은 부분이 상당히 된다고 말하는 것을 봐도 알 수 있듯이 초기 XP 이후 바뀌고 보완된 점이 상당수 있습니다. 따라서 책만으로 XP를 공부하기는 힘듭니다. 지금은 책 속의 XP가 사람들의 머리 속 XP에 한참 뒤쳐져 있습니다.
어찌 되었든 XP에는 무술이나 춤, 혹은 악기 연주 등과 유사한 면이 많습니다. 따라서 글을 보고 그것을 익히기는 쉽지 않습니다. 그나마 메일링 리스트 같은 '대화'를 보면 훨씬 더 많은 것을 얻을 수 있기는 하지만, 태권도 정권 찌르기를 말로 설명하는 것이 불가능에 가깝듯이 XP를 언어를 통해 익히기는 정말 어렵습니다. 우리의 언어는 너무도 성글은 미디어입니다(XP는 매 초, 매 순간 벌어지는 '일상적' 장면의 연속이 매우 중요합니다).
익스트림 프로그래밍 공부
XP를 이해하려면 다음 기본 자료에 대한 이해가 우선해야 합니다(본지 2001년 12월호 「허실문답 XP 강화」 참조).
◆『XP Explained』(Kent Beck): XP 선언서
◆『XP Installed』(Ron Jeffries et al): C3 프로젝트에 적용한 예, 얻은 교훈 등
◆『Planning XP』(Kent Beck, Martin Fowler): 계획 부분 설명(관리자, 코치용)
◆『Refactoring』(Martin Fowler): 리팩토링에 대한 최고의 책
◆『XP Applied』: 유즈넷과 메일링 리스트의 논의 등 최근 자료를 반영
◆『XP Explored』: 가장 쉽고 구체적인 XP 안내서
이 중에서 XPI나 XPX를 먼저 권합니다. XPE는 좀 추상적인 서술이 많아서 봐도 느낌이 별로 없을 수 있습니다(2001년 본지 11월호에 제가 쓴 리뷰 참고). 여유가 되면 다음 자료를 더 참고합니다.
◆ 『The Timeless Way of Building』: 패턴 운동을 일으킨 알렉산더의 저작. 현장 고객(On-site Customer), 점진 성장(Piecemeal Growth), 소통(Communication) 등의 아이디어가 여기에서 왔음.
◆『XP in Practice』(Robert C. Martin 외): 두세 사람이 짧은 기간 동안 간단한 프로젝트를 XP로 진행한 것을 기록. 자바 사용(중요한 문헌은 아님).
◆『XP Examined』: XP 컨퍼런스에 발표된 논문 모음
◆『Peopleware』(Tom DeMarco): 개발에 있어 인간 중심적 시각의 고전
◆『Adaptive Software Development』(Jim Highsmith): 복잡계 이론을 개발에 적용. 졸트상 수상.
◆『Surviving Object-Oriented Projects』(Alistair Cockburn): 얇고 포괄적인 OO 프로젝트 가이드라인
◆『Software Project Survival Guide』(Steve McConnell): 조금 더 전통적인 SE 시각.
◆ 『The Psychology of Computer Programming』(Gerald M. Weinberg): 프로그래밍에 심리학을 적용한 고전. 코드 공유와 짝 프로그래밍에 필수인 비자아적 프로그래밍(Egoless Programming)이 여기서 나왔다.
◆『Agile Software Development』(Alistair Cockburn): 전반적 애자일 방법론에 대한 개론서
◆『Software Craftsmanship』(Pete McBreen): 장인으로서의 새로운 프로그래머 상
◆『Agile Software Development with SCRUM』(Schwaber Ken): 최근 확장성(Scalability)을 위해 XP+SCRUM의 시도가 애자일 쪽의 큰 화두임.
◆『A Practical Guide to eXtreme Programming』(David Astels 외): 저자들이 직접 참여한 프로젝트를 따라가 보면서 배움. 자바로 구현. XPP보다는 스케일이 큼.
◆『Agile Modeling』(Scott Ambler): 애자일 쪽에서 모델링이 무시되는 느낌이 있을 수 있는데, 그쪽으로 깊게 천착한 사람이 앰블러임.
◆『Agile Software Development Ecosystems』(Jim Highsmith): 각각의 애자일 방법론에 대한 소개와 동시에 각 방법론의 대표자들 인터뷰가 백미.
◆『Test Driven Development』(Kent Beck): 곧(아마 올해 내에) 출간 예정인 최초의 TDD 서적. TDD를 모르면 XP도 모르는 것(TDD를 실제 적용하려면 적어도 반년 정도는 계속 훈련해야 함)
◆IEEE Software/Computer, CACM, Software Development Magazine 등에 실린 기사
◆『XP Conference, XP Universe 등의 논문들(특히 최근 것들)
◆유즈넷, 메일링 리스트, 오리지널 위키(http://c2.com)의 논의들
특히 유즈넷, 메일링 리스트, 오리지널 위키는 늘 가까이 하고 있어야 합니다. 이 세 곳을 살필 때, 특히 다음 사람들의 글은 꼭 읽어보고 항상 레이더를 열어두면 좋습니다(이 외에도 개발 경력 10년, 20년이 넘는 짱짱한 사람이 많으므로 눈 여겨 관찰하세요. 모든 글을 읽는 것은 무리이므로 그들의 대화를 일차적으로 읽어야 합니다).
켄트 벡, 론 제프리즈(Ron Jeffries), 워드 커닝엄(Ward Cunningham), 앨리스테어 코번(Alistair Cockburn), 마틴 파울러, 로버트 마틴 혹은 엉클 밥(Robert C. Martin aka Uncle Bob), 마이클 페더즈(Michael Feathers), 켄 아우어(Ken Auer), 윌리엄 웨이크(William Wake), 로이 밀러(Roy Miller), 데이브 토마스(Dave Thomas), 앤디 헌트(Andy Hunt), 랄프 존슨, 스카트 앰블러(Scott Ambler), 짐 하이스미스(Jim Highsmith), 조슈아 커리프스키(Joshua Kerievsky), 로렌트 보사빗(Laurent Bossavit), 존 브루어(John Brewer) 등
이런 자료들 외에, 기회가 된다면 주변에서 XP를 직접 사용하는 곳을 방문해서 하루만 같이 생활해 보기를 권합니다. 반년 공부를 앞당길 수 있습니다. 제가 공부할 때는 주변에 XP를 아는 사람이 없어서 혼자 공부했는데, 그것에 비해 XP를 직접 사용하는 상황에 처한 사람은 (제가 억울하리만큼) 더 적은 노력으로 몇 배 이상 빨리 몸에 익히는 것 같았습니다.
이게 힘들면 같이 공부하는 방법이 있습니다(앞서 언급된 스터디 그룹에 관한 패턴 참고). 이 때 같이 책을 공부하거나 하는 것은 시간 낭비가 많습니다. 차라리 공부는 미리 다 해오고 만나서 토론을 하거나 아니면 직접 실험을 해보는 것이 훨씬 좋습니다. 두 사람 당 한 대의 컴퓨터와 커대란 화이트보드를 옆에 두고 말이죠. 제 경우 스터디 팀과 함께 저녁 시간마다 가상 XP 프로젝트를 많이 진행했고, 짤막짤막하게 프로그래밍 세션도 많이 가졌습니다. 나중에 회사에서 직접 XP를 사용할 때 많은 도움이 되었습니다.
Refactor Me
제가 하고 싶은 말은 더 있지만 이 글은 일단 여기서 끝이 납니다. 서두에서 말씀드렸듯이 애초 쓰려던 '일반론'은 생략하고, 대신 독자들의 몫으로 남겨두려 합니다. 이 글 자체가 여러분의 리팩토링 수련의 연장(延長)이 되었으면 하는 바램입니다. 이 글과 함께 실생활에서 직접 실험을 해보면서 - 이 때 욕심 부리지 않고 한 가지씩 지긋이 해보는 느긋함과 음미의 정신이 필요할지도 모르겠습니다 - 자신의 경험을 축적해 나가고, 동시에 이 글을 적절히 리팩토링해서 자신만의 패턴을 차근히 만들어 나가길 바랍니다. 그렇습니다. 리팩토링은 대상에 대한 이해가 깊고 경험이 많을수록 더 잘할 수 있습니다.
이 글에 소개된 제 공부론은 어찌 보면 상당히 진부해 보이기도 할 것입니다. 하지만 저는 이런 상식적이고 일상적이며 심지어는 소소해 보이는 것들에서 많은 감동을 받아왔습니다. 이 글도 사실 제 감동의 개인사입니다. 저는 "만약 오늘 어떤 것에라도 감동한 것이 없었다면, 오늘은 뭔가 잘못 산 것이다"라는 신조를 갖고 있습니다. 그것이 컴퓨터이건 대화이건 상관없이 말이죠. 저는 날마다 감동하며 살려고 노력합니다. 그러나 이 감동에 뭔가 꼭 대단한 것이 필요한 것은 아닙니다. 저는 오히려 감동 받기 위해 스스로 대단해져야 할 필요를 느끼기도 합니다. '감동'이라는 것은 주어지는 것이 아닙니다. 나와 타자가 공조하여 만드는 대화입니다.
감동해야 체득할 수 있다고 생각합니다. 그리고 이 감동은 개인적 삶 속에서 자기가, 자신의 몸으로, 직접 얻는 것입니다. 工夫 열심히 합시다. @
출처 : http://www.zdnet.co.kr/hotissue/devcolumn/article.jsp?id=49399
김창준 (마이크로소프트웨어)
개인적으로 오라클에서는 대용량 데이터를 핸들링할 일이 별로 없어서 아쉬웠는데, 교육을 통해 대용량 데이터 운용에 대해 많이 배운 느낌이다. 특히 온라인과 배치작업의 차이, 부분범위처리 개념, 다양한 힌트적용방법 등은 깊이 이해하지 못하였던 부분이라 꽤 유용할 거 같다.
강의에서 나왔던 주요 내용들을 정리해 보자.
- 자주 사용되는 코드성 테이블에서 좀더 성능개선이 필요하면 일체형테이블(IOT)을 고려하자.
변경사항은 거의 없고, 조회가 많은 데이터 크기가 작은 테이블
- RowID : 인덱스는 인덱스 칼럼 다음에 RowID로 소팅된다. RowID에는 Block No, Slot No 등이 포함되어 있다.
- 오라클8i에 나온 LMT(Locally Managed Tablespace)는 기존 Dictionary 관리방식에서 로컬 tablespace의 bitmap정보로 공간관리를 한다. 대용량 테이블일 경우 UNIFORM방식으로 하는게 좋다. SYSTEM으로 할 경우 병렬처리시(parallel힌트)에 64M Extent를 여러개 생성해서 넣는 등 공간 낭비가 있을 수 있다.
- temp성 테이블이나 update가 없는 로그성 테이블은 테이블 생성시 pctfree를 0으로 생성하자.
- table full scan은 multi block I/O로 수행되고, index range scan은 Single block I/O로 수행된다. 따라서 조회건이 10%이상이거나 건수가 많으면 full scan이 낫다.
cf) index full scan 은 single block I/O, index fast full scan 은 multi block I/O
- 대용량테이블에서 특정 구간을 temp 테이블로 가져올 때
create table /*+ parallel */ temp_01 as select /*+ parallel(a, 8) */ from table1 a where 일자 = ' ';
- workarea_size_policy 가 AUTO일 경우 소팅, 해쉬처리시에 사용되는 메모리를 pga_aggregate_taget 값으로 자동으로 할당한다. 2G이상 주자.
- 배치작업시에는 다음과 같이 메모리 수동할당으로 작업할 수 도 있다.
alter session set workarea_size_policy = manual;
alter session set sort_area_size = 200M
alter session set hash_area_size = 200M
배치작업때는 여러 배치를 같이 돌려서 자원경합을 일으키는 것보다는 스케줄링을 통해 분산시켜 작업하는게 낫다.
- 배치작업시 select 를 통한 insert 수행예
alter session enable parallel dml;
alter session set db_file_multiblock_read_count = 100;
insert /*+ parallel(a 4) */ into tab1 a
select /*+ parallel(b 4) */ * from tab2 b;
- Hard Parsing(dynamic)과 Soft Parsing(static) 개념을 이해하자. Library Cache에 있는 정보를 이용하는 Soft Parsing만 사용해도 10%정도 성능향상이 이뤄진다고 한다. 자바에서의 PreparedStatement나 php의 bind함수사용 등을 통해 기존 파싱 및 옵티마이징된 plan을 활용할 것.
Cursor_sharing 파라미터를 FORCE로 놓아 강제로 static으로 만들 수 있으나(상수값을 변수로 전환시켜 파싱) 경우에 따라 에러가 발생하는 경우가 있다고 한다. 그냥 EXACT를 쓰는게 나을듯.
- 대부분의 DBMS에서의 옵티마이져는 현재 CBO(Cost based Optimizer)방식으로 동작한다고 하고, 오라클에서도 RBO(Rule Based Optimizer)에 대한 지원을 중단한다고 발표했다고 한다. 9i이후에는 CBO를 쓰자.
- 실행계획을 봤을 때 인덱스를 타야될 상황인데 자꾸 hash join이나 sort merge로 빠질 때 조정
optimizer_index_cost_adj = 20 정도로 조정.(인덱스 사용시 cost)
optimizer_index_caching = 100 (index가 buffer cache에 있을 확률)
- 옵티마이져 모드 : First_Rows_n (OLTP성 온라인에 적합), ALL_ROWS(OLAP나 배치작업시 적합)
-- page 57까지 작성
전체적으로 지난주에 엔코아에서 교육받은 대용량 데이터베이스 솔루션 과정 내용과 겹치는 내용이 많아,
복습효과도 있고 이해도 잘 되었던 거 같다.
전반적으로 DB모델링에 있어서 중요한 내용들과 협업에서 주로 잘못 사용하는 내용들에 대해 다뤄주고 있어서 도움이 많이 됐다.
전반적으로 이해는 하면서 읽었는데 나중에 다시 한번 읽어볼 만한 책이다.
원래 책을 통해 습득하는 지식이란 본인이 가지고 있는 지식에 비례하여 얻게 되는 법이니까..
* 책에 나오는 주요내용들
1. PK나 인덱스 구성시 결합인덱스의 경우 분포도나 조회패턴을 고려하여 순서를 결정짓자
.
2. 이력 데이터 저장시에 용도에 따라 모델을 설계하자. 컬럼단위 추적여부, 대량여부, 컬럼수 대소 등
3. Unique Index보다는 PK를 사용하자. FK를 개발초기부터 사용하자.
4. 필요시 자기참조관계를 사용하고, 이때에 관계의 연결속성에 인덱스를 설정하자.
5. 용어사전과 도메인정의를 통해 데이터타입, 길이를 일치시켜 인덱스를 못타거나 하는 일을 없애자.
6. 논리모델링과 물리모델링은 구분해서 진행되어야 한다.
논리모델링은 업무를 정의하는 것이고, 물리모델링은 해당 DBMS의 특성과 성능을 고려한 설계가 되어야 한다.
7. 정규화와 반정규화의 의미를 제대로 알고, 실제에 적용하자.
대부분의 DBA가 정규화를 실제 제대로 활용하지 못하고 있다. 정규화가 제대로 되지 않았을 경우 데이터 무결성이 깨지는 사례를 인지하고 개발자를 설득할 수 있어야 한다.
8. 대량 테이블의 경우 수직(파티셔닝)/수평(테이블분리) 분할을 통한 성능향상을 도모하자.
import, export사용(imp, exp)
linux라는 사용자로 연결한다
SQL> connect linux/linux
연결되었습니다.
SQL> set pagesize 30
linux가 가진 테이블은 다음과 같다
SQL> select * from tab;
TNAME TABTYPE CLUSTERID
------------------------------ ------- ----------
S_CUSTOMER TABLE
S_DEPT TABLE
S_EMP TABLE
S_IMAGE TABLE
S_INVENTORY TABLE
S_ITEM TABLE
S_LONGTEXT TABLE
S_ORD TABLE
S_PRODUCT TABLE
S_REGION TABLE
S_TITLE TABLE
S_WAREHOUSE TABLE
TEST TABLE
13 개의 행이 선택되었습니다.
SQL> quit
Oracle8 Release 8.0.5.0.0 - Production
PL/SQL Release 8.0.5.0.0 - Production에서 분리되었습니다.
[linux@localhost db]$ ls -l /usr/local/Oracle
total 2
drwxr-xr-x 3 oracle dba 1024 Sep 22 13:23 app
drwxr-xr-x 3 oracle dba 1024 Sep 22 13:26 oradata
[linux@localhost db]$ ls -l /usr/local/Oracle/oradata
total 1
drwxr-xr-x 2 oracle dba 1024 Sep 22 13:34 kang
[linux@localhost db]$ ls -l /usr/local/Oracle/oradata/kang
total 131172
-rw-r----- 1 oracle dba 1435648 Oct 5 23:46 control01.ctl
-rw-r----- 1 oracle dba 1435648 Oct 5 23:46 control02.ctl
-rw-r----- 1 oracle dba 1435648 Oct 5 23:46 control03.ctl
-rw-r----- 1 oracle dba 15730688 Oct 5 23:44 rbs01.dbf
-rw-r----- 1 oracle dba 512512 Oct 5 23:41 redokang01.log
-rw-r----- 1 oracle dba 512512 Oct 5 23:42 redokang02.log
-rw-r----- 1 oracle dba 512512 Oct 5 23:44 redokang03.log
-rw-r----- 1 oracle dba 83888128 Oct 5 23:44 system01.dbf
-rw-r----- 1 oracle dba 1050624 Oct 5 23:42 temp01.dbf
-rw-r----- 1 oracle dba 26216448 Oct 5 23:42 tools01.dbf
-rw-r----- 1 oracle dba 1050624 Oct 5 23:42 users01.dbf
linux의 테이블을 export한다. OCP에서는 이 툴을 이용하는 백업을 논리적(logical) 백업이라고 한다
그냥 명령인자없이 주면 interactive하게 진행할 수 있다.
이 경우 direct백업(특별한 경우가 아닌 이상, 이 방법으로 하면 속도가 빨라진다.)이 불가능하다(디폴트로 N)
[linux@localhost db]$ exp linux/linux
Export: Release 8.0.5.0.0 - Production on 화 Oct 5 23:46:43 1999
(c) Copyright 1998 Oracle Corporation. All rights reserved.
연결할 곳: Oracle8 Release 8.0.5.0.0 - Production
PL/SQL Release 8.0.5.0.0 - Production
배열 인출 버퍼 크기 입력: 4096 >
엑스포트 파일: expdat.dmp > 그냥 엔터(디폴트 화일명은 expdat.dmp이다)
논리적 백업은 3가지가 있는데 아래에 있는 user, table과 아래에 나오지 않은 full백업이 있다
(full백업은 보통 dba만이 할 수 있다, 만일 백업만을 전담할 사용자를 둔다면, exp_full_database, imp_full_database 권한을 주면 된다.).
user는 사용자가 가진 스키마의 모든 것들을 export하는 것이고, table은 말 그대로 사용자의 지정된 table만을 export하는 것이다.
full은 전체 DB를 백업한다
(2)U(사용자), 또는 (3)T(테이블): (2)U > 여기서는 2을 선택했다
권한부여 엑스포트 (yes/no): yes > 그냥 엔터
테이블 데이터 엑스포트 (yes/no): yes > 그냥 엔터
확장 영역 압축 (yes/no): yes > 그냥 엔터
KO16KSC5601 문자 설정과 KO16KSC5601 NCHAR 문자 설정에서 엑스포트가 종료되었습니다
. LINUX 사용자를 위해 외래 함수 라이브러리 이름을 엑스포트합니다
. LINUX 사용자에 대한 개체 유형 정의를 엑스포트 합니다
LINUX의 개체를 엑스포트하려고 합니다 ...
. 데이터베이스 링크 엑스포트 중
. 순차 번호 엑스포트 중
. 클러스터 정의 엑스포트 중
. LINUX의 테이블을 엑스포트하려고 합니다 via 규정 경로...
. . 테이블 S_CUSTOMER(를)을 엑스포트 중 15 행이 엑스포트됨
. . 테이블 S_DEPT(를)을 엑스포트 중 12 행이 엑스포트됨
. . 테이블 S_EMP(를)을 엑스포트 중 25 행이 엑스포트됨
. . 테이블 S_IMAGE(를)을 엑스포트 중 19 행이 엑스포트됨
. . 테이블 S_INVENTORY(를)을 엑스포트 중 114 행이 엑스포트됨
. . 테이블 S_ITEM(를)을 엑스포트 중 62 행이 엑스포트됨
. . 테이블 S_LONGTEXT(를)을 엑스포트 중 33 행이 엑스포트됨
. . 테이블 S_ORD(를)을 엑스포트 중 16 행이 엑스포트됨
. . 테이블 S_PRODUCT(를)을 엑스포트 중 33 행이 엑스포트됨
. . 테이블 S_REGION(를)을 엑스포트 중 5 행이 엑스포트됨
. . 테이블 S_TITLE(를)을 엑스포트 중 8 행이 엑스포트됨
. . 테이블 S_WAREHOUSE(를)을 엑스포트 중 5 행이 엑스포트됨
. . 테이블 TEST(를)을 엑스포트 중 16 행이 엑스포트됨
. 동의어 엑스포트 중
. 뷰 엑스포트 중
. 저장 프로시저 엑스포트 중
. 참조 무결성 제약조건 엑스포트 중
. 트리거 엑스포트 중
. 이후 테이블 처리를 엑스포트 중
. 스냅샷 엑스포트 중
. 스냅샷 로그 엑스포트 중
. 작업 대기열을 엑스포트 중
. 리프레쉬 그룹과 자식 엑스포트 중
엑스포트가 경고 없이 정상적으로 종료되었습니다.
[linux@localhost db]$ ls -l
total 96
-rw-rw-r-- 1 linux linux 3340 Aug 15 22:30 JdbcTest.class
-rw-r--r-- 1 linux linux 3112 Aug 15 22:43 JdbcTest.java
drwxrwxr-x 2 linux linux 1024 Oct 1 01:10 db_ex
-rw-rw-r-- 1 linux linux 40960 Oct 5 23:46 expdat.dmp
-rw-r--r-- 1 linux linux 41841 Jun 30 11:59 summit.sql
-rw-rw-r-- 1 linux linux 4096 Oct 5 23:46 사용자관리-백업_리스토어.txt
kang이라는 사용자로 import를 수행한다.
[linux@localhost db]$ imp kang/kang
Import: Release 8.0.5.0.0 - Production on 화 Oct 5 23:48:1 1999
(c) Copyright 1998 Oracle Corporation. All rights reserved.
연결할곳: Oracle8 Release 8.0.5.0.0 - Production
PL/SQL Release 8.0.5.0.0 - Production
임포트 파일: expdat.dmp >
삽입 버퍼 크기를 입력하십시오 (최소치는 4096) 30720>
엑스포트 파일은 규정 경로를 거쳐 EXPORT:V08.00.05 에 의해 생성되었습니다
경고: 개체는 다른 사용자 LINUX(이)가 엑스포트한 것입니다.
임포트 파일의 내용만 표시합니다. (yes/no): no >
개체 존재로 인해 발생한 생성 오류는 무시합니다. (yes/no): no >
권한부여 임포트 (yes/no): yes >
테이블 데이터 임포트 (yes/no): yes >
엑스포트 파일 전체를 임포트합니다. (yes/no): no >
사용자명:linux 여기서 kang이라고 입력하지 않도록 주의한다
테이블(T) 또는 분할(T:P)명을 입력하십시오. 널 목록은 사용자의 모든 테이블을 의미합니다.
테이블(T) 또는 분할(T:P)명을 입력하거나 완료 시에는 .을 입력하십시오.
. LINUX 개체를 KANG(으)로 임포트하는 중입니다.
. . 테이블 "S_CUSTOMER"(를)을 임포트 중 15 행이 임포트되었습니다
. . 테이블 "S_DEPT"(를)을 임포트 중 12 행이 임포트되었습니다
. . 테이블 "S_EMP"(를)을 임포트 중 25 행이 임포트되었습니다
. . 테이블 "S_IMAGE"(를)을 임포트 중 19 행이 임포트되었습니다
. . 테이블 "S_INVENTORY"(를)을 임포트 중 114 행이 임포트되었습니다
. . 테이블 "S_ITEM"(를)을 임포트 중 62 행이 임포트되었습니다
. . 테이블 "S_LONGTEXT"(를)을 임포트 중 33 행이 임포트되었습니다
. . 테이블 "S_ORD"(를)을 임포트 중 16 행이 임포트되었습니다
. . 테이블 "S_PRODUCT"(를)을 임포트 중 33 행이 임포트되었습니다
. . 테이블 "S_REGION"(를)을 임포트 중 5 행이 임포트되었습니다
. . 테이블 "S_TITLE"(를)을 임포트 중 8 행이 임포트되었습니다
. . 테이블 "S_WAREHOUSE"(를)을 임포트 중 5 행이 임포트되었습니다
. . 테이블 "TEST"(를)을 임포트 중 16 행이 임포트되었습니다
사용 가능한 제약 조건에 관해서...
임포트가 경고 없이 정상적으로 종료되었습니다.
[linux@localhost db]$ sqlplus
SQL*Plus: Release 8.0.5.0.0 - Production on 화 Oct 5 23:48:37 1999
(c) Copyright 1998 Oracle Corporation. All rights reserved.
사용자명 입력: kang
암호 입력:
Connected to:
Oracle8 Release 8.0.5.0.0 - Production
PL/SQL Release 8.0.5.0.0 - Production
정말 import가 되었는지 확인하자
SQL> select * from tab;
TNAME TABTYPE CLUSTERID
------------------------------ ------- ----------
S_CUSTOMER TABLE
S_DEPT TABLE
S_EMP TABLE
S_IMAGE TABLE
S_INVENTORY TABLE
S_ITEM TABLE
S_LONGTEXT TABLE
S_ORD TABLE
S_PRODUCT TABLE
S_REGION TABLE
S_TITLE TABLE
S_WAREHOUSE TABLE
TEST TABLE
13 개의 행이 선택되었습니다.
다시 system으로 접속해서 kang이라는 사용자를 지운다.
이때 kang이라는 사용자의 schema에는 테이블이 존재하므로 cascade옵션을 사용해야 한다
SQL> connect system
암호 입력:
연결되었습니다.
SQL> drop user kang;
drop user kang
*
1행에 오류:
ORA-01922: 'KANG'(을)를 삭제하려면 CASCADE를 지정하여야 합니다
SQL> drop user kang cascade;
사용자가 삭제되었습니다.
추가: 2001-12-20
imp에서 show패러미터는 export파일의 내용을 보여주고, 실제 import는 수행하지 않는다.
보여주는 내용이 SQL문으로 되어 있으므로 어떤 경우, 유용하게 사용될 수 있다.
imp system/xxxxxx full=y show=y file=full_20011220.dmp log=exp_content.sql
복구과정
1. 가장 최근의 exp파일에서 full=y inctype=system으로 imp를 수행하여 data dictionary와 DB구조들을 재생성
(이때 적용될 exp파일은 complete, cumulative, incremental의 종류에 관계없이 가장 최근의 exp파일이다.)
2. 가장 최근의 complete exp파일에서 full=y inctype=restore로 import한다.
(나머지 exp파일들에 대해 full=y와 inctype=restore로 계속 수행한다)
3. 가장 최근의 complete exp파일 이후로 수행된 cumulative exp파일들을 처리
4. 가장 최근의 cumulative exp파일 이후로 수행된 incremental exp파일들을 처리
export시 DB를 restricted session에 두고,(alter system enable restricted session)
exp 패러미터중 consistent를 Y에 두면 최소한의 읽기일관성 보장한다.
단, consistent패러미터는 complete, cumulative에서만 사용할 수 있다.
consistent패러미터는 incremental에서는 사용될 수 없으므로, incremental수행시에는
DB를 restricted session에 두고 수행하는 것이 좋다.
-- 읽기일관성 문제 예
테이블 데이터가 먼저 export되고, index는 그 다음에 수행된다.
따라서, 테이블데이터에 있지 않으나, 인덱스에는 해당 데이터가 있을 수도 있다.
export수행중, 변경된 데이터는 export파일에 저장되지 않는다.
conventional Vs direct
디폴트는 conventional path를 이용해 export된다.
conventional path는 SQL을 SQL*PLUS에서 수행하는 것과 동일한 방식으로 실행된다.
direct패러미터는 디폴트가 N이므로 명시적으로 Y값으로 설정해야 한다.
direct는 데이터 정의와 몇몇 SQL을 skip하여 수행되므로 백업의 속도가 빠르다.
direct path를 사용할 때는 buffer패러미터가 사용되지 않는다.
full=y로 수행된 export파일은 import시 user,table,full 모두에 사용될 수 있다
출처 : http://www.superuser.co.kr, http://blog.empas.com/bkseo21/18491412
어제 저녁 9시에 있었던 매치다. 집에서 하면 애들때문에 제대로 못할 거 같아서 회사에 남아서 했다.
간만에 500점 문제가 비교적 쉬웠던거 같고, 900문제도 그리 어렵지 않은거 같아 풀었는데 900문제는 문제이해를 잘 못해서 system fail됐다.
500문제를 challenge time에 두명 걸어봤는데, 둘다 실패났다.
9부터 나누어지는 수를 계산하면 되는 문제인데, 정말 엉뚱하게 소스가 빙빙 둘려졌길래..분명히 틀릴 거야 하고 챌린지를 했건만.. 실패라나 ㅎㅎ;
역시 챌린지는 확실할 때만 해야 될 듯하고, 다른 사람 소스를 파악하는 연습을 많이 해야 겠다는 것을 느꼈다.
rating은 1점의 변경도 없이 그대로 고정이다;
250pt.
주어진 스트링에서 A, Z가 들어간 부분만 뒤집고, 다른 char는 원래 자리를 그대로 유지시켜 주는 encrypt 를 구현하는 문제.
string fixTheSpell(string s)
{
string s1;
vector< pair<int, char> > p;
REP(i, s.size())
{
if( s[i] != 'A' && s[i] != 'Z') p.pb(make_pair(i, s[i]));
else s1 += s[i];
}
reverse(ALL(s1));
REP(i, p.size())
{
s1 = s1.substr(0, p[i].first) + p[i].second + s1.substr(p[i].first);
}
return s1;
}
500pt.
주어진 수 N이 숫자 X의 각 자리값들의 곱으로 나타내 진다고 했을때, X가 될 수 있는 최소값을 구하는 문제.
9부터 2까지 나누어질 수 있는 수만큼 나눠주면서 개수를 세면 된다.
class ProductOfDigits
{
public:
int smallestNumber(int N)
{
vector<int> d(8, 0);
if(N == 1) return 1;
for(int i = 9; i>= 2; i--)
{
int pos = 9 - i;
int cnt = 0;
while( N % i == 0 )
{
cnt++;
N /= i;
}
d[pos] = cnt;
}
if( N != 1 ) return -1;
int ret = 0;
REP(i, d.size()) ret += d[i];
return ret;
}
};
SRM144 550pt.
: encrypt된 문자를 decrypt하는 문제. 문제는 어렵지 않았으나 예외처리 같은 부분에 시간이 좀 소요
class BinaryCode
{
public:
vector <string> decode(string m)
{
vector<string> ret;
int n = m.size();
vector<int> d;
REP(i, n) d.pb( m[i] - '0');
int inx = 0;
while(inx < 2)
{
vector<int> out;
bool is_fail = false;
for(int i=0; i<n; i++)
{
int p;
if( i == 0 ) p = inx;
else if( i == 1 ) p = d[i-1] - out[i-1];
else p = d[i-1] - out[i-1] - out[i-2];
if( p > 1 || p < 0 )
{
is_fail = true;
break;
}
else
{
out.pb(p);
}
}
inx++;
if( is_fail || ( n == 1 && out[n-1] != d[n-1]) || (n > 1 && out[n-1] + out[n-2] != d[n-1]))
ret.pb("NONE");
else
{
string s;
REP(i, out.size())
s.pb(out[i]+'0');
ret.pb(s);
}
}
return ret;
}
};
SRM145 500pt.
: 문제해석이 잘 안되던 문제.. 뭘 원하는건지 이해하기가 어려웠다..
단위 work의 시간이 주어졌고, 퍼센트가 정수로 떨어질 경우만 퍼센트가 올라가는 머신이 있을 때,
단위 work가 끌날 시점에 몇 퍼센트를 가리키고 있을까가 문제다.
결국 현재까지 초와 100간의 gcd(s,100)-1 을 묻는 문제였음..
class ExerciseMachine
{
public:
int getPercentages(string time)
{
REP(i, time.size())
if(time[i] == ':') time[i] = ' ';
istringstream in(time);
int h, m, s;
in >> h >> m >> s;
int sec = h*3600 + m * 60 + s;
int ret = 0;
for(int i =1; i < sec; i++)
{
for(int j=1; j < 100; j++)
{
if( j*sec == 100*i )
ret++;
}
}
return ret;
}
};
SRM146 500pt.
: 사이즈 (w, h)인 직사각형안에 들어갈 수 있는 사이즈(x, y) 사각형의 개수는 (w-x+1)*(h-y+1)개라는 부분을 파악하면 풀이는 쉽다.
class RectangularGrid
{
public:
long long countRectangles(int w, int h)
{
long long ret = 0;
for(int i=1; i <= max(w, h); i++)
{
for(int j=1; j <= max(w, h); j++)
{
if( i == j) continue;
int x = w - i + 1;
int y = h - j + 1;
if( x > 0 && y > 0 )
{
ret += x * y;
}
}
}
return ret;
}
};
SRM147 500pt.
class PeopleCircle
{
public:
string order(int m, int f, int K)
{
int n = m+f;
string ret(n, 'M');
int p = 0;
REP(i, f)
{
int cnt = K;
while(cnt > 1)
{
if(ret[p] == 'M') cnt--;
p = (p+1)%n;
}
while( ret[p] != 'M')
{
p = (p + 1)%n;
}
ret[p] = 'F';
if( i == f-1) return ret;
while( ret[p] != 'M')
{
p = (p + 1)%n;
}
}
return ret;
}
};
SRM148 600pt.
: 600점이라 어려울 줄 알았는데 생각보다 쉬운 문제..
class CeyKaps
{
public:
string decipher(string t, vector <string> switches)
{
REP(i, switches.size())
{
string s = switches[i];
REP(j, t.size())
{
if( t[j] == s[0])
{
t[j] = s[2];
}
else if( t[j] == s[2])
{
t[j] = s[0];
}
}
}
return t;
}
};
난이도가 쉬웠던 250은 다들 쉽게 푼거 같고.. medium문제가 이번엔 600점으로 조금 어려운 난이도로 나왔다.
600점 문제는 x좌표와 y좌표로 나누어서 생각해 볼 수 있는데, 최소점과 최대점을 모두 검사하게 되면 시간초과가 난다. 그래서, 전체 좌표의 평균에 -10과 +10 사이로 계산하면 되지 않을까 해서 풀어봤는데, Challenge에서 걸렸다. ㅎㅎ
DIV Summary 보니 600문제는 맞춘 사람이 몇명 없었고, 1000점은 맞춘 사람이 하나도 없는 평균이 꽤나 낮은 매치였다. 나는 600문제에서 Challenge가 두개 성공해서 rating이 좀 올랐다. ㅎ;
나중에 풀이를 보니 600문제는 이해도 잘못하고 있었다. 체커의 순서에 상관없이 i+1개의 체커가 같은 자리에 있을 수 있는 최소 이동수를 묻는 문제였다.
그리고, 풀이의 핵심은 최소 이동수가 되는 자리는 체커가 놓여진 자리중 하나(x,y축 별도로)라는 것이다.
이것을 증명하기 위해 만약 체커가 놓여지지 않은 자리 k 가 최소 이동수의 자리라고 가정해 보면
1) 만약 k의 왼쪽과 오른쪽 체커의 개수가 같다면 k와 가장 가까운 체커까지의 이동수와 k까지의 이동수는 같다.
2) 만약 k의 왼쪽과 오른쪽 체커의 개수가 다르다면 가장가까운 체커중 체커가 많은 방향의 체커까지의 이동수가 k까지의 이동수보다 작다.
3) 따라서, 최소 이동수가 되는 자리는 체커들 중 하나다.
- 250pt.
class TheSimpleGame
{
public:
int count(int n, vector <int> x, vector <int> y)
{
int ret = 0;
REP(i, x.size())
{
ret += min( abs(x[i] -1) , abs(x[i] - n) );
ret += min( abs(y[i] -1) , abs(y[i] - n) );
}
return ret;
}
};
- 600pt.
class TheTower
{
public:
vector <int> count(vector <int> x, vector <int> y)
{
int n = x.size();
vector<int> ret(n, INT_MAX);
REP(i, n)
{
REP(j, n)
{
vector<int> v;
REP(k, n) v.pb( abs(x[k]-x[i]) + abs(y[k]-y[j]) );
sort(ALL(v));
int sum = 0;
REP(k, n)
{
sum += v[k];
ret[k] = min(ret[k], sum);
}
}
}
return ret;
}
};
300명 정도 참석한다고 했는데, 실제로 사람들이 더 많이 온 거 같았다.
개발자 행사라고 했는데, 실제로 개발자가 아닌 듯해 보이는 분들도 많다는 느낌을 받았다.
실제로 "개발자분들 손들어보세요." 하니까 반정도만 손을 들던데.. 위젯이 기획이나 마케팅 담당자들도 관심이 많은 이슈인 듯 하다.
이번 행사는 다음과 구글에서 위젯,가젯을 홍보하고 국내 개발자들에게 위젯 개발을 활성화 하고자 하는 의도인 거 같았다.
다음에서 최근 출시한 위젯뱅크에 대해 다음측에서의 관심도가 꽤 높다는 것을 알 수 있었고, 구글코리아에서도 iGoogle을 국내에 활성화 시키는데 관심이 많다는 인상을 받았다.
컨퍼런스 내용은 크게 다음과 구글의 현황과 관심사, 앞으로 비전 등에 대해 사업담당 하시는 분들께서 안내하는 시간과, 위젯 CP라고 할 수 있는 wezet, 위자드에서 보는 위젯 시장 및 전망 등에 대한 얘기들, 개발자가 개발시에 미리 알면 도움이 될만한 내용 안내 등으로 구성되어있었다.
내용을 들으면서 느낀 것은 위젯,가젯이 개인화 홈페이지를 구축하는데 한 축을 담당하지 않을까 하는 생각이 들었다. 국내 포털들에서도 고민하는게 각 사용자에 맞는 컨텐츠를 보여주는 것을 고민하고 있을텐데, 그것을 사용자가 선택한 위젯, 가젯으로 풀 수도 있을 것이고, google에서 iGoogle을 통해 접근하고 있는 듯 하다.
최근 위젯,가젯의 종류는 주로 각종 OpenAPI를 이용해서 정보를 제공해 주는 위젯과 광고 위젯, 엔터테인 위젯 의 세분류로 나눌 수 있을 거 같다.
광고의 경우 과거 국내에서 한때 떳다가 실패한 골드뱅크의 사례를 연상시키는 것과 같은 배너광고가 조금 더 이쁘고, 업그레이드 되서 재등장한 느낌을 받았다. 에드센스와 같이 성공모델이 나올지는 두고봐야 할 듯 하고.. 개인적으로는 회의적인 느낌이다.
iGoogle상에서 sandbox 를 활성화시키면, 여러 탭을 추가해서 탭을 꾸밀 수가 있는데, 꽤 인상적이었다. 조만간 iGoogle에 디폴트로 적용될 예정이라고 한다.
현재도 google.co.kr/ig/sandbox에서 동의하면 sandbox를 활성화 시킬 수가 있다.
iGoogle에 게시된 가젯이나 그 외 각종 CP(http://www.labpixies.com/ 등)에서 제공한 가젯들을 적용시켜 보고, 본인에게 도움이 될만한 것들로 꾸미면 iGoogle이 괜찮은 본인의 홈페이지겸 작업장이 될 수 있을 것이라고 본다.
다른사람이 만든 가젯들을 써보면서 필요한 가젯에 대한 아이디어가 떠오르면 경진대회도 한번 참석해 봐야겠음..
* 유용한 링크들
개발자 포럼 : http://igoogledeveloper.blogspot.com/
ps. 강연도중 서명덕 기자님이 앞에서 계속 사진을 찍는 모습을 봤는데.. 잠시 밖으로 나가시더니 그 와중에 블로그에 글을 올리셨나 보다.
중간에 강사가 본인의 RSS리더 가젯을 띄웠는데.. 서명덕님이 컨퍼런스 내용 올린 게시물이 제일 위에 나오는 우연이 발생해서 좀 웃겼다..
정말 실시간 블로그로 조금이라도 빨리 게시물을 올리려고 노력하는 서명덕님의 프로정신이 엿보였고.. 개인 블로그가 언론사 못지않은 파워를 발휘하는 시대가 왔구나 하는 걸 다시 한번 느낄 수 있었다.
SRM407 DIV2
: 250점짜리 치고 좀 복잡한 문제.. 오랜만에 해서 오래걸리나 했는데.. 매치 결과에도 많이들 못푼 문제다.
나중에 다시 한번 풀어볼 것.
class SpiralWalking
{
public:
int totalPoints(vector <string> m)
{
int idx[] = { 0, 1, 0, -1 };
int idy[] = { 1, 0, -1, 0 };
vector< vector<int> > d;
REP(i, m.size())
{
vector<int> sub;
REP(j, m[0].size())
{
sub.pb(m[i][j]-'0');
}
d.pb(sub);
}
int i = 0, j = 0, index = 0;
int sum = 0;
int w = m[0].size();
int h = m.size();
while(1)
{
if( i+idx[index] < 0 || i+idx[index] >= h || j+idy[index] < 0 || j+idy[index] >= w || d[i+idx[index]][j+idy[index]] == -1)
{
index = (index+1)%4;
if(d[i+idx[index]][j+idy[index]] == -1)
{
sum += d[i][j];
break;
}
i += idx[index];
j += idy[index];
}
else
{
sum += d[i][j];
d[i][j] = -1;
i += idx[index];
j += idy[index];
}
}
return sum;
}
};
SRM408 DIV2
class TournamentJudging
{
public:
int getPoints(vector <int> r, vector <int> c)
{
int ret = 0;
REP(i, r.size())
{
double a = r[i];
double b = c[i];
int s = a / b + 0.5;
ret += s;
}
return ret;
}
};
SRM409 DIV2
class Stick
{
public:
int pieces(int x)
{
int h = 64;
bool down = true;
int index = 64;
vector<int> d;
if( x == 64) return 1;
while(1)
{
if( down) index -= h/2;
else index += h/2;
h /= 2;
if( index <= x ) d.pb(index);
if( index == x ) return d.size();
else if(index > x ) down = true;
else if(index < x ) down = false;
}
}
};
컴퓨터 게임에 푹 빠졌던 학생이 공부를 시작하면서, 게임에서 세웠던 전략을 공부에 응용하면서 성공(?)했다는 책 요약을 보고 읽어봤다.
나날이 사회적으로 문제가 되고 있는 게임중독 문제에 있어 앞으로 자식들 교육에도 참고가 될 거 같고.. 나도 한때 게임에 빠졌다가 그것을 어떻게 다른 분야에 적용할 수 있을까 고민하던 차라 나름 재밌게 읽었다.
책 내용은 주로 저자가 어릴때 게임에 얼마나 중독되어 있었느냐와 그 후에 치열하게 공부를 하고, 시련이 닥칠때 마다 게임에서 그랬던 것처럼 포기하지 않고, 시련을 즐기면서 레벨업을 해 나가는 과정을 그리고 있다.
게임에 빠지면 깊이 빠진다는 점이나, 게임을 할 때 쓸데없는 일을 안하고, 전략적으로 접근한다는 점에서 나랑 비슷하다는 생각을 해봤다.. 수학을 좋아한다는 점도 그렇고, 고등학교때 성적이 갑자기 오른 얘기도 비슷한 거 같았다.
그런데, 미국으로 교환학생을 가면서 어려움을 이겨내고, 그 사회에 적응해 나갔던 모습은 나에게 좀더 치열하게 살지 못한데 대한 반성을 안겨줬다.
다른 책에서도 느끼는 것이지만, 자기 생각을 뚜렷이 가지고 성공하는 사람들에게는 잘못하더라도 묵묵히 지켜봐주고 믿어주는 부모가 옆에 있었던거 같다. 저자의 부모님도 게임에만 빠져 몇일 밤낮을 세우는 아들에게 그렇게 잔소리도 많이 하지 않고, 지켜봐 주었다고 한다.
잔소리를 하고 게임을 못하게 했다면 저자와 같이 스스로 게임을 그만두고, 게임에서 배운 전략을 공부나 다른 일에 적용하면서 극복할 수 있도록 만들진 못했으리라..
인생을 살아가면서 겪게될 많은 어려움과 벽들을 게임에서의 레벨업과정이라고 생각하고 꾸준히 하나하나 해결해 나가면 나중에 웃으면서 서있을 때가 오지 않을까 ㅎㅎ
나중에 전세계를 돌면서 힘든 사람들을 돕고 사는 복지가가 되고 싶다는 저자의 글을 보면서, 나도 나중에 돈을 많이 벌어서 Topcoder같은 곳에 후원이나 많이 하고 싶다는 생각을 해봤다.
지난주 목요일에 참석했던 웹앱스콘 2008에서도 위젯이 한 꼭지를 차지할 정도로 비중이 커진 것을 느낄 수 있었다.
내가 그동안 알고 있던 위젯은 주로 웹관련 기능을 브라우저 밖으로 빼낸 윈도우 비스타의 가젯이나 야후 위젯과 같이 컴퓨터 바탕화면을 차지하고 있는 도구였다. 이러한 위젯은 PC 메모리를 불필요하게 점유하고, 특별히 필요성을 느끼지 못해서 사용이 확산되지 못했다고 본다.
그런데, 최근에 위젯이 뜨거워지는 이슈로는 아이폰, 구글 안드로이드 등 모바일쪽에서 불고있는 위젯 열풍과 최근 다음에서 위젯뱅크를 오픈하는 등 블로그나 카페에 붙이는 위젯이 인기를 끌고 있기 때문인거 같다.
모바일쪽에서는 각종 단말기 업체부터 구글, 오페라 등 여러 업체에서 저마다의 API와 개발대회를 유치하면서 표준을 선점하기 위해 분주한 것 같다. 얼마후면 PC에서 IBM이 표준을 장악한 것과 같이 표준이 나올 것이다.
그때까지는 오페라의 조만영님의 세미나 말씀과 같이 여기저기 API 파악한다고 시간보낼거 없이 웹표준이나 더 공부하는게 나을지도 모른다.
Daum Google 위젯=가젯 개발 컨퍼런스 경진대회
그 와중에 Daum과 Google에서 위젯 경진대회를 한다고 하고, 이번주 목요일 저녁에는 간단한 컨퍼런스 형식으로 대회 설명을 해주는 거 같다. 이참에 위젯이나 한번 만들어 볼까 싶어서 참가신청은 했는데.. 목요일날 컨퍼런스 내용 한번 들어보고, 결정해야 겠다.
250 easy. 문안한 문제.
class MultiNumber
{
public:
string toStr(int num)
{
stringstream s;
s << num;
return s.str();
}
string check(int num)
{
string s = toStr(num);
for(int i = 1; i<s.size(); i++)
{
int a = 1, b = 1;
REP(j, i)
a *= s[j] - '0';
for(int j=i; j < s.size(); j++)
b *= s[j] - '0';
if( a == b ) return "YES";
}
return "NO";
}
};
500 mid
: 확률에 대한 문제. 확률 개념이 가물가물해서 네이버에서 간단한 이론 조금 둘러본 후에 풀었다.
class PrimeSoccer
{
public:
bool is_prime(int n)
{
if( n == 2 ) return true;
if( n < 2 || n % 2 == 0 ) return false;
for(int i = 3; i*i <= n; i++)
{
if( n != i && n % i == 0) return false;
}
return true;
}
double getProbability(int skillOfTeamA, int skillOfTeamB)
{
double sum_A = 0, sum_B = 0;
double sA = skillOfTeamA;
double sB = skillOfTeamB;
//1~18까지 넣을 골수가 prime이 아닌 경우
for(int i = 0; i <= 18; i++)
{
if( is_prime(i) ) continue;
double pA = 1, pB = 1;
for(int j=0; j < i; j++)
{
pA *= (sA/100.0);
pB *= (sB/100.0);
}
for(int j=i; j < 18; j++)
{
pA *= ((100-sA)/100.0);
pB *= ((100-sB)/100.0);
}
for(int j= 18; j>18-i; j--)
{
pA *= j;
pB *= j;
}
for(int j= 2; j<= i; j++)
{
pA /= j;
pB /= j;
}
sum_A += pA;
sum_B += pB;
}
return 1 - sum_A * sum_B;
}
};
: 오랜만에 topcoder를 해서 그런가 20분이나 걸렸다.. 꾸준히 연습을 해야겠음.
그냥 string을 sorting해서 eis인지 확인해도 된다.
class ReadingBooks
{
public:
int countBooks(vector <string> r)
{
int ret = 0;
int n = r.size();
for(int i=2; i< n; i++)
{
string s = r[i-2].substr(0,1) + r[i-1].substr(0,1) + r[i].substr(0, 1);
if( count(ALL(s), 'i') == 1 && count(ALL(s), 's') == 1 && count(ALL(s), 'e') == 1)
{
i += 2;
ret++;
}
}
return ret;
}
};
SRM405 DIV2
class FallingFactorialPower
{
public:
double compute(int n, int k)
{
double ret = 1;
if( k > 0)
{
REP(i, k)
ret *= (n - i);
}
else if( k < 0)
{
k = -k;
REP(i, k)
ret = ret / ( n+1+i);
}
return ret;
}
};
즉, 16*24개의 경우에 대해 각각 6가지 유형일때 최소로 싸는 사각형을 구했다.
6번째 유형에서 겹치는 부분 처리가 안되서 몇번 틀림..
개인적으로 next_permutation 함수를 처음 써봤는데 상당히 쓸만한듯..
vector< pair<int, pair<int, int> > > ret;
bool find_cycle(int cycle[], int order)
{
int i = 0;
while(order > 0)
{
cycle[i] = order % 2;
order /= 2;
i++;
}
return true;
}
bool check_value(int a, int b, int &cur, int &x, int&y)
{
if( cur >= a*b)
{
cur = a*b;
x = min(a, b);
y = max(a, b);
ret.pb(make_pair(cur, make_pair(x, y)));
}
return true;
}
int main() {
ofstream fout ("packrec.out");
ifstream fin ("packrec.in");
vector< vector<int> > d;
int a, b;
REP(i, 4)
{
fin >> a >> b;
VI sub;
sub.pb(a);
sub.pb(b);
d.pb(sub);
}
int order = 0;
int cycle[4] = {0,0,0,0};
int cur = INT_MAX, x = 0, y = 0;
while(order < 16)
{
find_cycle(cycle, order);
order++;
int width[4];
int height[4];
REP(i, 4)
{
width[i] = d[i][cycle[i]];
height[i] = d[i][1-cycle[i]];
}
int a = 0, b = 0;
int step[4] = {0, 1, 2, 3};
do
{
//type 1
a = width[step[0]] + width[step[1]] + width[step[2]] + width[step[3]];
b = max( max(height[step[0]], height[step[1]]), max(height[step[2]], height[step[3]]));
check_value(a, b, cur, x, y);
//type 2
a = width[step[1]] + width[step[2]] + width[step[3]];
a = max(a, width[step[0]]);
b = max(height[step[1]], max(height[step[2]], height[step[3]]));
b += height[step[0]];
check_value(a, b, cur, x, y);
//type3
a = max(width[step[0]], width[step[2]] + width[step[3]]);
a += width[step[1]];
b = max(height[step[2]], height[step[3]]) + height[step[0]];
b = max( b, height[step[1]]);
check_value(a, b, cur, x, y);
//type 4 & 5
a = width[step[0]] + width[step[3]];
a += max( width[step[1]], width[step[2]]);
b = max(height[step[0]], height[step[3]]);
b = max( b, height[step[1]] + height[step[2]]);
check_value(a, b, cur, x, y);
//type6
a = max( width[step[0]] + width[step[1]], width[step[2]] + width[step[3]]);
b = max( height[step[0]] + height[step[3]], height[step[1]] + height[step[2]]);
if( a < width[step[0]] + width[step[2]] && b < height[step[0]] + height[step[2]])
a = width[step[0]] + width[step[2]];
if( a < width[step[1]] + width[step[3]] && b < height[step[1]] + height[step[3]])
a = width[step[1]] + width[step[3]];
check_value(a, b, cur, x, y);
}while( next_permutation(step, step+4));
}
fout << cur << endl;
sort(ALL(ret));
ret.erase(unique(ALL(ret)), ret.end());
for(int i=0; i < ret.size() && ret[i].first == cur ; i++)
fout << ret[i].second.first << " " << ret[i].second.second << endl;
return 0;
}
[USACO] 1.3 Prime Cryptarithm(Winning Solutions)

주어진 n이 10이기 때문에 O(n^5)이어도 시한제약에 걸리지 않았다. n이 충분히 작다면 단순하게 푸는 것도 이기는 방법이 될 수 있다.
bool check_ok(int x, vector<int> &d)
{
while(x)
{
int a = x % 10;
if( find(ALL(d), a) == d.end() ) return false;
x /= 10;
}
return true;
}
int main() {
ofstream fout ("crypt1.out");
ifstream fin ("crypt1.in");
int n;
fin >> n;
vector<int> d;
REP(i, n)
{
int a;
fin >> a;
d.pb(a);
}
int f1 = 0, f2 = 0, ret = 0;
REP(i, d.size())
{
REP(j, d.size())
{
REP(k, d.size())
{
REP(l, d.size())
{
REP(m, d.size())
{
f1 = d[i] + 10 * d[j] + 100*d[k];
f2 = d[l] + 10 * d[m];
int p1 = f1 * (f2%10);
int p2 = f1 * (f2/10);
int p3 = p1 + 10*p2;
if( (p1/1000 == 0) && check_ok(p1, d) && (p2/1000 == 0) && check_ok(p2, d) && (p3/10000 == 0) && check_ok(p3, d))
ret++;
}
}
}
}
}
fout << ret << endl;
return 0;
}
처음 시도한 방법은 n까지 돌면서, 2000부터 역으로 돌면서 펠린드롬인 경우 크기를 리턴하게 하면서,
나머지 경우에 대해 2000부터 현재 구한 값까지 펠린드롬인지 계산했다.
이 방법으로 1~7번 입력까지는 만족했으나, 계속 8번 입력이 1.5초를 초과했다.
결국 수정한 알고리즘은 n까지 돌면서, n을 중심으로 한 최대 펠린드롬의 크기를 구하는 방법.
앞의 방법이 n* 2000에 대해 펠린드롬을 계산하는데 반해 뒤의 방법은 n에 대해 펠린드롬을 계산한다.
즉, O(n*2000*펠린드롬계산)과 O(n*2*펠린드롬계산)의 차이로 볼 수 있겠다..
int check_pal(char d[], int start, int end, int n)
{
int ret = 0;
while(start >= 0 && end - start <= 2000 && end < n)
{
if( !isalpha(d[start]) ) start--;
else if( !isalpha(d[end]) ) end++;
else if( tolower(d[start]) != tolower(d[end]) ) return ret;
else
{
if( start == end) ret++;
else ret += 2;
start--;
end++;
}
}
return ret;
}
int main() {
ofstream fout ("calfflac.out");
ifstream fin ("calfflac.in");
char data[20000];
char c;
int n = 0;
while(fin.good())
{
c = fin.get();
data[n] = c;
n++;
}
int ret = 0, sum = 0;
REP(i, n)
{
int start = i;
int end = i;
int m1 = check_pal(data, start, end, n);
if( sum < m1 )
{
sum = m1;
ret = i;
}
end = i+1;
int m2 = check_pal(data, start, end, n);
if( sum < m2)
{
sum = m2;
ret = i;
}
}
fout << sum << endl;
int cnt = sum / 2;
int start, end;
int ret2 = ret;
if( sum % 2 == 1) ret2--;
int count = cnt;
for( int i= ret2; i >= 0; i--)
{
if( isalpha(data[i]) ) count--;
if(count == 0)
{
start = i;
break;
}
}
count = cnt;
for( int i= ret+1; i < n; i++)
{
if( isalpha(data[i]) ) count--;
if(count == 0)
{
end = i;
break;
}
}
for(int i= start; i <= end; i++)
fout << data[i];
fout << endl;
return 0;
}
임백준씨가 그동안(대략 2004~2007년) 마이크로소프트웨어나 경영과 컴퓨터에 기고한 칼럼들을 모은 책이다.
그리고, 지금 시점(2008년 5월경?)에서의 칼럼에 대한 소감들을 적어 두었다.
나는 그동안 해당 잡지를 통해 임백준씨의 글들을 못봤었기 때문에, 개인적으로 아주 좋았다.
최근 임백준씨의 책들을 뒤늦게 읽으면서, 그가 그동안 쓴 칼럼들을 구해서 읽고 싶었는데 이렇게 칼럼들을 모아놓은 책을 출판하다니 ㅎㅎ
책에서 보는 내용들은 그동안 그의 책들(행복한 프로그래밍, 뉴욕의 프로그래머, 누워서 읽는 알고리즘 등)에서 다뤄왔던 얘기들과 비슷한 얘기들도 있고, 그 외에 여러 다양한 주제에 대해 다루고 있다.
그 중에서 인상깊었던 칼럼은 '리펑토링'에 대한 내용과 '유닛테스트'에 대해 다룬 내용정도였다.
리펑토링에 있어서는 내가 그동안 코딩하던 방법들이 얼마나 엔트로피를 높히고 있었던가를 반성할 수 있었고, 유닛테스트에 대한 내용에서는 Topcoder를 하면서, 매번 테스트를 돌려보는게 유닛테스트와 비슷하지 않나 하는 생각과 함께 실전에서도 유닛테스트를 도입해봐야 겠다는 생각을 했다.
아, 그리고 알고리즘 공부와 함께 멀티쓰레드에 대한 깊은 연습과 디자인패턴을 위시한 설계에 대한 연습, 디버깅에 대한 연습이 수반되어야 겠다는 것을 느꼈다.
책을 읽는 내내 몇년전 칼럼내용을 읽으며, 내가 그동안 얼마나 시대에 뒤쳐져서 살아왔나를 반성하며 시대의 중요 흐름들을 느낄 수 있었고, 칼럼뒤의 Comment에서는 현재 시점에서 저자의 변화된 시각과 함께 나름의 내 생각을 정립해 볼 수 있는 시간이었다.
책에서 못다룬 내용들은 pdf로 웹에 올려두어져 있는데, 출력해서 계속 읽어봐야 겠다.
PDF를 먼저 읽어보고 싶으신 분은 아래에서 다운이 가능하다. 별도 인증키없이 웹에 올려진 걸 보면, 누구나 받아가서 보도록 해 놓은 듯..
www.hanb.co.kr/itessay/baekjunlim
2008_한국의_사회지표_보도자료_1.pdf
Prev
Rss Feed