블랙박스 접근법
프로젝트 실패하는 가장 일반적인 이유 중 하나는 데이터베이스에 대한 실질적인 이해 부족 때문
- 사용하고 있는 기본 툴(데이터베이스)에 대한 기초지식 부족으로 블랙박스처럼 다루게 됨
- 막연한 두려움 : 데이터베이스, SQL, 트랜잭션, 무결성은 "어렵다"
- (필자는) 데이터베이스 보다 자바/C 개념을 배우는 것이 훨씬 더 어려움
데이터베이스 애플리케이션을 구축하고 있다면, 가장 중요한 부분은 데이터베이스임을 인정하고 집중해야 함
하지만 일반적인 개발 시나리오는
- 개발자는 GUI 툴이나 자바와 같이 화면단을 구축하는 데 사용하는 언어를 잘 다룸
- 개발자는 오라클 경험/교육이 전무하고, "데이터베이스 독립적" 강요 받음 (데이터베이스가 어떤 것인지 충분히 알지 못하는데)
- 멋진 화면을 개발 했으나, 성능/무결성/장애 문제에 직면
이런일도...
- 개발은 오라클 7.3 인데, SQL Reference 는 6.0 것을 참조 (트레이스, 튜닝 및 수백가지 추가된 기능들 무시...)
- 개발자는 데이터베이스를 잘 다루지 못해도 된다는 태도는 여전하다 (데이터베이스 공부 하는데 시간을 들이면 안되고, 할 필요도 없다는 주장도 있다)
- 오라클이 가장 확장성이 좋은 데이터베이스라고 해도, 잘못 이용하면 재앙이 될 수 있다.
- 호두를 호두까기로 망치처럼 내려칠것인가? (가루가 된 호두를 먹거나 손가락을 다치는 등 옳지 않다)
Instrumenting code
- 버그 추적, 성능 모니터링, 레벨들을 측정하기 위해 프로그램 안에 심어놓은 프로그램
- 오라클 데이터베이스는 조금은 무겁게 Instrumenting code가 내부에 담겨 있으며 손쉽게 활용 가능
문제투성이 프로젝트 사례
- 많은 성능 이슈에 직면, 시스템은 많은 트랜잭션을 직렬화(Serializing) 하고 있음 (마치 줄을 서는 것 처럼)
- 3-tier 구조 : JSP - EJB <데이터베이스 독립적 형태로 SQL 수행> - 데이터베이스
- Instrumenting code 부재 등으로 진단이 어려움
- 오라클의 V$ 테이블 진단으로 비트맵 인덱스 확인 (프로세스 플래그 컬럼 : 값<Y,N> / low-cardinality)
- 비트맵 인덱스는 한 개의 키(key entry)가 수백 개 이상의 많은 로우를 가르키며, 변경시 많은 로우가 동시에 락(Lock) 되므로 동시성이 떨어짐
autonomus 트랜잭션
- 세션 내부에 이미 만들어져 있는 트랜잭션과는 분리되고 구별된 서브트랜잭션(subtransaction)을 시작한다.
- 서브트랜잭션은 아주 다른 세션에서 실행된 것 처럼 동작 하며, 부모 트랜잭션에 의해 블로킹될 수 있음, 부모 트랜잭션은 거의 중단됨
- 오류 로깅 메커니즘(error-logging mechanism)으로 사용 가능하며 그 외는 잘못된 것으로 볼 수 있음
- autonomus 트랜잭션은 개발자들에게 공개되서는 안되는 기능이라 믿음 (부정확하게 사용시 데이터 무결성을 해침)
문제투성이 프로젝트 문제점1 데모
SQL> drop table t;
Table dropped.
SQL>
SQL>
SQL> create table t
2 ( processed_flag varchar2(1)
3 );
Table created.
SQL> create bitmap index t_idx on t(processed_flag);
Index created.
SQL> insert into t values ( 'N' );
1 row created.
SQL> declare
2 pragma autonomous_transaction;
3 begin
4 insert into t values ( 'N' );
5 commit;
6 end;
7 /
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 4
- 데드락(deadlock) 발생, 두개 세션 이었다면 두번째 입력은 단지 블로킹 되었음 (직렬화 이슈)
- 오라클 내 큐(queue) 관련 내부 기능(AQ)이 있는데, 별도의 프로그램 코드로 직접 구현 함
- 해결책
- 프로세스 처리 여부 플래그 컬럼에 B*Tree 인덱스 생성
- 대부분의 값인 'Y' 를 제외하고 관심있는 값인 'N' 에 대해서만 인덱스 생성 : FBI / DECODE(PROCESSED_FLAG, 'N', 'N')
문제투성이 프로젝트 문제점2 데모
- PROCESSED_FLAG = 'N' 인 데이터를 큐에서 꺼내는 처리를 직렬로 수행
-- 한 번에 한 세션만 사용 가능(10g)
SELECT * FROM QUEUE_TABLE WHERE DECODE(PROCESSED_FLAG, 'N', 'N') = 'N' FOR UPDATE;
SQL> drop table t purge;
Table dropped.
SQL> create table t
2 ( id number primary key,
3 processed_flag varchar2(1),
4 payload varchar2(20)
5 );
Table created.
SQL> create index
2 t_idx on
3 t( decode( processed_flag, 'N', 'N' ) );
Index created.
SQL> insert into t
2 select r,
3 case when mod(r,2) = 0 then 'N' else 'Y' end,
4 'payload ' || r
5 from (select level r
6 from dual
7 connect by level <= 5)
8 /
5 rows created.
SQL> select * from t;
ID P PAYLOAD
---------- - --------------------
1 Y payload 1
2 N payload 2
3 Y payload 3
4 N payload 4
5 Y payload 5
-- 해결책 타입1
SQL> create or replace
2 function get_first_unlocked_row
3 return t%rowtype
4 as
5 resource_busy exception;
6 pragma exception_init( resource_busy, -54 );
7 l_rec t%rowtype;
8 begin
9 for x in ( select rowid rid
10 from t
11 where decode(processed_flag,'N','N') = 'N')
12 loop
13 begin
14 select * into l_rec
15 from t
16 where rowid = x.rid and processed_flag='N'
17 for update nowait;
18 return l_rec;
19 exception
20 when resource_busy then null;
21 end;
22 end loop;
23 return null;
24 end;
25 /
Function created.
SQL> set serveroutput on
declare
l_rec t%rowtype;
begin
l_rec := get_first_unlocked_row;
dbms_output.put_line( 'I got row ' || l_rec.id || ', ' || l_rec.payload );
end;
/SQL> 2 3 4 5 6 7
I got row 2, payload 2
PL/SQL procedure successfully completed.
SQL> declare
2 pragma autonomous_transaction;
3 l_rec t%rowtype;
4 begin
5 l_rec := get_first_unlocked_row;
6 dbms_output.put_line( 'I got row ' || l_rec.id || ', ' || l_rec.payload );
7 commit;
8 end;
9 /
I got row 4, payload 4
PL/SQL procedure successfully completed.
-- 해결책 타입2 : SKIP LOCKED (11g)
SQL> declare
2 l_rec t%rowtype;
3 cursor c
4 is
5 select *
6 from t
7 where decode(processed_flag,'N','N') = 'N'
8 FOR UPDATE
9 SKIP LOCKED;
10 begin
11 open c;
12 fetch c into l_rec;
13 if ( c%found )
14 then
15 dbms_output.put_line( 'I got row ' || l_rec.id || ', ' || l_rec.payload );
16 end if;
17 close c;
18 end;
19 /
I got row 2, payload 2
PL/SQL procedure successfully completed.
SQL> declare
2 pragma autonomous_transaction;
3 l_rec t%rowtype;
4 cursor c
5 is
6 select *
7 from t
8 where decode(processed_flag,'N','N') = 'N'
9 FOR UPDATE
10 SKIP LOCKED;
11 begin
12 open c;
13 fetch c into l_rec;
14 if ( c%found )
15 then
16 dbms_output.put_line( 'I got row ' || l_rec.id || ', ' || l_rec.payload );
17 end if;
18 close c;
19 commit;
20 end;
21 /
I got row 4, payload 4
PL/SQL procedure successfully completed.
위의 해결책 두가지는 직렬화 문제를 해결 하는데 도움이 되나, 오라클에서 제공하는 Advanced Queuing 을 사용 검토 필요
- 일반적으로 데이터베이스가 애플리케이션의 초석이 되어야 함
- 블랙박스가 망가지면 우리는 "왜 잘 작동하지 않지?" 라며 궁금해 하는 일만 할 수 있을 뿐이다.
- 데이터베이스를 블랙박스 다루듯 하지 말고, 어떻게 일하는지, 어떤 일을 할 수 있는지 알고 모든 능력을 사용하자