Sentinel 방식은 말 그대로 보초로서 Redis서버(Master, Replica)에 대한 모니터링을 수행하고 장애가 발생했을 때 Failover를 수행함으로서 가용성을 보장하는 구성이다
Sentinel의 역할
모니터링/장애 조치 1) Redis서버에 지속적으로 Ping을 보내어 Redis서버가 alive 상태인지 확인 2) Redis서버에 장애가 발생하는 경우 Sentinel이 이를 감지하고 다른 센티넬들에게 이를 공유하고 정족수(quorum)이상의 Sentinel들이 down으로 판단한 경우 실질적 down으로 판단 3) 리더 Sentinel이 선출되고 리더 Sentinel에 의해 장애 Redis서버에 대해 Failover 수행(단, Master에 대해서만 Failover 수행하며 Replica에 대해서는 Failover 수행하지 않음)
구성 정보 제공 Sentinel은 Redis 노드를 감시하고 마스터 노드에 대한 정보를 제공하는 역할을 한다 Sentinel로 구성된 Redis르 Client가 연결할 때는 Sentinel을 통해 구성 정보를 받아야 한다(Failover가 발생했을 때 Master가 변경되기 때문)
Sentinel구성은 Sentinel이 장애가 발생하여 quorum(정족수)를 미달하는 경우 Failover가 정상적으로 이루어지지 않을 수 있기 때문에 Sentinel에 대한 모니터링이 중요하다
2) Cluster
Cluster 방식은 확장성 및 고가용성을 갖춘 방식으로 데이터를 분산(Sharding)하고 자동으로 Failover를 지원하는 방식이다
데이터 분산 16384개의 Hash Slot으로 분할된 키를 각 마스터가 담당하여 처리한다
장애조치 Master 별로 1개 이상의 Replica를 가지고 Master가 장애로 감지되면 Replica중 하나가 Master로 승격된다 이때, Cluster 내 다수의 노드가 동의 하는 경우 장애로 간주 된다(quorum 기준 이상)
노드 간 통신 Cluster 노드 간에는 gossip protocol로 서로 통신하고 주기적으로 상태 정보를 교환 한다 이를 통해 각 노드는 Cluster 내 다른 노드 정보를 알고 있다
노드별로 슬롯을 담당하고 있는 형태인 Cluster는 노드가 추가되는 경우 리벨런싱 작업이 필요하고 이는 자동으로 이루어지지 않는다
UPDATE의 경우 해당되는 인덱스만 찾아서 변경해주면 된다. 다만, 테이블에서 한건을 변경할 때 마다 인덱스에는 두개의 오퍼레이션이 발생(삭제 후 삽입)
DML에서 인덱스 개수가 미치는 영향이 크다.
핵심 트랜잭션 테이블에서 인덱스의 개수를 하나라도 줄이면 TPS(Transcation Per Second)는 그만큼 향상된다.
인덱스가 존재할 때와 그러지 않을 때의 insert 시간 비교
왼쪽 인덱스가 없을때, 가운데 인덱스가 2개 존재할 때, 오른쪽 일반 인덱스와 PK 제약을 모두 제거한 상태
데이터베이스에 논리적으로 의미있는 자료만 저장되게 하는 데이터 무결성 규칙
개체 무결성
참조 무결성
도메인 무결성
사용자 정의 무결성
PK, FK 제약은 Check, Not Null 제약보다 성능에 더 큰 영향을 미친다
Check, Not Null은 정의한 제약 조건을 준수하지는만 확인하면 되지만, PK, FK 제약은 실제 데이터를 조회해 봐야 하기 때문
Update, Delete 문의 실행 계획이 Select 문의 실행 계획과 다르지 않으므로 인덱스 튜닝 원리를 그대로 적용할 수 있다. 조인 튜닝 또한 동일하게 적용 가능하다
인덱스도 select와 같이 적용된다
Redo 로깅과 DML 성능
데이터파일과 컨트롤 파일에 가해지는 모든 변경사항을 Redo 로그에 기록한다
Redo 로그는 트랜잭션 데이터가 어떤 이유에서건 유실되었을 때, 트랜잭션을 재현함으로써 유실 이전 상태로 복구하는데 사용된다
DML이 수행될 때마다 Redo 로그를 생성하므로 Redo 로깅은 DML 성능에 영향을 미친다
Redo 로그 목적
Database Recovery : Media Fail 발생 시 데이터베이스를 복구하기 위해 사용(온라인 Redo 로그를 백업해 둔 Archived Redo 로그 이용) → Media Recovery
Cache Recovery (Instnace Recovery 시 roll forward 단계) : 캐시에 저장된 변경사항이 디스크 상의 데이터 블록에 기록되지 않은 상태로 인스턴스가 비정상적으로 종료되면, 그때까지의 작업내용을 모두 잃게 된다.(버퍼캐시는 휘발성) 트랜잭션 데이터 유실에 대비하기 위해 Redo 로그를 남긴다
Fast Commit : 변경된 메모리 버퍼 블록을 디스크 상의 데이터 블록에 반영하는 작업은 랜덤 액세스 방식으로 이루어지므로 매우 느리지만 로그는 Append 방식으로 기록하므로 상대적으로 빠르다. 따라서 변경사항을 Append 방식으로 빠르게 로그 파일에 기록하고 변경된 메모리 버퍼 블록과 데이터 파일 블록 간 동기화는 DBWR을 이용해 나중에 배치 방식으로 일괄 수행한다. 아직 디스크에 기록되지 않았지만 Redo 로그를 믿고 커밋을 완료하는 것을 Fast Commit 이라고 한다
Undo 로깅과 DML 성능
Redo는 트랜잭션을 재현함으로써 과거를 현재 상태로 되돌리는 데 사용 Undo는 트랜잭션을 롤백함으로써 현재를 과거로 돌리는데 사용
Undo 데이터 사용 조건
Transaction Rollback : 트랜잭션에 의한 변경사항을 최종 커밋하지 않고 롤백할 때 Undo 데이터 사용
Transaction Recovery (Instance Recovery 시 rollback 단계) : Instance Crash 발생 후 Redo를 이용해 roll forward 단계가 완료되면 최종 커밋되지 않은 변경사항까지 모두 복구되므로 Undo 데이터를 사용하여 아직 커밋되지 않았던 트랜잭션들을 모두 롤백해야 한다
Read Consistency : 읽기 일관성에 사용
MVCC(Multi Version Concurrency Control) 모델
current 모드: 디스크에서 캐시로 적재된 원본 블록을 현재 상태 그대로 읽는 방식
consistent 모드: 쿼리가 시작된 이후에 다른 트랜잭션에 의해 변경된 블록을 만나면 원본 블록으로부터 복사본(CR Copy) 블록을 만들고 거기에 Undo 데이터를 적용함으로써 쿼리가 시작된 시점으로 되돌려 읽는 방식
SCN(System Commit Number) 마지막 커밋이 발생한 시점정보
블록 SCN: 각 블록이 마지막으로 변경된 시점을 관리하기 위해 블록 헤더에 기록됨
쿼리 SCN: 모든 쿼리는 Global 변수인 SCN 값을 먼저 확인하고 읽기 작업 시작, 이를 쿼리 SCN이라고 한다
데이더를 읽다가 블록 SCN이 쿼리 SCN보다 더 큰 블록을 만나면 복사본 블록을 만들고 Undo 데이터를 적용함으로써 쿼리가 시작된 시점으로 되돌려서 읽는다
Undo 데이터가 다른 트랜잭션에 의해 재사용됨으로써 쿼리 시작 시점으로 되돌리는 작업에 실패할 때 Snapshot too old 에러 발생
Lock과 DML 성능
Lock은 DML 성능에 매우 크고 직접적인 영향을 미친다. Lock을 필요 이상으로 자주, 길게 사용하거나 레벨이 높을수록 DML 성능은 느려진다
트랜잭션 격리 수준
Read Uncommited
Read Commited (기본)
Reapeatable Read
Serializable
트랜잭션의 저장 과정 (Write Ahead Logging)
DML 문을 실행하면 Redo 로그버퍼에 변경사항 기록
버퍼블록에서 데이터를 변경(추가,수정,삭제), 버퍼캐시에서 블록을 찾지 못하면 데이터파일에서 읽는 작업부터 수행
커밋
LGWR 프로세스가 Redo 로그버퍼 내용을 로그파일에 일괄 저장
DBWR 프로세스가 변경된 버퍼블록들을 데이터파일에 일괄 저장
Log Force at Commit : 서버 프로세스가 커밋을 발행했다고 신보를 보낼때 LGWR 프로세스가 로그파일에 저장하므로 적어도 커밋시점에는 Redo 로그버퍼 내용이 로그파일에 기록됨
데이터베이스 Call
Parse Call : SQL 파싱과 최적화를 수행하는 단계. SQL과 실행계획을 라이브러리 캐시에서 찾으면, 최적화 단계 생각 가능
Execute Call: SQL을 실행하는 단계. DML은 이 단계에서 모든 과정이 종료. SELET 문은 Fetch 단계를 거친다
Fetch Call: 데이터를 읽어서 사용자에게 결과집합을 전송하는 과정. SELECT문에서만 나타나며 전송할 데이터가 많으면 Fetch Call 여러번 발생
User Call, Recursive Call
Call 이 발생하는 위치에 따라 User Call과 Recursive Call로 나뉜다
User Call: 네트워크를 통해 DBMS 외부로부터 인입되는 Call 3-Tier 아키텍처에서 User Call은 WAS(또는 AP서버) 서버에서 발생하는 Call
Recursive Call: DBMS 내부에서 발생하는 Call
커밋과 성능
: 커밋을 자주하는 경우 트랜잭션 원자성에 문제가 생긴다
: 매우 오래 걸리는 트랜잭션을 한 번도 커미하지 않고 진행하면 Undo 공간 부족으로 인해 시스템에 부작용을 초래할 수 있다
: 적당한 주기로 커밋을 하는것이 좋다
커밋을 자주하면 수행 시간이 오래걸리게 되는데, 이는 User Call에서는 더 많은 시간이 걸리게 된다
One SQL
Insert Into Select
수정가능 조인 뷰
Merge 문
실무에서 One SQL로 구현이 쉽지 않다
Array Processing 기능을 활용하면 One SQL을 쓰지 않고도 Call 부하를 획기적으로 줄일 수 있다
PL/SQL
Java
인덱스 및 제약 해제를 통한 대량 DML 튜닝
동시 트랜잭션 없이 대량 데이터를 적재하는 배치 프로그램에서는 인덱스 및 제약 조건을 해제함으로써 큰 성능 개선 효과를 얻을 수 있다
PK 제약 및 인덱스 생성 (총 인덱스 2개)
PK제약과 인덱스 해제
PK제약에 Unique 인덱스를 사용한 경우
PK 제약 해제 및 인덱스 삭제
인덱스 비활성화
PK 제약 설정 및 인덱스 활성화
2.PK제약에 Non-Unique 인덱스 사용
PK제약을 Unusable한 상태에서는 데이터를 입력할 수 없다
PK 인덱스를 Drop 하지 않고 Unusable 상태에서 데이터를 입력하려면 PK 제약에 Non-Unique 인덱스를 사용하면 된다
Non-Unique 인덱스 생성 및 PK 제약으로 지정
PK 제약 해제 및 인덱스 비활성화
PK 제약 설정 및 인덱스 활성화
수정가능 조인 뷰
전통적인 UPDATE
전통적인 UPDATE문은 다른 테이블과 조인이 필요할 때 비율을 완전히 해소할 수 없다
수정가능 조인 뷰
수정가능 조인 뷰를 활용하면 참조 테이블과 두 번 조인하는 비효율을 없앨 수 있다
수정가능 조인 뷰에서는 1쪽 집합에 PK 제약을 설정하거나 Unique 인덱스를 생성해야 수정가능 조인뷰를 통한 입력/수정/삭제가 가능하다
키 보존 테이블
: 조인된 결과집합을 통해서도 중복 값 없이 Unique 하게 식별이 가능한 테이블 Unique한 1쪽 집합과 조인되는 테이블이어야 조인된 결과집합을 통한 식별이 가능
ORA-01779 오류 회피 : Group By 한 집합과 조인한 테이블은 키가 보존되므로 해당 문제를 해결할 수 있다
Merge 문
DW Merge 예시
Merge문은 Source 테이블 기준으로 Target 테이블과 Left Outer 방식으로 조인해서 조인 성공하면 Update, 실패하면 Insert 한다
Update 와 Insert를 선택적으로 처리 가능
where 절을 통한 추가 조건절 기술 가능
이미 저장된 데이터를 조건에 따라 지우는 기능 제공
Delete의 경우
Merge문을 수행한 결과가 Null 이면 삭제하지 않는다
조인에 성공한 데이터만 삭제할 수 있다
Direct Path I/O
DW, 배치 프로그램에서는 대량 데이터를 처리하기 때문에 버퍼캐시를 경유하는 I/O 메커니즘이 오히려 성능을 떨어뜨릴 수 있다
그래서 버퍼캐시를 경유하지 않고 곧바로 데이터 블록을 읽고 쓸 수 있는 Direct Path I/O 기능을 제공한다
동작하는 경우
병렬 쿼리로 Full Scan을 수행할 때
병렬 DML을 수행할 때
Direct Path Insert 를 수행할 때
Temp 세그먼트 블록들을 읽고 쓸 때
direct 옵션을 지정하고 export를 수행할 때
nocache 옵션을 지정한 LOB 컬럼을 읽을 때
Direct Path Insert
INSERT가 느린 이유
데이터를 입력할 수 있는 블록을 Freelist에서 찾는다
Freelist에서 할당 받은 블록을 버퍼캐시에서 찾는다
버퍼캐시에 없으면, 데이터 파일에서 읽어 버퍼캐시에 적재한다
INSERT 내용을 Undo 세그먼트에 기록한다
INSERT 내용을 Redo 로그에 기록한다
Direct Path Insert 적용 방법
INSERT … SELECT 문에 append 힌트 적용
parallel 힌트를 이용해 병렬 모드로 INSERT
direct 옵션을 지정하고 SQL Loader(sqlldr)로 데이터 적재
CTAS 문 수행
Direct Path Insert 방식이 빠른 이유
Freelist를 참조하지 않고 HWM(High-Water Mark) 바깥 영역에 데이터를 순차적으로 입력한다
블록을 버퍼캐시에서 탐색하지 않는다
버퍼캐시에 적재하지 않고, 데이터파일에 직접 기록한다
Undo 로깅을 하지 않는다
Redo 로깅을 안하게 할 수 있다(nologging 모드 상태일 때)
Direct Path Insert 주의점
exclusive 모드 TM Lock 발생
Freelist를 조회하지 않고 HWM 바깥 영역을 입력하므로 테이블에 여유공간이 있어도 재사용하지 않는다
병렬 DML
INSERT는 append 힌트로 Direct Path Write 방식을 유도할 수 있지만, UPDATE, DELETE 는 불가능하다
병렬 DML로 Direct Path Write 방식 사용가능
병렬 DML 활성화 방법
아래 힌트 적용 시 대상 레코드 찾는 작업(insert는 select 쿼리, update/delete 는 조건절 검색)과 데이터 추가/변경/삭제 병렬로 진행
병렬 DML을 활성화 하지 않고 수행하면 대상 레코드를 찾는 작업만 병렬로 진행되어 추가/변경/삭제 는 QC(Query Coordinator)가 혼자 담당하므로 병목 발생
병렬 INSERT는 append 힌트를 적용하지 않아도 Direct Path Insert 방식을 사용한다
하지만 병렬 DML이 작동하지 않을 경우를 대비해 apeend 힌트를 같이 사용하는 것이 좋다
병렬 DML이 작동하지 않더라도 QC 가 Direct Path Insert를 사용하면 어느정도 만족할 만한 성능을 낼 수 있기 때문
enable_parallel_dml 힌트를 통해 활성화 가능(12c이후)
* 병렬 DML도 Direct Path Write 방식을 사용하므로 데이터 입력/수정/삭제 때 Exclusive 모드 TM Lock 발생
병렬 DML 동작 확인 방법
UPDATE 가 PX COORDINATOR 아래 쪽애 나타나면 UPDATE를 각 병렬 프로세스가 처리
루트 블록 스캔 과정에서 특정 레코드를 찾았다면 그것이 가리키는 리프 블록으로 내려가면 안되고 그 이전 레코드가 가리키는 리프 블록 으로 내려가야 한다. 레코드의 값보다 크거나 같은 값들이 리프 블록에 존재하므로, 이전 레코드에 조건에 포함되는 레코드가 존재할 수 있기 때문이다
루트 블록에서 C1, C2가 B,3 이전의 레코드의 리프블록으로 내려가서 B, 3인 레코드를 찾고 B, 4인 레코드를 만나는 순간 스캔을 멈춘다
루트 블록에서 C1, C2가 B, 3 이전의 레코드의 리프블록으로 내려가 B, 3인 레코드를 찾고 C1이 C인 값을 만나면 스캔을 멈춘다. C2는 스캔을 멈추는데는 역할을 하지 못했지만 스캔의 시작점을 결정하는데는 역할을 했다
C2는 수직적 탐색(스캔 시작지점)을 찾는데는 역할을 하지 못했으나 스캔을 멈추는데는 역할을 했다
C2는 수직적 탐색(스캔의 시작지점)을 찾는데 역할을 했고, 스캔을 멈추는데도 역할을 했다. 즉, 스캔량을 줄이는데 역할을 했다
C1 조건은 스캔의 시작과 끝을 결정하는데 역할을 했으나, C2는 그렇지 못했다
C2는 스캔량을 줄이는데 거의 역할을 못했다
좌측은 성능검으로 시작하는 용어를 스캔할 때,
우측은 성능으로 시작하고 4번째 값이 선 인 단어를 스캔할 때의 경우이다
인덱스 선행 컬럼이 조건절에 없거나 = 조건이 아니면 인덱스 스캔 과정에서 비효율이 발생한다
인덱스를 7463개의 블록을 읽으며서 10개의 블록을 찾았고, 이 블록을 가지고 10개의 블록에 테이블 엑세스를 했다
단, 버퍼 피닝에 의해 같은 블록에 있는 데이터는 추가로 블록에 엑세스 하지 않아 7463블록에 8블록을 추가로 읽어 총 7471개의 블록을 읽었다
선두 컬럼 : 인덱스 구성상 ‘맨 앞쪽’에 있는 컬럼을 지칭할 때 사용
선행 컬럼 : 어떤 컬럼보다 ‘상대적으로 앞쪽’에 놓인 컬럼을 지칭할 때 사용
비용 = 인덱스 수직적 탐색 비용 + 인덱스 수평적 탐색 비용 + 테이블 랜덤 액세스 비용 = 인덱스 루트와 브랜치 레벨에서 읽은 블록 수 + 인덱스 리프 블록을 스캔하는 과정에 읽는 블록 수 + 테이블 액세스 과정에 읽는 블록 수
테이블과 달리 인덱스에는 같은 값을 갖는 레코드들이 서로 군집해 있다
첫번째 나타나는 범위 검색 조건까지만 만족하는 인덱스 레코드는 모두 연속해서 모여 있지만, 그 이하 조건까지 만족하는 레코드는 비교 연산자 종류에 상관없이 흩어진다
인덱스 선행 컬럼이 모두 = 조건일 때 필요한 범위만 스캔하고 멈출 수 있는 것은, 조건을 만족하는 레코드가 모두 한데 모여 있기 때문!
Between 을 IN-List로 전환
IN-Llist를 사용하는 경우 IN-List 내 존재하는 개수 만큼 수직적 탐색이 발생한다
IN-List 항목 개수가 늘어날 수 있다면 BETWEEN을 IN-List로 전환하는 방식은 사용하기 곤란하다
이런 경우는 NL조인이나 서브쿼리로 구현하면 된다
BETWEEN 조건을 IN-List 조건으로 전환할 때 IN-List 개수가 많으면 수직적 탐색이 많이 발생한다
이는 BETWEEN 조건으로 리프 블록을 많이 스캔하는 비효율보다 IN-List 개수만큼 브랜치 블록을 반복 탐색하는 비효율이 더 클 수 있다
NUM_INDEX_KEYS 힌트 : 인덱스의 지정한 번호의 컬럼까지만 액세스 조건으로 사용하라는 의미
해당 힌트를 쓰지 않고 컬럼을 가공하여 같은 효과를 낼 수 있다
BETWEEN과 LIKE 비교
BETWEEN의 경우 판매월이 201912이고 판매구분이 B인 첫번째 레코드를 만나면 스캔을 멈춘다
LIKE는 판매월이 201912인 레코드를 모두 스캔하고나서야 멈춘다
OR 조건 활용한 옵션 처리
인덱스 선두 컬럼에 대해 옵션 조건에 OR 조건을 사용하면 인덱스를 사용할 수 없다
인덱스에 포함되지 않는 컬럼의 경우는 OR 조건을 사용해도 괜찮다
OR 조건을 활용한 옵션 조건 처리
인덱스 액세스 조건으로 사용 불가
인덱스 필터 조건으로도 사용 불가
테이블 필터 조건으로만 사용 가능
OR 조건 사용의 장점은 NULL 허용 컬럼이어도 사용할 수 있다는 것
LIKE/BETWEEN 패턴을 사용하고자 할 때 점검해야할 것
인덱스 선두 컬럼
NULL 허용 컬럼 이때 고객ID가 NULL 허용 컬럼이면 결과집합에 오류가 생긴다
숫자형 컬럼
가변 길이 컬럼
UNION ALL 을 사용하면 코딩량은 늘지만 고객ID를 입력할 때와 그러지 않을 때에 대해 인덱스를 사용할 수 있다
PL/SQL 사용자 정의 함수는 생각보다 느리다
그 이유는
가상머신 상에서 실행되는 인터프리터 언어
호출 시마다 컨텍스트 스위칭 발생
내장 SQL에 대한 Recursive Call 발생 *
효율적인 인덱스 구성을 통한 함수 호출 최소화
조건절에 PL/SQL 함수를 사용했을 때 Full Scan으로 읽으면 해당 함수가 테이블 건수만큼 수행된다
하지만 다른 조건절과 같이 있으면 함수는 조건절을 만족하는 건 수 만큼만 실행되게 되므로 좀 더 효율적이게 된다
마지막으로 함수가 = 로 동등 비교하게 되는 컬럼이 포함된 인덱스를 사용하여 해당 컬럼이 인덱스 액세스 조건으로 사용되면 함수는 한번만 수행된다
인덱스 구성 시 중요한 두가지 선택 기준
조건절에 항상 사용하거나, 자주 사용하는 컬럼을 선정한다
= 조건으로 자주 조회하는 컬럼을 앞쪽에 둔다
스캔 효율성 이외의 판단 기준
수행 빈도
업무상 중요도
클러스터링 팩터
데이터량
DML 부하
저장 공간
인덱스 관리 비용 등
I/O를 최소화 하면서 소트 연산을 생략하기 위한 인덱스 구성 공식
= 연산자로 사용한 조건절 컬럼 선정
ORDER BY 절에 기술한 컬럼 추가
= 연산자가 아닌 조건절 컬럼은 데이터 붆포를 고려해 추가 여부 결정
IN-LIst 는 = 이 아니다
IN-List 가 = 이 되기 위해 IN-List Iterator 로 풀리게 되면 UNION ALL로 결과 집합을 묶기 때문에 Sort 연산을 생략 할 수 없다