DB Study

2장. SGA — 공유 메모리 구조

모든 세션이 함께 쓰는 커다란 작업대, SGA. 디스크를 대신하는 Buffer Cache와 "같은 SQL은 두 번 준비하지 않는" Shared Pool — Oracle 성능의 8할이 이 방에서 결정됩니다.

중급 ⏱ 약 30분 🎬 애니메이션 3개 선수 지식: 1장 Instance/Database
🧭
이 장에서 배우는 것

① 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 PoolRMAN 백업, 병렬 실행, shared server의 UGA 등 큰 단위 할당대형 작업용 임시 창고
Java PoolDB 내장 JVM의 자바 코드·데이터자바 전용 방

2.2 Database Buffer Cache — 디스크를 안 읽는 기술

버퍼 캐시는 데이터파일의 DB block(기본 크기 8KB, DB_BLOCK_SIZE 파라미터)을 그대로 담는 메모리 버퍼들의 모음입니다. 서버 프로세스는 어떤 블록이 필요하면 무조건 버퍼 캐시부터 찾습니다. 있으면 cache hit(메모리 속도), 없으면 cache miss(디스크에서 읽어 캐시에 적재)입니다.

캐시는 유한하므로 자리가 없으면 누군가를 내보내야 합니다. Oracle은 LRU(Least Recently Used) 기반 알고리즘으로 "가장 오랫동안 사용되지 않은" 버퍼를 밀어냅니다. 자주 쓰는 블록일수록 캐시에 오래 살아남는 구조입니다.

⚙️ 서버 프로세스 블록 A가 필요해! 🧠 Database Buffer Cache MRU(뜨거움) ←────────→ LRU(차가움) 블록 X 블록 Y 블록 Z ← 가장 오래 안 씀 블록 A 💾 데이터파일 8KB block들 ① 캐시 검색… 블록 A 없음 → cache miss ✗ ② 데이터파일에서 블록 A를 읽어 캐시에 적재 (물리 I/O) ③ 같은 블록 재요청 → cache hit ✓ 디스크를 안 감! (논리 I/O) ④ 캐시가 가득 찬 상태에서 새 블록 B가 필요해짐 — 누굴 내보내지? 블록 B ⑤ 블록 Z(LRU 끝) 방출 → 그 자리에 B 적재 버퍼 캐시의 목표: "두 번째부터는 디스크에 가지 않는다"
🐘
PostgreSQL과 비교 — shared buffers

버퍼 캐시는 PostgreSQL의 shared buffers와 정확히 같은 역할입니다(pg 2장). 차이점: PG는 shared buffers를 보통 RAM의 25% 정도로 잡고 나머지는 OS 페이지 캐시에 맡기는 이중 캐시 전략인 반면, Oracle은 SGA에 크게 할당해 DB가 직접 캐싱을 주도하는 편입니다.

Dirty Buffer — 고친 블록은 누가 디스크에 쓰나

UPDATE가 실행되면 서버 프로세스는 버퍼 캐시 안의 블록을 직접 수정합니다. 이렇게 메모리 내용과 디스크 내용이 달라진 버퍼를 dirty buffer라고 합니다. 중요한 것은 — 수정한 서버 프로세스가 디스크에 쓰지 않는다는 점입니다. 쓰기는 백그라운드 프로세스 DBWn(Database Writer)이 나중에 모아서 처리합니다.

🧠 Buffer Cache 블록 P 블록 P′ dirty 🔥 블록 Q 블록 Q′ dirty 🔥 블록 R ⚙️ 서버 프로세스 UPDATE 실행 ① UPDATE — 캐시 안의 블록을 메모리에서 수정 (디스크는 아직 그대로) ② 메모리 ≠ 디스크가 된 버퍼 = dirty buffer. 서버 프로세스는 쓰지 않고 제 갈 길을 갑니다 ✍️ DBWn Database Writer ③ 백그라운드의 DBWn이 dirty buffer들을 모아서(batch) 처리할 준비 💾 데이터파일 디스크 반영 완료 ④ DBWn이 여러 dirty buffer를 한꺼번에 데이터파일에 기록 → 버퍼는 다시 clean

2.3 Shared Pool — 같은 SQL은 두 번 준비하지 않는다

SQL 한 문장을 실행하려면 문법 검사, 의미 검사(테이블·컬럼 존재 확인), 그리고 옵티마이저의 실행계획 수립이라는 준비 작업(파싱)이 필요합니다. 특히 실행계획 수립은 CPU를 많이 쓰는 비싼 작업입니다. Shared Pool은 이 준비 결과물을 저장해 모든 세션이 재사용하게 하는 공간입니다.

여기서 Oracle 튜닝의 최중요 개념이 나옵니다 — 하드 파싱 vs 소프트 파싱.

SELECT … WHERE id=:1 SQL 도착 → 해시 계산 📚 Library Cache (Shared Pool) 커서 #A11F 커서 #7C3D + 실행계획 ✓ SQL 해시로 검색하는 커서 보관소 문법·의미 검사 syntax/semantic 옵티마이저 최적화 💸 가장 비쌈 실행계획 생성 → 캐시에 저장 🔨 하드 파싱 — 전 과정을 처음부터 (CPU·래치 소모 큼) ① 해시 #7C3D 검색… 없음 ✗ SELECT … WHERE id=:1 같은 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 남발 = Shared Pool 오염

바인드 변수 없는 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  ← 확인용 예시 값
💡
한 단계 더 — AMM(MEMORY_TARGET)도 있지만

MEMORY_TARGET을 쓰면 SGA와 PGA까지 통합 자동 관리(AMM)가 되지만, Linux HugePages와 함께 쓸 수 없는 제약 때문에 실무 대형 시스템에서는 SGA_TARGET(ASMM) + PGA_AGGREGATE_TARGET 조합이 일반적입니다. PGA 쪽 이야기는 3장에서 이어집니다.

✍️ 이해도 체크

하드 파싱과 소프트 파싱의 결정적 차이는?
✅ 파싱의 가장 비싼 단계는 옵티마이저의 실행계획 수립입니다. 소프트 파싱은 SQL 해시로 Library Cache에서 기존 커서를 찾아 그 계획을 재사용하므로 이 단계를 생략합니다. 데이터를 어디서 읽는지(버퍼 캐시/디스크)는 파싱이 아니라 실행 단계의 이야기입니다.
UPDATE로 버퍼 캐시의 블록을 수정한 직후, 이 dirty buffer를 데이터파일에 기록하는 주체는?
✅ 서버 프로세스는 메모리(버퍼 캐시)만 수정하고, 디스크 기록은 DBWn이 뒤에서 batch로 처리합니다. LGWR이 커밋 시 기록하는 것은 데이터 블록이 아니라 redo입니다 — 이 구분이 4장의 핵심 메시지입니다.
📚
원문으로 더 깊이

Oracle Database 19c Concepts Guide — Memory Architecture 장에서 buffer cache의 상태 전이, shared pool의 세부 구조(reserved pool, result cache 등)까지 확인할 수 있습니다.