01. PostgreSQL 기본 방식
기본 동작
PostgreSQL은 전통적으로 모든 I/O를 운영 체제의 페이지 캐시(Page Cache)를 사용하는 Buffered I/O 방식으로 처리합니다.
파일을 열 때 open() 시스템 호출에서 O_DIRECT 플래그를 사용하지 않으며, WAL, 테이블, 인덱스 파일 모두 OS 캐시를 거쳐 디스크에 기록됩니다.
동일한 데이터를 Shared Buffer 와 OS Cache 에 이중으로 저장하므로 메모리 낭비 (Double Buffering)
Direct I/O 란?
운영체제의 파일 시스템 캐시를 사용하지 않고 데이터를 직접 디스크에 읽거나 쓰는 방법
파일 캐시를 거치지 않고 Shared Buffer 에서 디스크로 직접 엑세스하기 때문에 불필요한 지연(2중 캐시 처리)이 발생하지 않는다.
메모리 효율적 사용: 복사 과정 감소
캐시 관리 단순화: 파일 시스템 캐시를 사용하지 않기 때문에 캐시 관리의 복잡성을 피할 수 있음
동작 방식
Buffered I/O
Shared Buffer -> OS Cache -> Disk
OS가 자동으로 캐시 관리
Direct I/O
Shared Buffer -> Disk
DBMS가 직접 캐시 관리
PostgreSQL 에서의 Direct I/O 사용
PostgreSQL 16에서의 변화
PostgreSQL 16부터 Direct I/O를 지원하는 debug_io_direct 설정이 가능합니다.
이 설정은 운영 체제의 캐시 효과를 최소화하여, PostgreSQL의 공유 버퍼만을 캐시 계층으로 활용하는 I/O 경로를 제공합니다.
관련 GUC 설정
※ 단, 다음과 같은 환경에서는 예외 가능성 있습니다.
mount 옵션으로 강제 Direct IO 설정된 경우
PostgreSQL이 아닌 외부 프로그램이 COPY 대상 파일에 Direct IO 방식으로 접근하는 경우
02. Buffered I/O 방식
# Dummy Table 생성
postgres=# DO $$
postgres$# BEGIN
postgres$# FOR i IN 1..5 LOOP
postgres$# EXECUTE 'CREATE TABLE table_' || i || ' (
postgres$# id SERIAL PRIMARY KEY,
postgres$# random_string TEXT,
postgres$# date DATE DEFAULT current_date
postgres$# );';
postgres$# END LOOP;
postgres$# END $$;
DO
postgres=# \dt
List of relations
Schema | Name | Type | Owner
--------+---------+-------+---------
public | table_1 | table | tarandb
public | table_2 | table | tarandb
public | table_3 | table | tarandb
public | table_4 | table | tarandb
public | table_5 | table | tarandb
(5 rows)
# Dummy Data INSERT
postgres=# DO $$
postgres$# DECLARE
postgres$# i INT;
postgres$# BEGIN
postgres$# FOR i IN 1..5 LOOP
postgres$# FOR j IN 1..1000000 LOOP
postgres$# EXECUTE 'INSERT INTO table_' || i || ' (random_string, date) VALUES (md5(random()::text), current_date)';
postgres$# END LOOP;
postgres$# END LOOP;
postgres$# END $$;
# 프로세스 확인
[tarandb@single ~]$ ps -ef | grep postgres
tarandb 3095 1 0 Mar12 ? 00:00:29 /app/tarantuladb/v16/bin/postgres
tarandb 3096 3095 0 Mar12 ? 00:00:00 postgres: logger
tarandb 3097 3095 0 Mar12 ? 00:00:00 postgres: checkpointer
tarandb 3098 3095 0 Mar12 ? 00:00:03 postgres: background writer
tarandb 3100 3095 0 Mar12 ? 00:00:03 postgres: walwriter
tarandb 3101 3095 0 Mar12 ? 00:00:03 postgres: autovacuum launcher
tarandb 3102 3095 0 Mar12 ? 00:00:01 postgres: archiver last was 000000010000000000000019.00000028.backup
tarandb 3103 3095 0 Mar12 ? 00:00:00 postgres: logical replication launcher
# strace 를 사용하여 B/W 프로세스 추적을 통해 파일 시스템 호출 시 O_DIRECT(Direct I/O) 사용 여부 추적
[tarandb@single ~]$ strace -f -e open,openat -p 3098
strace: Process 3098 attached
openat(AT_FDCWD, "base/5/16408", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "base/5/16418", O_RDWR|O_CLOEXEC) = 6
openat(AT_FDCWD, "base/5/16428", O_RDWR|O_CLOEXEC) = 7
openat(AT_FDCWD, "base/5/2610", O_RDWR|O_CLOEXEC) = 8
openat(AT_FDCWD, "base/5/16388", O_RDWR|O_CLOEXEC) = 9
openat(AT_FDCWD, "base/5/2224", O_RDWR|O_CLOEXEC) = 10
openat(AT_FDCWD, "base/5/2604", O_RDWR|O_CLOEXEC) = 12
openat(AT_FDCWD, "base/5/16396", O_RDWR|O_CLOEXEC) = 13
openat(AT_FDCWD, "base/5/16389_fsm", O_RDWR|O_CLOEXEC) = 14
openat(AT_FDCWD, "base/5/16389", O_RDWR|O_CLOEXEC) = 15
openat(AT_FDCWD, "base/5/16399", O_RDWR|O_CLOEXEC) = 16
openat(AT_FDCWD, "base/5/16406", O_RDWR|O_CLOEXEC) = 17
openat(AT_FDCWD, "base/5/16406", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "base/5/16399", O_RDWR|O_CLOEXEC) = 6
openat(AT_FDCWD, "base/5/2604", O_RDWR|O_CLOEXEC) = 7
openat(AT_FDCWD, "base/5/2224", O_RDWR|O_CLOEXEC) = 8
openat(AT_FDCWD, "base/5/1249", O_RDWR|O_CLOEXEC) = 9
openat(AT_FDCWD, "base/5/16399_fsm", O_RDWR|O_CLOEXEC) = 10
strace: Process 3098 detached
# O_DIRECT(Direct I/0) 미사용 확인
# strace 를 사용하여 walwriter 프로세스 추적을 통해 파일 시스템 호출 시 O_DIRECT(Direct I/O) 사용 여부 추적
[root@single ~]# strace -f -e open,openat -p 3100
strace: Process 3100 attached
openat(AT_FDCWD, "pg_wal/xlogtemp.3100", O_RDWR) = 5
openat(AT_FDCWD, "pg_wal/000000010000000000000047", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/archive_status/000000010000000000000047.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "pg_wal/000000010000000000000048", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/archive_status/000000010000000000000048.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "pg_wal/000000010000000000000049", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/archive_status/000000010000000000000049.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "pg_wal/00000001000000000000004A", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/archive_status/00000001000000000000004A.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "pg_wal/00000001000000000000004B", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/archive_status/00000001000000000000004B.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "pg_wal/00000001000000000000004C", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/archive_status/00000001000000000000004C.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "pg_wal/00000001000000000000004D", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/00000001000000000000004E", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/00000001000000000000004F", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/000000010000000000000050", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/000000010000000000000051", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/000000010000000000000052", O_RDWR|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/archive_status/000000010000000000000052.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
...
strace: Process 3100 detached
# O_DIRECT(Direct I/0) 미사용 확인
03. Direct I/O 방식
PostgreSQL 16부터 Direct I/O를 지원하므로 16버전사용 및 파라미터 변경 필요
psql (16.8 - TarantulaDB)
Type "help" for help.
postgres=# show debug_io_direct ;
debug_io_direct
-----------------
data,wal
(1 row)
# Dummy Table 생성
postgres=# DO $$
postgres$# BEGIN
postgres$# FOR i IN 1..5 LOOP
postgres$# EXECUTE 'CREATE TABLE table_' || i || ' (
postgres$# id SERIAL PRIMARY KEY,
postgres$# random_string TEXT,
postgres$# date DATE DEFAULT current_date
postgres$# );';
postgres$# END LOOP;
postgres$# END $$;
DO
postgres=# \dt
List of relations
Schema | Name | Type | Owner
--------+---------+-------+---------
public | table_1 | table | tarandb
public | table_2 | table | tarandb
public | table_3 | table | tarandb
public | table_4 | table | tarandb
public | table_5 | table | tarandb
(5 rows)
# Dummy Data INSERT
postgres=# DO $$
postgres$# DECLARE
postgres$# i INT;
postgres$# BEGIN
postgres$# FOR i IN 1..5 LOOP
postgres$# FOR j IN 1..1000000 LOOP
postgres$# EXECUTE 'INSERT INTO table_' || i || ' (random_string, date) VALUES (md5(random()::text), current_date)';
postgres$# END LOOP;
postgres$# END LOOP;
postgres$# END $$;
# 프로세스 확인
[root@single ~]# ps -ef | grep postgres
tarandb 26470 1 0 15:39 ? 00:00:00 /app/tarantuladb/v16/bin/postgres
tarandb 26471 26470 0 15:39 ? 00:00:00 postgres: logger
tarandb 26472 26470 0 15:39 ? 00:00:00 postgres: checkpointer
tarandb 26473 26470 0 15:39 ? 00:00:00 postgres: background writer
tarandb 26475 26470 0 15:39 ? 00:00:00 postgres: walwriter
tarandb 26476 26470 0 15:39 ? 00:00:00 postgres: autovacuum launcher
tarandb 26477 26470 0 15:39 ? 00:00:00 postgres: archiver
tarandb 26478 26470 0 15:39 ? 00:00:00 postgres: logical replication launcher
tarandb 26480 26470 0 15:40 ? 00:00:00 postgres: tarandb postgres [local] idle
root 26485 26074 0 15:40 pts/3 00:00:00 grep --color=auto postgres
# strace 를 사용하여 B/W 프로세스 추적을 통해 파일 시스템 호출 시 O_DIRECT(Direct I/O) 사용 여부 추적
[root@single ~]# strace -f -e open,openat -p 26473
strace: Process 26473 attached
openat(AT_FDCWD, "base/5/16450", O_RDWR|O_DIRECT|O_CLOEXEC) = 5
openat(AT_FDCWD, "base/5/16496", O_RDWR|O_DIRECT|O_CLOEXEC) = 6
openat(AT_FDCWD, "base/5/16500", O_RDWR|O_DIRECT|O_CLOEXEC) = 7
openat(AT_FDCWD, "base/5/16499", O_RDWR|O_DIRECT|O_CLOEXEC) = 8
openat(AT_FDCWD, "base/5/16501", O_RDWR|O_DIRECT|O_CLOEXEC) = 9
openat(AT_FDCWD, "base/5/16487", O_RDWR|O_DIRECT|O_CLOEXEC) = 10
openat(AT_FDCWD, "base/5/16488", O_RDWR|O_DIRECT|O_CLOEXEC) = 12
openat(AT_FDCWD, "base/5/16640", O_RDWR|O_DIRECT|O_CLOEXEC) = 13
openat(AT_FDCWD, "base/5/16641", O_RDWR|O_DIRECT|O_CLOEXEC) = 14
openat(AT_FDCWD, "base/5/16449", O_RDWR|O_DIRECT|O_CLOEXEC) = 15
openat(AT_FDCWD, "base/5/16444", O_RDWR|O_DIRECT|O_CLOEXEC) = 16
openat(AT_FDCWD, "base/5/16484", O_RDWR|O_DIRECT|O_CLOEXEC) = 17
openat(AT_FDCWD, "base/5/16637", O_RDWR|O_DIRECT|O_CLOEXEC) = 18
strace: Process 26473 detached
# O_DIRECT(Direct I/0) 사용 확인
# strace 를 사용하여 walwriter 프로세스 추적을 통해 파일 시스템 호출 시 O_DIRECT(Direct I/O) 사용 여부 추적
[root@single ~]# strace -f -e open,openat -p 26475
strace: Process 26475 attached
openat(AT_FDCWD, "pg_wal/archive_status/00000001000000000000006E.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "pg_wal/00000001000000000000006F", O_RDWR|O_DIRECT|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/archive_status/00000001000000000000006F.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "pg_wal/000000010000000000000070", O_RDWR|O_DIRECT|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/archive_status/000000010000000000000070.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "pg_wal/000000010000000000000071", O_RDWR|O_DIRECT|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/archive_status/000000010000000000000071.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "pg_wal/000000010000000000000072", O_RDWR|O_DIRECT|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/archive_status/000000010000000000000072.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "pg_wal/000000010000000000000073", O_RDWR|O_DIRECT|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/archive_status/000000010000000000000073.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "pg_wal/000000010000000000000074", O_RDWR|O_DIRECT|O_CLOEXEC) = 5
openat(AT_FDCWD, "pg_wal/archive_status/000000010000000000000074.ready", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "pg_wal/000000010000000000000075", O_RDWR|O_DIRECT|O_CLOEXEC) = 5
openat(AT_FDCWD, "/pgdata/tarandb/global/pg_control", O_RDWR) = 39
openat(AT_FDCWD, "pg_wal", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 39
openat(AT_FDCWD, "pg_wal", O_RDONLY) = 40
openat(AT_FDCWD, "pg_wal", O_RDONLY) = 40
openat(AT_FDCWD, "pg_wal", O_RDONLY) = 40
openat(AT_FDCWD, "pg_wal", O_RDONLY) = 40
openat(AT_FDCWD, "pg_wal", O_RDONLY) = 40
openat(AT_FDCWD, "pg_wal", O_RDONLY) = 40
openat(AT_FDCWD, "pg_wal", O_RDONLY) = 40
openat(AT_FDCWD, "pg_subtrans", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 39
strace: Process 26472 detached
# O_DIRECT(Direct I/0) 사용 확인