#3 - WSL에서 시작하는 실전 PostgreSQL 17 + Redis 개발환경 구축, 강의용 데이터 구축
중년개발자
@loxo
22일 전
Next.js, Spring Boot 강의에 필요한 데이터베이스 구축 및 샘플 데이터입니다.
중간중간 설치 시 에러가 있을 수 있습니다. 각자의 환경이 제각각이라서 그럴 땐 AI에게 질문하셔서 끝까지 설치 부탁드립니다.
WSL 개발환경 구축 가이드 (Debian + PostgreSQL 17 + Redis)
WSL 설치부터 PostgreSQL/Redis 구성, 소유자 계정과 애플리케이션 계정 분리, 파티션 설계, 대량 샘플 데이터까지 포함합니다.
⚠️ 이 문서는 *그대로 따라 쳐도 오류 없이 완주**하도록 설계되었습니다.
0. 전체 구조 요약 (먼저 읽기)
역할 분리 원칙 (중요)
| 계정 | 역할 |
|---|---|
| postgres | DBMS 슈퍼유저 (설치/관리 전용) |
| lecture_own | DB/Schema/Table 소유자 |
| lecture_app | Spring Boot 등 애플리케이션 전용 계정 |
Tablespace 분리
| 용도 | Tablespace |
|---|---|
| 데이터 | lecture_data_ts |
| 인덱스 | lecture_index_ts |
1. WSL 설치 (처음부터 시작)
1-1. Windows PowerShell (관리자)
wsl --install -d debian이미 WSL이 있다면 무시됨
설치 후 재부팅 → Debian 실행
💡 Windows에서 Debian(WSL) 실행 방법 보충 설명
Win 키 + X → Windows 터미널 선택
- 관리자 권한이 필요한 경우: Windows 터미널(관리자)
터미널 상단의
▼버튼 클릭목록에서 Debian 선택
또는 터미널에서 바로 실행:
powershellwsl -d debian👉 강의 중에는 Win + X → Windows 터미널 방식을 권장 (PowerShell / WSL / 여러 리눅스를 빠르게 전환 가능)
2. Debian 기본 설정
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget gnupg ca-certificates lsb-release uuid-runtime3. PostgreSQL 17 설치 (Debian 기본 권장)
3-1. 버전 확인
apt show postgresqlDebian 최신 배포판 기준 PostgreSQL 17
3-2. 설치
sudo apt install -y postgresql postgresql-contrib확인:
psql --version3-3. pgroonga 패키지 저장소 추가 및 설치 (PostgreSQL 17 기준)
⚠️ 중요
- pgroonga는 PostgreSQL 메이저 버전과 정확히 매칭되어야 함
- apt show postgresql 버전과 일치하는 pgroonga 패키지 설치
postgresql-17-pgroonga← PostgreSQL 17 전용 패키지- 버전을 명시하지 않으면 다른 버전이 설치되어 extension 생성 실패 가능
- debian 용
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://packages.groonga.org/debian/groonga-archive-keyring.gpg \
| sudo tee /etc/apt/keyrings/groonga.gpg > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/groonga.gpg] \
https://packages.groonga.org/debian/ \
$(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/groonga.list
sudo apt update
sudo apt install -y postgresql-17-pgroonga
- 참고:ubuntu 용
sudo apt install -y software-properties-common
sudo add-apt-repository ppa:groonga/ppa
sudo apt update
sudo apt install -y postgresql-17-pgroonga4. 접근 제어 설정 (PostgreSQL 설치 직후)
⚠️ PostgreSQL 설치 후 바로 설정해야 이후 접속 시 보안이 적용됩니다.
4-1. pg_hba.conf 수정
💡 업계 표준 설정
local+peer: postgres 슈퍼유저는 OS 계정으로 인증 (sudo -i -u postgres)host+scram-sha-256: 애플리케이션 계정은 TCP/IP로 비밀번호 인증
sudo vi /etc/postgresql/17/main/pg_hba.conf# TYPE DATABASE USER ADDRESS METHOD
# Unix socket: postgres 관리자는 peer (OS 사용자 인증)
local all postgres peer
# TCP/IP: 애플리케이션 계정은 host + scram-sha-256
host all postgres 127.0.0.1/32 scram-sha-256
host lecture_db lecture_own 127.0.0.1/32 scram-sha-256
host lecture_db lecture_app 127.0.0.1/32 scram-sha-256
# IPv6 localhost
host lecture_db lecture_own ::1/128 scram-sha-256
host lecture_db lecture_app ::1/128 scram-sha-2564-2. postgresql.conf 수정
sudo vi /etc/postgresql/17/main/postgresql.conflisten_addresses = 'localhost'
shared_buffers = 512MB
max_connections = 2004-3. PostgreSQL 재시작
sudo systemctl restart postgresql5. Tablespace 물리 폴더 생성 (로그인 사용자)
⚠️ 중요: 이 단계는 postgres 계정이 아닌 로그인 사용자가
sudo로 실행합니다.
sudo mkdir -p /data/pgdata /data/pgindex
sudo chown postgres:postgres /data/pgdata /data/pgindex
sudo chmod 700 /data/pgdata /data/pgindex6. PostgreSQL 초기 설정 (postgres 계정)
여기서부터 postgres 계정으로 작업합니다.
sudo -i -u postgres
psql6-1. 기본 보안
ALTER USER postgres WITH PASSWORD 'strong_postgres_password';7. Tablespace 생성 (postgres 실행)
psql 접속 상태에서 실행
CREATE TABLESPACE lecture_data_ts LOCATION '/data/pgdata';
CREATE TABLESPACE lecture_index_ts LOCATION '/data/pgindex';8. Role / Database 생성 (postgres 실행)
CREATE ROLE lecture_own LOGIN PASSWORD 'lecture_own_pw';
CREATE ROLE lecture_app LOGIN PASSWORD 'lecture_app_pw';
CREATE DATABASE lecture_db
OWNER lecture_own
TABLESPACE lecture_data_ts
ENCODING 'UTF8';
-- public 스키마 기본 권한 제거 (보안 기본값)
REVOKE ALL ON SCHEMA public FROM PUBLIC;
REVOKE ALL ON DATABASE lecture_db FROM PUBLIC;
-- extension 사용을 위한 public 스키마 접근은 최소한으로 재허용
GRANT USAGE ON SCHEMA public TO lecture_own;
GRANT USAGE ON SCHEMA public TO lecture_app;
GRANT CONNECT ON DATABASE lecture_db TO lecture_app;
-- lecture_own에게 데이터베이스와 테이블스페이스에 대한 모든 권한 부여
GRANT ALL PRIVILEGES ON DATABASE lecture_db TO lecture_own;
GRANT CREATE ON TABLESPACE lecture_data_ts TO lecture_own;
GRANT CREATE ON TABLESPACE lecture_index_ts TO lecture_own;
-- 소유자 기본 search_path 설정 (extension → public, 도메인 → board)
ALTER ROLE lecture_own SET search_path = board, public;
ALTER ROLE lecture_app SET search_path = board, public;
-- 슈퍼유저(postgres) 권한이 필요한 extension 설치
\c lecture_db
CREATE EXTENSION IF NOT EXISTS "pgroonga";
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";psql 종료 후 postgres 계정에서 나옵니다:
sql\qbashexit
9. Schema 및 권한 (lecture_own 실행)
psql -U lecture_own -d lecture_db -h localhostCREATE SCHEMA board AUTHORIZATION lecture_own;
-- Manage Application User Permissions
GRANT USAGE ON SCHEMA board TO lecture_app;
-- Set DEFAULT PRIVILEGES
ALTER DEFAULT PRIVILEGES FOR ROLE lecture_own IN SCHEMA board
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO lecture_app;
ALTER DEFAULT PRIVILEGES FOR ROLE lecture_own IN SCHEMA board
GRANT USAGE, SELECT ON SEQUENCES TO lecture_app;
-- public 스키마에 생성되는 객체 기본 권한 통제
ALTER DEFAULT PRIVILEGES FOR ROLE lecture_own IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO lecture_app;
ALTER DEFAULT PRIVILEGES FOR ROLE lecture_own IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO lecture_app;10. 사용자(users) 테이블 – Google OAuth 기본 설계
실무 기준 OAuth 사용자 테이블
- Google OAuth 전용 (확장 가능)
- UUID v7 기반 생성 시점 정렬
- 게시글과 FK 연결
- 사용자 삭제 시 게시글은 논리적 보호 (facade)
10-1. UUID v7 생성 함수
PostgreSQL 17 기준 (시간 정렬 UUID)
PostgreSQL 18 에는 uuidV7 함수 내장으로 탑재 되어 있습니다.
SELECT uuidv7();
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- UUIDv7 Generation Function
CREATE OR REPLACE FUNCTION uuidv7()
RETURNS uuid AS $$
DECLARE
timestamp_ms bigint;
unix_ts_ms bytea;
uuid_bytes bytea;
BEGIN
timestamp_ms := (extract(epoch from clock_timestamp()) * 1000)::bigint;
unix_ts_ms := substring(int8send(timestamp_ms) from 3);
uuid_bytes := unix_ts_ms || gen_random_bytes(10);
uuid_bytes := set_byte(uuid_bytes, 6, (get_byte(uuid_bytes, 6) & x'0f'::int) | x'70'::int);
uuid_bytes := set_byte(uuid_bytes, 8, (get_byte(uuid_bytes, 8) & x'3f'::int) | x'80'::int);
RETURN encode(uuid_bytes, 'hex')::uuid;
END
$$ LANGUAGE plpgsql VOLATILE;10-2. users 테이블
CREATE TABLE board.users (
user_id UUID PRIMARY KEY DEFAULT uuidv7(),
provider VARCHAR(20) NOT NULL, -- google
provider_id VARCHAR(100) NOT NULL, -- sub
email VARCHAR(255) NOT NULL,
name VARCHAR(100),
picture_url TEXT,
created_at TIMESTAMP NOT NULL DEFAULT now(),
UNIQUE (provider, provider_id)
) TABLESPACE lecture_data_ts;10-3. OAuth / 조회 최적화 인덱스
CREATE UNIQUE INDEX idx_users_provider
ON board.users (provider, provider_id)
TABLESPACE lecture_index_ts;
CREATE INDEX idx_users_email
ON board.users (email)
TABLESPACE lecture_index_ts;
CREATE INDEX idx_users_created_desc
ON board.users (user_id DESC)
TABLESPACE lecture_index_ts;11. 게시판 테이블 (users 연관, schema.sql 기준)
11-1. 부모 테이블 (HASH 파티션)
CREATE TABLE board.posts (
post_id UUID NOT NULL DEFAULT uuidv7(),
user_id UUID NOT NULL,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now(),
PRIMARY KEY (post_id)
) PARTITION BY HASH (post_id);
-- HASH 파티션 생성 (예: 4개 버킷)
CREATE TABLE board.posts_p0 PARTITION OF board.posts
FOR VALUES WITH (MODULUS 4, REMAINDER 0)
TABLESPACE lecture_data_ts;
CREATE TABLE board.posts_p1 PARTITION OF board.posts
FOR VALUES WITH (MODULUS 4, REMAINDER 1)
TABLESPACE lecture_data_ts;
CREATE TABLE board.posts_p2 PARTITION OF board.posts
FOR VALUES WITH (MODULUS 4, REMAINDER 2)
TABLESPACE lecture_data_ts;
CREATE TABLE board.posts_p3 PARTITION OF board.posts
FOR VALUES WITH (MODULUS 4, REMAINDER 3)
TABLESPACE lecture_data_ts;11-2. FK + CASCADE DELETE (탈퇴 시 즉시 삭제)
ALTER TABLE board.posts
ADD CONSTRAINT fk_posts_user
FOREIGN KEY (user_id)
REFERENCES board.users (user_id)
ON DELETE CASCADE;❗ 사용자 탈퇴 정책 (가정):
- users 레코드는 즉시 DELETE
- 해당 사용자의 posts는 DB 레벨에서 자동 삭제
- 애플리케이션 레이어에서 별도 정합성 처리 불필요
- 탈퇴 = 데이터 완전 제거 (개인정보/게시글 모두 삭제)
12. 검색 전용 테이블 (posts 연관)
-- 🔍 검색 전용 테이블 (쓰기/조회 분리 원칙)
-- posts 와 1:1 관계, 검색 컬럼만 유지
CREATE TABLE board.post_search (
post_id UUID NOT NULL,
search_text TEXT NOT NULL,
PRIMARY KEY (post_id)
) PARTITION BY HASH (post_id);
-- HASH 파티션 (posts 와 동일한 버킷 전략)
CREATE TABLE board.post_search_p0 PARTITION OF board.post_search
FOR VALUES WITH (MODULUS 4, REMAINDER 0)
TABLESPACE lecture_data_ts;
CREATE TABLE board.post_search_p1 PARTITION OF board.post_search
FOR VALUES WITH (MODULUS 4, REMAINDER 1)
TABLESPACE lecture_data_ts;
CREATE TABLE board.post_search_p2 PARTITION OF board.post_search
FOR VALUES WITH (MODULUS 4, REMAINDER 2)
TABLESPACE lecture_data_ts;
CREATE TABLE board.post_search_p3 PARTITION OF board.post_search
FOR VALUES WITH (MODULUS 4, REMAINDER 3)
TABLESPACE lecture_data_ts;
-- pgroonga 인덱스 (파티션 전체에 자동 적용)
CREATE INDEX idx_post_search_text
ON board.post_search
USING pgroonga (search_text)
TABLESPACE lecture_index_ts;13. 대량 샘플 데이터 생성 (무한 스크롤용)
13-1. users 샘플 데이터 생성 (Google OAuth 가정)
강의용 사용자 더미 데이터
- 실제 Google OAuth 구조와 동일
- provider + provider_id 유니크 보장
- 게시글 FK 테스트용
INSERT INTO board.users (provider, provider_id, email, name, picture_url)
SELECT
'google',
'google-sub-' || gs,
'user' || gs || '@example.com',
'강의용 사용자 ' || gs,
'https://picsum.photos/seed/' || gs || '/200/200'
FROM generate_series(1, 100) gs;13-2. posts 샘플 데이터 생성 (무한 스크롤용)
INSERT INTO board.posts (post_id, user_id, title, content, created_at)
SELECT
uuidv7(),
u.user_id,
'강의용 게시글 ' || gs,
'이것은 무한 스크롤 테스트를 위한 본문 데이터 ' || gs,
now() - (gs || ' seconds')::interval
FROM generate_series(1, 20000) gs
CROSS JOIN LATERAL (
SELECT user_id FROM board.users ORDER BY random() LIMIT 1
) u;13-3. post_search 샘플 데이터 동기화
posts 데이터를 기준으로 검색 전용 테이블 동기화 (실무에서는 트리거 또는 애플리케이션 레이어에서 처리)
INSERT INTO board.post_search (post_id, search_text)
SELECT
p.post_id,
u.name || ' ' || p.title || ' ' || p.content
FROM board.posts p
INNER JOIN board.users u ON p.user_id = u.user_id;14. Redis 설치 및 보안
sudo apt install -y redis-server
sudo vi /etc/redis/redis.confrequirepass strong_redis_password
bind 127.0.0.1
protected-mode yessudo systemctl restart redis-server15. 이 상태에서 가능한 강의
- Spring Boot + PostgreSQL 17 연동
- UUID + HASH 파티션 전략
- pgroonga 전문 검색
- Redis 캐시 & 무한 스크롤 API
- 실무 권한 분리 구조 설명
다음과 같이 되어 있어야 합니다.


목차
- #1 - 처음 만드는 PostgreSQL 프로젝트 DB 생성 및 실행문 까지
- #2 - 실무적인 입장에서 대용량 처리 테이블 생성
- #3 - WSL에서 시작하는 실전 PostgreSQL 17 + Redis 개발환경 구축, 강의용 데이터 구축
- #4 - PostgreSQL WAL · INSERT · SELECT · 디스크 관계 정리
- #5 - PostgreSQL Heap Page(8KB)와 CTID - 이해가 쉬운 설명
- #6 - PostgreSQL VACUUM (배큠) - ‘청소’이자 ‘건강검진’이다.
- #7 - PgBouncer로 이해하는 대규모 PostgreSQL 커넥션 풀링
- #8 - PgBouncer 실전 운영 챕터 - Spring Boot 읽기·쓰기 분리 전략
- #9 - WSL PostgreSQL 17 Master-Replica 구축 가이드