2장. SGA — 공유 메모리 구조
모든 세션이 함께 쓰는 커다란 작업대, SGA. 디스크를 대신하는 Buffer Cache와 "같은 SQL은 두 번 준비하지 않는" Shared Pool — Oracle 성능의 8할이 이 방에서 결정됩니다.
① Database Buffer Cache — DB block 캐싱, LRU, dirty buffer ② Shared Pool — Library Cache의 커서 공유와 하드/소프트 파싱 ③ Redo Log Buffer와 Large/Java Pool ④ SGA_TARGET으로 하는 자동 메모리 관리(ASMM)
2.1 SGA — 인스턴스의 심장인 공유 메모리
SGA(System Global Area)는 인스턴스가 기동될 때 할당되는 큰 공유 메모리 영역입니다. "System"이라는 이름처럼 모든 서버 프로세스와 백그라운드 프로세스가 함께 읽고 씁니다. 반대로 각 프로세스의 사적 메모리는 PGA(3장)입니다.
SGA는 용도별 구성 요소(component)로 나뉩니다. 이 장의 지도를 먼저 보세요.
| 구성 요소 | 역할 | 비유 |
|---|---|---|
| Database Buffer Cache | 데이터파일의 블록을 캐싱 — 디스크 I/O를 메모리 접근으로 대체 | 책상 위에 펼쳐 둔 장부 사본 |
| Shared Pool | 파싱된 SQL(커서)·실행계획·딕셔너리 정보를 공유 | 모두가 재사용하는 요리 레시피 선반 |
| Redo Log Buffer | 변경 기록(redo entry)을 디스크에 쓰기 전 잠시 모아두는 버퍼 | 회계 전표를 모으는 서류함 |
| Large Pool | RMAN 백업, 병렬 실행, shared server의 UGA 등 큰 단위 할당 | 대형 작업용 임시 창고 |
| Java Pool | DB 내장 JVM의 자바 코드·데이터 | 자바 전용 방 |
2.2 Database Buffer Cache — 디스크를 안 읽는 기술
버퍼 캐시는 데이터파일의 DB block(기본 크기 8KB, DB_BLOCK_SIZE 파라미터)을 그대로 담는 메모리 버퍼들의 모음입니다. 서버 프로세스는 어떤 블록이 필요하면 무조건 버퍼 캐시부터 찾습니다. 있으면 cache hit(메모리 속도), 없으면 cache miss(디스크에서 읽어 캐시에 적재)입니다.
캐시는 유한하므로 자리가 없으면 누군가를 내보내야 합니다. Oracle은 LRU(Least Recently Used) 기반 알고리즘으로 "가장 오랫동안 사용되지 않은" 버퍼를 밀어냅니다. 자주 쓰는 블록일수록 캐시에 오래 살아남는 구조입니다.
서버 프로세스가 블록 A를 요청합니다. 먼저 버퍼 캐시를 검색하지만 없습니다 — cache miss.
데이터파일에서 블록 A를 읽어(물리 I/O) 캐시의 빈 버퍼에 적재합니다. 디스크 읽기는 메모리보다 수백~수천 배 느린 비싼 작업입니다.
잠시 후 같은 블록 A를 다시 요청하면? 캐시에서 바로 발견 — cache hit. 디스크에 가지 않고 메모리에서 끝납니다(논리 I/O).
캐시가 가득 찬 상태에서 새 블록 B가 필요해졌습니다. 누군가를 내보내야 합니다.
LRU 알고리즘이 가장 오랫동안 사용되지 않은 블록 Z를 방출하고 그 자리에 B를 넣습니다. 자주 쓰는 블록은 계속 살아남아 "뜨거운 데이터는 늘 메모리에" 있게 됩니다.
버퍼 캐시는 PostgreSQL의 shared buffers와 정확히 같은 역할입니다(pg 2장). 차이점: PG는 shared buffers를 보통 RAM의 25% 정도로 잡고 나머지는 OS 페이지 캐시에 맡기는 이중 캐시 전략인 반면, Oracle은 SGA에 크게 할당해 DB가 직접 캐싱을 주도하는 편입니다.
Dirty Buffer — 고친 블록은 누가 디스크에 쓰나
UPDATE가 실행되면 서버 프로세스는 버퍼 캐시 안의 블록을 직접 수정합니다. 이렇게 메모리 내용과 디스크 내용이 달라진 버퍼를 dirty buffer라고 합니다. 중요한 것은 — 수정한 서버 프로세스가 디스크에 쓰지 않는다는 점입니다. 쓰기는 백그라운드 프로세스 DBWn(Database Writer)이 나중에 모아서 처리합니다.
UPDATE가 실행되면 서버 프로세스는 버퍼 캐시 안에서 블록을 수정합니다. 이 시점에 디스크의 원본 블록은 아직 옛날 내용 그대로입니다.
메모리와 디스크 내용이 달라진 버퍼가 dirty buffer입니다. 수정한 서버 프로세스는 디스크에 쓰지 않고 다음 일을 하러 갑니다 — 그래서 UPDATE가 빠릅니다.
디스크 기록은 백그라운드 프로세스 DBWn의 몫입니다. dirty buffer가 쌓이거나 빈 버퍼가 부족해지면 움직입니다.
DBWn은 dirty buffer 여러 개를 모아서 한꺼번에 데이터파일에 씁니다(효율적인 batch write). 기록된 버퍼는 다시 clean 상태가 되어 재사용될 수 있습니다. "그럼 커밋의 안전은 누가 보장하지?"의 답은 redo — 4장과 7장에서!
2.3 Shared Pool — 같은 SQL은 두 번 준비하지 않는다
SQL 한 문장을 실행하려면 문법 검사, 의미 검사(테이블·컬럼 존재 확인), 그리고 옵티마이저의 실행계획 수립이라는 준비 작업(파싱)이 필요합니다. 특히 실행계획 수립은 CPU를 많이 쓰는 비싼 작업입니다. Shared Pool은 이 준비 결과물을 저장해 모든 세션이 재사용하게 하는 공간입니다.
- Library Cache — 파싱된 SQL 문장(shared SQL area, 흔히 "커서")과 실행계획, PL/SQL 코드를 보관합니다. SQL 텍스트의 해시값으로 찾습니다.
- Data Dictionary Cache — 테이블 정의, 컬럼, 권한 같은 딕셔너리 정보를 행 단위로 캐싱합니다(row cache라고도 부릅니다). 파싱할 때마다 딕셔너리를 디스크에서 읽지 않기 위해서입니다.
여기서 Oracle 튜닝의 최중요 개념이 나옵니다 — 하드 파싱 vs 소프트 파싱.
SQL이 도착하면 텍스트의 해시값을 계산해 Library Cache를 검색합니다. 처음 보는 SQL이라 커서가 없습니다.
없으면 하드 파싱: 문법 검사 → 의미 검사(딕셔너리 캐시 활용) → 옵티마이저 최적화(가장 비싼 단계) → 실행계획 생성까지 전 과정을 수행합니다.
완성된 커서(파싱 결과 + 실행계획)가 Library Cache에 저장됩니다. 이제 이 SQL의 레시피가 선반에 올라갔습니다.
다른 세션이 같은 텍스트의 SQL을 실행하면? 해시가 일치해 커서를 바로 발견합니다.
소프트 파싱: 저장된 실행계획을 재사용하고 옵티마이저를 건너뜁니다. OLTP처럼 같은 SQL이 초당 수천 번 도는 시스템에서 이 차이가 성능을 좌우합니다.
커서가 공유되려면 SQL 텍스트가 완전히 같아야 합니다. 그래서 값을 문장에 직접 박아 넣으면(리터럴) 매번 다른 SQL로 취급되어 전부 하드 파싱이 됩니다. 해결책이 바인드 변수입니다.
-- ❌ 리터럴 SQL: 세 문장 모두 다른 해시 → 하드 파싱 3번, 커서 3개
SELECT * FROM orders WHERE id = 101;
SELECT * FROM orders WHERE id = 102;
SELECT * FROM orders WHERE id = 103;
-- ✅ 바인드 변수: 텍스트가 동일 → 하드 파싱 1번 + 소프트 파싱 2번, 커서 1개
SELECT * FROM orders WHERE id = :order_id;
바인드 변수 없는 SQL이 쏟아지면 하드 파싱 폭증으로 CPU가 타고, 일회용 커서가 Shared Pool을 가득 채워 library cache 경합과 메모리 부족(ORA-04031)까지 이어질 수 있습니다. "OLTP에서는 바인드 변수"는 Oracle 개발의 제1원칙입니다. 자세한 처방은 ⚡ 튜닝 팁에서.
2.4 Redo Log Buffer — 변경 기록의 대기실
데이터를 변경하면 "무엇을 어떻게 바꿨는지"의 기록인 redo entry가 만들어집니다. 이 기록이 디스크의 리두 로그 파일로 가기 전에 잠시 머무는 곳이 Redo Log Buffer입니다. 서버 프로세스가 redo entry를 버퍼에 쌓으면, 백그라운드 프로세스 LGWR(Log Writer)이 커밋 시점 등에 디스크로 내려씁니다.
버퍼 캐시(수백 MB~수십 GB)에 비해 Redo Log Buffer는 보통 수 MB~수십 MB로 작습니다. LGWR이 부지런히 비워주기 때문에 클 필요가 없습니다. "커밋 = LGWR의 redo 기록"이라는 Oracle 내구성의 핵심 드라마는 4장에서 애니메이션으로 봅니다.
2.5 Large Pool과 Java Pool — 특수 목적 공간
📦 Large Pool
이름 그대로 큰 덩어리 메모리 할당 전용입니다. RMAN 백업/복구 버퍼, 병렬 실행(parallel execution) 메시지 버퍼, 그리고 shared server 구성 시 세션 메모리(UGA)가 여기 놓입니다. Shared Pool이 큰 할당으로 조각나는 것을 막아주는 완충 지대입니다.
☕ Java Pool
Oracle DB 안에는 JVM이 내장되어 있어 자바 스토어드 프로시저를 돌릴 수 있습니다. 그 자바 코드와 세션 데이터가 사는 곳입니다. 자바 기능을 안 쓰면 존재감이 거의 없습니다.
2.6 SGA 자동 관리 — SGA_TARGET과 ASMM
옛날에는 버퍼 캐시 얼마, Shared Pool 얼마… 하고 DBA가 일일이 정했지만, 지금은 ASMM(Automatic Shared Memory Management)이 표준입니다. SGA_TARGET으로 SGA 전체 크기만 정해주면, Oracle이 워크로드를 관찰하며 구성 요소 간 크기를 자동으로 재분배합니다. 파싱이 많아지면 Shared Pool을 키우고, 스캔이 많아지면 버퍼 캐시를 키우는 식입니다.
-- SGA 관련 파라미터 확인
SHOW PARAMETER sga_target;
NAME TYPE VALUE
----------- ------------ ------
sga_target big integer 8G
-- 구성 요소별 현재 크기 보기
SELECT name, bytes/1024/1024 AS mb FROM v$sgainfo ORDER BY bytes DESC;
NAME MB
-------------------------- ------
Buffer Cache Size 5632
Shared Pool Size 1792
Large Pool Size 128
Java Pool Size 64
Redo Buffers 16 ← 확인용 예시 값
MEMORY_TARGET을 쓰면 SGA와 PGA까지 통합 자동 관리(AMM)가 되지만, Linux HugePages와 함께 쓸 수 없는 제약 때문에 실무 대형 시스템에서는 SGA_TARGET(ASMM) + PGA_AGGREGATE_TARGET 조합이 일반적입니다. PGA 쪽 이야기는 3장에서 이어집니다.
✍️ 이해도 체크
Oracle Database 19c Concepts Guide — Memory Architecture 장에서 buffer cache의 상태 전이, shared pool의 세부 구조(reserved pool, result cache 등)까지 확인할 수 있습니다.