긍정적인 사고와 행동으로 선한 영향력을 줄 수 있도록

PostgreSQL

[PostgreSQL] bufferd I/O vs direct I/O

리거니 2025. 11. 25. 09:43

01. PostgreSQL 기본 방식


  1. 기본 동작
  • PostgreSQL은 전통적으로 모든 I/O를 운영 체제의 페이지 캐시(Page Cache)를 사용하는 Buffered I/O 방식으로 처리합니다.
  • 파일을 열 때 open() 시스템 호출에서 O_DIRECT 플래그를 사용하지 않으며, WAL, 테이블, 인덱스 파일 모두 OS 캐시를 거쳐 디스크에 기록됩니다.
  • 동일한 데이터를 Shared Buffer 와 OS Cache 에 이중으로 저장하므로 메모리 낭비 (Double Buffering)
 
  1. Direct I/O 란?
  • 운영체제의 파일 시스템 캐시를 사용하지 않고 데이터를 직접 디스크에 읽거나 쓰는 방법
  • 파일 캐시를 거치지 않고 Shared Buffer 에서 디스크로 직접 엑세스하기 때문에 불필요한 지연(2중 캐시 처리)이 발생하지 않는다.
  • 메모리 효율적 사용: 복사 과정 감소
  • 캐시 관리 단순화: 파일 시스템 캐시를 사용하지 않기 때문에 캐시 관리의 복잡성을 피할 수 있음
  1. 동작 방식
Buffered I/O Shared Buffer -> OS Cache -> Disk OS가 자동으로 캐시 관리
Direct I/O Shared Buffer -> Disk DBMS가 직접 캐시 관리

 

  1. 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) 사용 확인