DBA 실무/PostgreSQL

PostgreSQL, 설치할 때 정한 걸로 3년을 삽니다 — 설치부터 초기 설정까지

isony 2026. 8. 10. 21:28
반응형

PostgreSQL, 설치할 때 정한 걸로 3년을 삽니다 — 설치부터 초기 설정까지

PostgreSQL 실무 시리즈 ① 이 글의 모든 명령어와 출력은 실제로 실행한 결과입니다.
검증 환경: PostgreSQL 16.2 / Ubuntu 22.04 / UTF8

 

PostgreSQL 설치는 쉽습니다. apt install postgresql 한 줄이면 끝나니까요.

문제는 그 다음입니다. 설치 시점에 정하고 나면 두 번 다시 못 바꾸는 옵션이 몇 개 있는데, 하필 그게 기본값으로 두면 안 되는 것들입니다. 그리고 그 대가는 보통 2~3년 뒤, 데이터가 수백 GB로 불어난 다음에 청구됩니다.

이 글에서는 설치부터 첫 접속까지를 다루되, "왜 이렇게 하는가" 에 집중합니다. 명령어만 필요하시면 맨 아래 체크리스트로 바로 가셔도 됩니다.

 

1. 배포판 기본 패키지를 쓰지 마세요

Ubuntu 22.04에서 기본 저장소를 확인해 보면 이렇습니다.

$ apt-cache policy postgresql
postgresql:
  Installed: (none)
  Candidate: 14+238

14입니다. PostgreSQL 14는 2026년 11월에 지원이 끝납니다. 지금 새로 구축하면서 이걸 깔면, 서비스를 오픈하기도 전에 EOL 카운트다운이 시작됩니다.

메이저 버전은 릴리스 후 5년간 지원됩니다. 그래서 신규 구축이면 최소한 16 이상, 가급적 17을 쓰는 게 맞습니다.

Ubuntu / Debian — PGDG 공식 저장소

sudo apt install -y curl ca-certificates
sudo install -d /usr/share/postgresql-common/pgdg
sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc \
     --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc

. /etc/os-release
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] \
https://apt.postgresql.org/pub/repos/apt $VERSION_CODENAME-pgdg main" \
  | sudo tee /etc/apt/sources.list.d/pgdg.list

sudo apt update
sudo apt install -y postgresql-17 postgresql-contrib-17

RHEL / Rocky / Alma 9

sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo dnf -qy module disable postgresql          # ← 이거 빼면 충돌합니다
sudo dnf install -y postgresql17-server postgresql17-contrib

RHEL 계열에서 dnf module disable postgresql을 빠뜨리면 OS 기본 모듈과 충돌해서 설치가 꼬입니다. 검색하면 나오는 설치 가이드 중에 이 줄이 빠진 게 꽤 많습니다.

contrib를 반드시 같이 설치하세요

postgresql-contrib에는 이런 게 들어 있습니다.

확장 용도

pg_stat_statements 쿼리별 누적 실행시간 통계. 슬로우 쿼리를 찾는 유일한 제대로 된 수단
pgstattuple 테이블 팽창 정밀 측정
pg_buffercache 버퍼 캐시 내용 조회
pg_trgm LIKE '%검색어%' 인덱싱
pgcrypto 암호화 함수

이걸 안 깔고 운영하다가 장애가 나면, "어떤 쿼리가 문제인지" 자체를 알 수 없습니다. 나중에 넣으려면 shared_preload_libraries 변경 → 재기동이 필요해서, 결국 장애 상황에서 서비스를 한 번 더 내려야 합니다.

참고로 이 시리즈를 쓰면서 쓴 검증 환경에는 contrib가 없었습니다(샌드박스 제약). 덕분에 pg_stat_statements 없이 슬로우 쿼리를 추적하는 게 얼마나 답답한지 몸으로 겪었습니다. 로그를 뒤지는 것 말고는 방법이 없습니다.

 

2. initdb — 여기서 정하면 되돌릴 수 없습니다

PostgreSQL에서 클러스터는 하나의 데이터 디렉터리(PGDATA)와 그 안의 여러 데이터베이스를 뜻합니다. initdb는 이 클러스터를 만드는 명령입니다.

initdb -D /var/lib/pgsql/17/data \
       -U postgres \
       --encoding=UTF8 \
       --locale=C \
       --data-checksums

실제 실행 결과입니다.

Data page checksums are enabled.
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... Asia/Seoul
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

initdb: warning: enabling "trust" authentication for local connections
initdb: hint: You can change this by editing pg_hba.conf or using the option -A, ...

Success. You can now start the database server using:
    pg_ctl -D /var/lib/pgsql/17/data -l logfile start

여기서 짚어야 할 게 셋입니다.

 

① --data-checksums — 무조건 켜세요

디스크에서 비트가 조용히 뒤집히는 일(silent corruption)은 생각보다 자주 일어납니다. 체크섬이 없으면 데이터가 깨져도 아무도 모릅니다. 깨진 값이 그대로 조회되고, 그대로 백업되고, 그대로 복제됩니다.

체크섬을 켜면 손상된 페이지를 읽을 때 에러를 냅니다. 문제를 "지금" 알게 되는 거죠.

$ psql -c "SHOW data_checksums;"
 data_checksums
----------------
 on

성능 오버헤드는 수 % 수준입니다. 이걸 아끼려다 데이터 정합성을 잃는 건 나쁜 거래입니다.

PostgreSQL 18부터는 기본값이 on입니다. 17 이하라면 직접 지정해야 합니다. 이미 만든 클러스터에 나중에 켜려면 pg_checksums 명령으로 가능하지만, DB를 정지시킨 상태에서 전체 페이지를 다시 써야 합니다. 100GB면 서비스 중단 시간이 꽤 깁니다. 처음에 켜세요.

 

② --locale=C — 한국어 서비스에서도 이게 맞습니다

여기가 제일 오해가 많은 지점입니다. "한국어 데이터니까 ko_KR.UTF-8을 써야 하는 거 아닌가?" 싶으실 텐데요.

실제로 C 로케일에서 한글이 어떻게 정렬되는지 보시죠.

SELECT unnest(ARRAY['하늘','가방','나무','다리','마을']) AS w ORDER BY 1;
  w
------
 가방
 나무
 다리
 마을
 하늘

정확히 가나다순입니다.

우연이 아닙니다. 유니코드의 한글 음절 블록(U+AC00~U+D7A3)이 초성-중성-종성 순서로 배열되어 있어서, 코드포인트 순서가 곧 한글 사전순입니다. C 로케일은 바이트 순서로 정렬하는데, UTF-8은 코드포인트 순서를 보존하므로 결과가 같아집니다.

그럼 C 로케일에서 뭐가 다르냐면, 영문 대소문자입니다.

-- C 로케일 (기본)
SELECT w FROM unnest(ARRAY['apple','Banana','apricot','Avocado','cherry']) AS t(w) ORDER BY w;
 Avocado
 Banana
 apple
 apricot
 cherry

대문자가 전부 앞으로 나옵니다. 사람이 기대하는 순서는 아니죠. 이건 컬럼 단위로 COLLATE를 지정하면 해결됩니다.

SELECT w FROM unnest(ARRAY['apple','Banana','apricot','Avocado','cherry']) AS t(w)
ORDER BY w COLLATE "en_US.utf8";
 apple
 apricot
 Avocado
 Banana
 cherry

 

C 로케일을 쓰면 얻는 것

첫째, LIKE '접두어%' 가 인덱스를 그냥 탑니다.

EXPLAIN (COSTS OFF) SELECT * FROM customers WHERE email LIKE 'user100%';
 Bitmap Heap Scan on customers
   Filter: (email ~~ 'user100%'::text)
   ->  Bitmap Index Scan on customers_email_key
         Index Cond: ((email >= 'user100'::text) AND (email < 'user101'::text))

일반 인덱스가 접두어 검색 범위로 변환됐습니다. C 이외의 로케일에서는 이게 안 됩니다. text_pattern_ops 연산자 클래스를 지정한 별도 인덱스를 하나 더 만들어야 합니다.

-- C 로케일이 아니라면 이런 인덱스가 추가로 필요합니다
CREATE INDEX idx_customers_email_pattern ON customers(email text_pattern_ops);

둘째, 문자열 비교가 빠릅니다. 로케일 규칙을 적용하는 비교는 단순 바이트 비교보다 훨씬 무겁습니다. 정렬이 많은 워크로드에서 체감 차이가 납니다.

셋째, 이게 제일 중요한데 — glibc 업그레이드 사고를 원천 차단합니다.

OS를 업그레이드하면 glibc의 정렬 규칙이 바뀝니다. 그러면 기존에 만들어 둔 문자열 인덱스는 논리적으로 깨진 상태가 됩니다. 인덱스는 옛 규칙으로 정렬돼 있는데 검색은 새 규칙으로 하니까, 분명히 있는 데이터가 조회되지 않습니다. UNIQUE 제약이 뚫려서 중복이 들어가기도 합니다.

에러도 안 납니다. 그냥 조용히 틀린 답을 줍니다. 겪어본 사람들이 하나같이 치를 떠는 이유입니다.

-- 지금 클러스터의 로케일 확인
SELECT datname, datcollate, datctype, pg_encoding_to_char(encoding) AS encoding
FROM pg_database WHERE datname = 'shop';
 datname | datcollate | datctype | encoding
---------+------------+----------+----------
 shop    | C          | C        | UTF8

C 로케일은 정렬 규칙이 "바이트 순서"로 고정이라 OS가 바뀌어도 변하지 않습니다.

PostgreSQL 15 이상이면 ICU 콜레이션이라는 더 나은 선택지가 있습니다.

initdb --locale-provider=icu --icu-locale=ko-KR --encoding=UTF8 ...

ICU는 정렬 규칙 버전이 DB에 기록되어서, 규칙이 바뀌면 PostgreSQL이 경고를 띄웁니다. glibc처럼 조용히 깨지지 않습니다. 다만 ICU 라이브러리를 포함해 빌드된 패키지여야 합니다. 확인은 이렇게:

SELECT collname FROM pg_collation WHERE collprovider = 'i' LIMIT 5;

(ICU 미지원 빌드에서는 CREATE COLLATION ... provider = icu 시 ERROR: ICU is not supported in this build가 납니다.)

정리하면 — 클러스터는 C 또는 ICU로 만들고, 로케일별 정렬이 필요한 곳에만 COLLATE를 붙이세요.

 

③ 마지막 경고 줄을 읽으세요

initdb: warning: enabling "trust" authentication for local connections

trust는 "비밀번호 없이 아무나 들어와도 된다"는 뜻입니다. 로컬 접속 한정이지만, 서버에 셸을 딸 수 있는 사람은 누구나 슈퍼유저로 DB에 들어옵니다. 애플리케이션 계정 하나만 뚫려도 끝입니다.

이건 5장에서 반드시 고칩니다.

 

3. postgresql.conf — 처음 손댈 것만

먼저, 파일 위치는 SQL로 확인하세요

배포판마다 경로가 다릅니다. 추측하지 말고 물어보세요.

SHOW config_file;
SHOW hba_file;
SHOW data_directory;
                    config_file
----------------------------------------------------
 /var/lib/pgsql/17/data/postgresql.conf

                    hba_file
------------------------------------------------
 /var/lib/pgsql/17/data/pg_hba.conf

Debian/Ubuntu 계열은 설정이 /etc/postgresql/17/main/에, 데이터가 /var/lib/postgresql/17/main/에 따로 있습니다. RHEL 계열은 둘 다 PGDATA 안에 있습니다. 그래서 더더욱 SQL로 확인하는 게 확실합니다.

설정을 바꾸는 3가지 방법

-- 1) 파일 직접 편집 (전통적)
-- 2) ALTER SYSTEM (postgresql.auto.conf에 기록, 파일보다 우선)
ALTER SYSTEM SET log_min_duration_statement = '200ms';
SELECT pg_reload_conf();

-- 3) 세션/롤 단위
SET work_mem = '256MB';                          -- 현재 세션만
ALTER ROLE reporter SET statement_timeout = '10min';   -- 해당 롤 접속 시마다

ALTER SYSTEM은 편하지만 파일에 뭐가 적혀 있는지와 실제 동작이 달라질 수 있어서 헷갈립니다. 현재 값은 항상 pg_settings로 확인하세요.

재기동이 필요한지 아닌지 — context 컬럼

이게 실무에서 정말 유용한데 의외로 안 알려져 있습니다.

SELECT name, setting, context FROM pg_settings
WHERE name IN ('shared_buffers','work_mem','max_connections','random_page_cost',
               'log_min_duration_statement','wal_level','archive_command',
               'effective_cache_size','autovacuum_naptime','listen_addresses')
ORDER BY context, name;
            name            |  setting  |  context
----------------------------+-----------+------------
 listen_addresses           | localhost | postmaster    ← 재기동 필요
 max_connections            | 100       | postmaster    ← 재기동 필요
 shared_buffers             | 16384     | postmaster    ← 재기동 필요
 wal_level                  | replica   | postmaster    ← 재기동 필요
 archive_command            | test ! ...| sighup        ← reload로 충분
 autovacuum_naptime         | 60        | sighup        ← reload로 충분
 log_min_duration_statement | 200       | superuser     ← 세션에서도 변경 가능
 effective_cache_size       | 524288    | user          ← 아무나 세션에서 변경 가능
 random_page_cost           | 4         | user
 work_mem                   | 4096      | user

context 의미 개수

user 세션에서 SET으로 즉시 변경 141
sighup pg_reload_conf() 로 반영 94
postmaster 재기동 필요 57
superuser 슈퍼유저만 세션 변경 가능 44
internal 컴파일 시 결정, 변경 불가 18

sighup 파라미터는 무중단으로 바뀝니다. 실제로 해보면:

SHOW autovacuum_naptime;        -- 1min
ALTER SYSTEM SET autovacuum_naptime = '30s';
SELECT pg_reload_conf();
SHOW autovacuum_naptime;        -- 30s

postmaster 파라미터는 reload해도 안 먹습니다. 대신 PostgreSQL이 "재기동 대기 중"이라고 알려줍니다.

ALTER SYSTEM SET shared_buffers = '256MB';
SELECT pg_reload_conf();

SELECT name, setting, unit, pending_restart FROM pg_settings WHERE pending_restart;
      name      | setting | unit | pending_restart
----------------+---------+------+-----------------
 shared_buffers | 16384   | 8kB  | t

pending_restart가 t인 항목이 있는지 배포 후 확인하는 습관을 들이세요. "설정을 바꿨는데 왜 안 먹지?"의 대부분이 이겁니다.

초기 설정 — 메모리 16GB 서버 기준

# --- 접속 ---
listen_addresses = 'localhost'     # 앱 서버가 따로면 해당 대역만
port = 5432
max_connections = 100              # 무작정 늘리지 마세요

# --- 메모리 ---
shared_buffers = 4GB               # 물리 메모리의 25%
effective_cache_size = 12GB        # 75%. 실제 할당이 아니라 옵티마이저 힌트
work_mem = 16MB                    # 세션×정렬연산 단위로 잡힙니다
maintenance_work_mem = 1GB         # VACUUM / CREATE INDEX 속도에 직결

# --- 디스크 ---
random_page_cost = 1.1             # SSD면 반드시 낮추세요 (기본 4.0은 HDD 가정)
effective_io_concurrency = 200

# --- WAL ---
wal_level = replica
max_wal_size = 4GB
checkpoint_timeout = 15min
checkpoint_completion_target = 0.9

# --- 로깅 (운영 필수) ---
logging_collector = on
log_directory = 'log'
log_min_duration_statement = 200   # 200ms 넘는 쿼리 전부 기록
log_line_prefix = '%m [%p] %u@%d %a %h '
log_checkpoints = on
log_lock_waits = on
log_temp_files = 0
log_autovacuum_min_duration = 0

# --- 타임아웃 (사고 예방) ---
lock_timeout = 5s
idle_in_transaction_session_timeout = 5min

이 중에서 가장 효과가 큰 두 줄

random_page_cost = 1.1

기본값 4.0은 회전 디스크를 가정한 값입니다. SSD 서버에서 이걸 그대로 두면 옵티마이저가 인덱스 스캔을 부당하게 싫어하고 순차 스캔을 고릅니다. 인덱스를 만들었는데 안 타는 이유 중 상당수가 이겁니다.

log_min_duration_statement = 200

처음부터 켜세요. 장애가 터진 다음에 켜면 이미 늦습니다. 원인 쿼리는 이미 지나갔으니까요.

work_mem은 함정이 있습니다. "커넥션당"이 아니라 "쿼리 안의 정렬/해시 연산 하나당" 입니다. 복잡한 쿼리 하나가 정렬 3개 + 해시 2개를 쓰면 work_mem × 5를 잡습니다. 여기에 동시 접속 100개를 곱하면 서버가 OOM으로 죽습니다. 전역은 보수적으로(8~32MB) 두고, 무거운 배치에서만 SET LOCAL work_mem = '512MB'로 올리세요.

OS 레벨에서 하나만 — THP 끄기

echo never > /sys/kernel/mm/transparent_hugepage/enabled

Transparent Huge Pages가 켜져 있으면 예측 불가능한 지연 스파이크가 생깁니다. PostgreSQL뿐 아니라 거의 모든 DB의 공통 권고사항입니다. 부팅 시 적용되도록 systemd 유닛이나 GRUB 파라미터로 고정하세요.

 

4. pg_hba.conf — 여기서 사고가 납니다

initdb가 만들어 준 기본 파일입니다.

# TYPE  DATABASE        USER            ADDRESS                 METHOD
local   all             all                                     trust
host    all             all             127.0.0.1/32            trust
host    all             all             ::1/128                 trust
local   replication     all                                     trust
host    replication     all             127.0.0.1/32            trust
host    replication     all             ::1/128                 trust

전부 trust입니다. 이 상태로 운영에 올라간 서버, 생각보다 많습니다.

규칙 ①: 위에서부터 첫 매칭이 이깁니다

# TYPE  DATABASE  USER      ADDRESS         METHOD
local   all       postgres                  peer
host    shop      app_rw    10.0.1.0/24     scram-sha-256
host    shop      reporter  10.0.2.15/32    scram-sha-256
host    all       all       0.0.0.0/0       reject

pg_hba.conf는 위에서부터 순서대로 평가하고, 처음 매칭된 줄로 확정합니다. 아래에 더 엄격한 규칙이 있어도 소용없습니다.

그래서 맨 위에 trust 줄이 하나 남아 있으면 그 아래 모든 규칙이 무의미해집니다. 흔한 사고 패턴이 이렇습니다: 보안 설정을 열심히 추가했는데, 기본 trust 줄을 지우는 걸 잊는 것.

마지막에 reject 줄을 두는 걸 습관화하세요. 의도하지 않은 접속을 명시적으로 막습니다.

규칙 ②: md5 말고 scram-sha-256

md5는 레거시입니다. PostgreSQL 10부터 scram-sha-256이 들어왔고, 14부터는 기본값입니다.

SHOW password_encryption;
 scram-sha-256

실제로 어떻게 저장되는지 확인할 수 있습니다.

SELECT rolname, left(rolpassword, 14) AS stored_as
FROM pg_authid WHERE rolpassword IS NOT NULL;
 rolname  |   stored_as
----------+----------------
 repl     | SCRAM-SHA-256$
 reporter | SCRAM-SHA-256$

그리고 여기가 진짜 함정입니다

password_encryption을 바꿔도 기존 비밀번호는 그대로입니다.

실험해 봤습니다. md5로 계정을 하나 만들고:

SET password_encryption = 'md5';
CREATE ROLE legacy_user LOGIN PASSWORD 'secret123';
   rolname   | stored_as
-------------+------------
 legacy_user | md57e8388b     ← md5 해시
 reporter    | SCRAM-SHA-

이제 password_encryption을 scram-sha-256으로 되돌리고 다시 확인하면:

SHOW password_encryption;          -- scram-sha-256
SELECT rolname, left(rolpassword,10) FROM pg_authid WHERE rolname='legacy_user';
   rolname   | stored_as
-------------+------------
 legacy_user | md57e8388b     ← 여전히 md5

설정만 바꿔서는 아무 일도 일어나지 않습니다. 비밀번호를 다시 설정해야 전환됩니다.

ALTER ROLE legacy_user PASSWORD 'secret123';   -- 같은 비밀번호라도 다시 설정
   rolname   |   stored_as
-------------+----------------
 legacy_user | SCRAM-SHA-256$

구버전에서 업그레이드해 온 클러스터라면 반드시 확인하세요. pg_hba.conf를 scram-sha-256으로 바꿨는데 md5 해시만 있는 계정은 아예 로그인이 안 됩니다. 배포 직후 장애로 이어집니다.

전수 점검 쿼리:

SELECT rolname,
       CASE WHEN rolpassword LIKE 'SCRAM-SHA-256$%' THEN 'scram'
            WHEN rolpassword LIKE 'md5%'           THEN 'md5 (전환 필요)'
            ELSE '없음/기타' END AS pw_type
FROM pg_authid WHERE rolcanlogin ORDER BY 2, 1;

인증 방식 정리

METHOD 언제

scram-sha-256 기본 선택. 앱/사용자 계정
peer 로컬 유닉스 소켓. OS 계정명 = DB 롤명일 때. postgres 유지보수 접속에 편리
cert 클라이언트 인증서. 보안 요구가 높을 때
trust 운영 금지
md5 레거시 호환이 정말 필요할 때만

수정 후 반영

psql -c "SELECT pg_reload_conf();"

재기동 필요 없습니다. 다만 이미 접속된 세션에는 적용되지 않습니다.

적용 결과는 이 뷰로 확인할 수 있습니다 (PostgreSQL 10+).

SELECT line_number, type, database, user_name, address, auth_method, error
FROM pg_hba_file_rules;
 line_number | type  |   database    | user_name |  address  | auth_method | error
-------------+-------+---------------+-----------+-----------+-------------+-------
         117 | local | {all}         | {all}     |           | trust       |
         119 | host  | {all}         | {all}     | 127.0.0.1 | trust       |
         121 | host  | {all}         | {all}     | ::1       | trust       |
         124 | local | {replication} | {all}     |           | trust       |

error 컬럼이 비어 있지 않으면 문법 오류입니다. reload 전에 이걸로 검증하는 습관을 들이면 "설정 고치다 아무도 접속 못 하게 되는" 사고를 막을 수 있습니다.

 

5. 기동하고 확인하기

sudo systemctl enable --now postgresql-17
sudo systemctl status postgresql-17

# 또는 pg_ctl 직접
pg_ctl -D $PGDATA -l $PGDATA/startup.log start

접속해서 확인:

SELECT version();
 PostgreSQL 16.2 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 10.2.1, 64-bit

최소한의 계정 설계

슈퍼유저로 애플리케이션을 붙이지 마세요. SQL 인젝션 하나로 COPY ... FROM PROGRAM을 통해 서버 셸이 열립니다.

-- 1) 권한 묶음 롤 (로그인 불가)
CREATE ROLE app_rw NOLOGIN;
CREATE ROLE app_ro NOLOGIN;

GRANT CONNECT ON DATABASE shop TO app_rw, app_ro;
GRANT USAGE ON SCHEMA public TO app_rw, app_ro;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_rw;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_ro;

-- 2) 앞으로 만들 테이블에도 자동 적용 ← 이거 빼먹으면 매번 GRANT 해야 합니다
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO app_ro;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_rw;

-- 3) 실제 접속 계정
CREATE ROLE api LOGIN PASSWORD '...';
GRANT app_rw TO api;

-- 4) 폭주 방지
ALTER ROLE api SET statement_timeout = '30s';
ALTER ROLE api SET idle_in_transaction_session_timeout = '60s';

ALTER DEFAULT PRIVILEGES는 꼭 넣으세요. 이게 없으면 새 테이블을 만들 때마다 조회 계정에 GRANT를 다시 해야 합니다. "리포팅 계정이 새로 만든 테이블만 못 봐요" 문의의 대부분이 이것 때문입니다.

주의할 점은 이 설정을 실행한 롤이 만든 테이블에만 적용된다는 겁니다. 마이그레이션 도구가 다른 롤로 테이블을 만든다면 그 롤로도 걸어줘야 합니다.

 

6. 배포 전 체크리스트

□ PGDG 저장소로 최신 지원 버전을 설치했는가 (배포판 기본 X)
□ postgresql-contrib 를 함께 설치했는가
□ initdb 에 --data-checksums 를 줬는가        → SHOW data_checksums
□ 로케일이 C 또는 ICU 인가                     → SELECT datcollate FROM pg_database
□ 인코딩이 UTF8 인가
□ pg_hba.conf 에 trust 가 남아 있지 않은가
□ 인증이 scram-sha-256 인가
□ 기존 계정이 전부 SCRAM 해시로 저장돼 있는가  → pg_authid 조회
□ pg_hba_file_rules 에 error 가 없는가
□ log_min_duration_statement / log_checkpoints / log_lock_waits 를 켰는가
□ random_page_cost 를 SSD 에 맞게 낮췄는가
□ pending_restart 가 비어 있는가
□ 앱 계정이 슈퍼유저가 아니고, ALTER DEFAULT PRIVILEGES 를 걸었는가
□ THP 를 껐는가
□ statement_timeout / idle_in_transaction_session_timeout 을 설정했는가

 

마치며

정리하면, 되돌릴 수 없는 결정은 세 개입니다.

  1. 데이터 체크섬 — 나중에 켜려면 서비스를 세워야 합니다
  2. 로케일 — 바꾸려면 클러스터를 다시 만들고 데이터를 전부 옮겨야 합니다
  3. 인코딩 — 마찬가지입니다

나머지는 다 나중에 고칠 수 있습니다. 그러니 이 셋만은 처음에 제대로 잡고 시작하세요.

다음 글에서는 자료형과 제약조건을 다룹니다. numeric과 float의 차이 때문에 정산이 1원씩 틀어지는 이유, timestamp와 timestamptz를 잘못 고르면 글로벌 서비스에서 무슨 일이 생기는지, 그리고 NOT IN에 NULL이 하나 섞였을 때 쿼리 결과가 통째로 사라지는 현상을 실제 출력으로 보여드리겠습니다.

 

반응형