본문으로 건너뛰기

한 가지 목적을 해내는 절차, 하우투 가이드 - 개발자를 위한 기술문서 입문 ep.03

하우투 가이드의 독자는 이미 무엇을 하려는지 알고 옵니다.

“PostgreSQL을 9에서 14로 마이그레이션하려고 한다.” “Next.js 앱을 Cloudflare에 배포하려고 한다.” “도커 컨테이너에서 인증서를 자동 갱신하려고 한다.” 이런 사람들입니다. 그들에게 필요한 건 가 아니라 어떻게입니다. 그것도 가능한 한 가장 짧은 거리로.

하우투가 무너지는 가장 흔한 방식은 두 가지입니다. 첫째, 튜토리얼처럼 설명하기 시작합니다. 둘째, 분기를 잘못 다뤄 독자가 길을 잃습니다. 이번 편은 그 둘을 피하는 방법입니다.


하우투의 다섯 칸 골격

하우투는 다섯 칸으로 단순화할 수 있습니다.

하우투 가이드의 표준 골격 다이어그램. 다섯 단계가 흐름으로 연결된다. 01 GOAL 목적은 "이 글을 다 따라가면 무엇을 해낸 것인가"를 답한다. 02 PREREQUISITE 전제는 "무엇을 알고 있고, 무엇이 준비되어 있어야 하나"를 명시한다. 03 STEPS 단계는 "이 순서대로 하면 목적에 닿는다"는 핵심으로, 하나의 단계 = 하나의 동작이며 단계 안에 분기가 있어도 되고, 번호를 매기며 그 단계에서 왜 이걸 하는지를 한 줄로 적는다. 04 VERIFICATION 검증은 "제대로 됐는지 어떻게 확인하나"를 다룬다. 05 NEXT 다음 행동은 "여기서 다음에 갈 곳, 또는 롤백 방법"을 안내한다.

각 칸을 한 번씩 짚습니다.

1. 목적 — 해낸 것을 명시

하우투의 제목은 과업 동사로 시작합니다. “PostgreSQL 마이그레이션”, “Cloudflare에 배포하기”, “인증서 자동 갱신”. 명사형 제목보다 동사형 제목이 검색에도 잘 잡힙니다.

첫 한 단락은 완료의 상태를 정의합니다.

이 가이드를 따라가면 PostgreSQL 9에서 14로 무중단 마이그레이션을 완료한 상태가 됩니다.
구체적으로:

- 기존 9 인스턴스가 read-only로 잠긴 상태
- 14 인스턴스가 동기화된 상태로 트래픽을 받는 중
- 롤백 절차가 준비되어 있는 상태

무엇이 끝난 상태인지가 명확할수록 독자는 자기가 어디로 가는지 압니다.

2. 전제 — 준비물을 명시

튜토리얼의 전제는 최소화하지만, 하우투의 전제는 정확하게입니다. 독자가 이미 무언가를 알고 있다고 가정해도 됩니다. 다만 무엇을 가정하는지를 명시합니다.

## 전제

- PostgreSQL 9.6 인스턴스가 동작 중
- pg_basebackup, pglogical에 익숙함 (몰라도 따라갈 수 있지만 시간이 더 걸립니다)
- 슈퍼유저 권한
- 충분한 디스크 (현재 데이터 크기 × 1.5)

전제가 빠지면 독자는 절반쯤 갔다가 막힙니다. 가장 큰 시간 낭비는 진작에 알았어야 할 것을 중간에 만나는 일입니다.

3. 단계 — 번호 + 동사 + 이유 한 줄

각 단계는 다음 세 부분을 갖습니다.

  • 번호: 1, 2, 3 … 명확한 순서
  • 동사로 시작하는 제목: “데이터베이스 백업하기”, “동기화 시작하기”
  • 그 단계에서 이걸 하는가 한 줄
### 3. pglogical 노드 등록하기

새 14 인스턴스를 기존 9 인스턴스의 subscriber로 등록합니다.
이 단계가 끝나면 두 인스턴스가 같은 데이터를 가지게 됩니다.

```sql
SELECT pglogical.create_subscription(
  subscription_name := 'pg14_sub',
  provider_dsn := 'host=pg9 port=5432 dbname=app user=replicator'
);
```

이유 한 줄이 있으면 독자는 자기가 이걸 하는지 알고 진행합니다. 도중에 막혀도 무엇을 디버깅해야 하는지 판단할 수 있습니다.

4. 검증 — 실패 신호까지 함께

각 중요한 단계 끝, 그리고 글 전체의 끝에는 검증이 들어갑니다. 그리고 검증은 성공 신호실패 신호를 함께 다뤄야 좋습니다.

### 검증

다음 쿼리로 동기화 상태를 확인합니다.

```sql
SELECT * FROM pglogical.show_subscription_status();
```

**성공 신호**: `status` 컬럼이 `replicating`
**실패 신호**: `down` 또는 `disabled` — 이 경우 [트러블슈팅 섹션](#트러블슈팅)으로

실패 신호 없이 성공 신호만 적으면, 실패한 독자는 자기가 실패했다는 것조차 모르고 다음 단계로 갑니다. 이는 운영 환경에서 사고로 이어집니다.

5. 다음 행동 — 완료 후롤백

하우투의 끝에는 두 가지가 들어가야 합니다.

완료 후 무엇을 해야 하는가. 이 작업이 끝났다면 보통 그다음이 있습니다. “이제 클라이언트의 connection string을 바꾸세요” 같은 것.

롤백은 어떻게 하는가. 이 작업이 실패했을 때, 어떻게 되돌리는가. 이걸 작업을 시작하기 전에 알려주는 게 가장 좋습니다.

## 롤백

이 작업 중 문제가 발생하면 다음 절차로 9 인스턴스로 되돌립니다.

1. ...
2. ...
3. ...

롤백은 약 N분 안에 완료됩니다.

운영 작업의 하우투는 롤백 없이 출시되지 않아야 합니다.

다섯 칸을 한 페이지에 — PG 9 → 14 시뮬레이션

다섯 칸이 따로 떨어져 있으면 추상적입니다. 가상의 GreenDeck 데이터팀이 운영 중인 100GB 짜리 PostgreSQL을 9.6에서 14로 옮기는 하우투 한 장을 시뮬레이션해봅니다.

# PostgreSQL 9.6 → 14.x 무중단 마이그레이션 (자체 운영, pglogical 사용)

## 완료 후 상태
- 9.6 인스턴스: read-only, 24시간 대기 후 폐기
- 14.x 인스턴스: 트래픽 100%, 동기화 lag < 1s
- 롤백 스크립트: /ops/runbooks/pg-rollback.sh 위치

## 전제
- 데이터 크기 100GB, 디스크 여유 ≥ 150GB
- pglogical extension 양쪽 인스턴스 모두 설치 가능
- 슈퍼유저 권한, maintenance window 1시간

## 단계
1. 14.x 인스턴스 프로비저닝 (15분)
2. pglogical 노드 등록 (5분) — 두 노드가 같은 데이터를 가지게 됨
3. 초기 동기화 + 증분 따라잡기 (40분) — `replication_lag` 메트릭 모니터
4. 애플리케이션 connection string 교체 (1분) — 9.6은 read-only로 잠금
5. 24시간 관찰 후 9.6 폐기

## 검증
- 성공: `SELECT * FROM pglogical.show_subscription_status()` → status = replicating
- 실패: status = down → 롤백 절차로

## 롤백
1. connection string을 9.6으로 되돌림
2. 9.6의 read-only 잠금 해제
3. 14.x의 변경분은 폐기 (이 시점에서 5분 이내의 데이터 손실 허용 정책 적용)

이렇게 한 페이지에 모이면 글의 모양 자체로 하우투가 무엇인지 보입니다. 첫 화면에서 완료 상태가 보이고, 단계는 번호로 매겨졌고, 각 단계에 시간 추정이 있고, 검증과 롤백이 끝까지 같이 있습니다. 다섯 칸을 모두 채운 글은 시작도 끝도 명확합니다.


분기를 다루는 세 가지 방식

하우투가 망가지는 가장 흔한 두 번째 이유가 분기입니다. “Mac이면 X, Windows면 Y, 그런데 ARM이면 Z, …” 같은 분기가 단계마다 들어오면 글은 빠르게 미궁이 됩니다.

분기를 다루는 패턴은 셋입니다.

하우투에서 분기를 다루는 세 가지 방식 다이어그램. 패턴 1은 OK 패턴으로 "초기에 분기, 곧 합류". OS별 설치처럼 한 번만 갈라지고 다음 단계부터 같은 길로 합쳐진다. 흐름은 시작 → (Mac / Windows) → 합류 → 검증. 패턴 2도 OK 패턴으로 "아예 글을 둘로 분리". 분기가 처음부터 끝까지 갈라지면 두 개의 하우투로 나눈다. 흐름은 진입 페이지 → (Mac 하우투 / Windows 하우투). 패턴 3은 금지 패턴으로 "단계마다 분기, 합류, 재분기". 매 단계마다 OS·환경·버전을 분기하면 독자는 매번 어느 경로인지 확인하며 멈춘다. 흐름은 시작 → (A / B) → (A1 / B1) → (A1a / A1b / B1a). 이 패턴이 금지인 이유: 독자가 자기 위치를 매번 재확인해야 하고, "이게 내 경로 맞나" 점검 비용이 작업 비용을 넘어선다. 해결책: 분기가 N개 단계로 펼쳐지면 패턴 2로 분리, 분기가 한 단계에서만 일어나면 패턴 1로 합류, 분기가 정말 산재해야 한다면 그건 하우투가 아니라 레퍼런스에 가까운 글일 수 있다.

패턴 1: 초기에 갈라지고 곧 합류

OS, 패키지 매니저, 환경 같은 초기에만 갈라지는 분기는 첫 번째 단계 안에서 처리하고 합류시킵니다.

### 1. 의존성 설치

**Mac**:
```bash
brew install pgloader
```

**Ubuntu**:
```bash
apt install pgloader
```

### 2. 설정 파일 만들기

(여기부터는 두 OS 공통)

패턴 2: 글을 통째로 둘로 나누기

분기가 한 단계가 아니라 글 전체에 걸쳐 다르면, 그건 사실 다른 글입니다. 두 글로 나눕니다.

# PostgreSQL 마이그레이션 — 가이드 모음

상황에 맞는 가이드를 골라주세요.

- [9 → 14 (자체 운영)](./self-hosted.md)
- [AWS RDS PostgreSQL 9 → 14](./aws-rds.md)
- [Heroku Postgres 9 → 14](./heroku.md)

진입 페이지를 따로 두고, 각 가이드는 자기 경로에만 집중하게 합니다. 글 하나하나는 짧고 명확해집니다.

패턴 3 (금지): 매 단계 분기

가장 자주 보이고 가장 빠르게 글을 무너뜨리는 패턴입니다. 단계마다 OS, 환경, 버전을 분기해 글이 트리처럼 펼쳐집니다. 독자는 매번 자기가 어느 가지에 있는지 확인하느라 멈춥니다.

이 패턴을 발견하면 패턴 1 또는 패턴 2로 재구성합니다. 그래도 안 된다면 그 글은 사실 하우투가 아니라 레퍼런스에 가까운 글일 수 있습니다. ep.04에서 다룰 주제입니다.

탭 컴포넌트는 패턴 1의 UI 구현

요즘 Docusaurus·MkDocs Material·Starlight 같은 도구로 만든 문서를 보면 코드 블록 위에 Linux / Mac / Windows 또는 npm / yarn / pnpm 같은 탭이 붙어 있습니다. Docker 설치 가이드, Tailwind Installation, pnpm Installation이 대표적입니다. 탭은 본질적으로 패턴 1의 시각화입니다. 분기는 한 자리에서만 일어나고, 독자는 한 번 골라 뒤로는 같은 길로 갑니다.

탭을 쓸 때 두 가지를 지킵니다.

선택을 페이지 전체에 동기화합니다. Docusaurus의 groupId, MkDocs Material의 linked content tabs, Starlight의 syncKey 같은 기능이 있어서 한 곳에서 Mac을 고르면 같은 페이지의 모든 탭이 Mac으로 맞춰집니다. 이 동기화가 없으면 독자는 8단계까지 내려가는 동안 내가 지금 어느 탭이더라를 매번 확인하게 되고, 이는 패턴 3을 UI로 위장한 것에 지나지 않습니다.

탭 안의 내용은 명령어 한두 줄 차이여야 합니다. 탭마다 흐름이나 개념이 달라진다면 그건 패턴 1이 아니라 패턴 3입니다. 그 경우는 솔직하게 패턴 2로 글을 나눕니다. “탭이 있으니 한 글 안에서 다 다룰 수 있다”는 착각이 가장 흔히 글을 무너뜨립니다.

공개 문서가 분기를 자르는 방식

두 곳의 공개 문서가 분기를 어떻게 다루는지 보면 위의 패턴이 더 또렷해집니다.

  • AWS의 re:Post / Knowledge Center: “How do I …?” 형식의 글이 수천 편 쌓여 있지만, 각 글은 한 시나리오에 한 답입니다. RDS인지 Aurora인지, MySQL인지 PostgreSQL인지가 다르면 글이 통째로 다릅니다. 진입 페이지에서 서비스·엔진·시나리오로 골라 들어가게 만들어 패턴 2를 극단까지 밀어붙인 형태입니다.
  • Kubernetes의 Tasks 섹션: “Configure a Pod to Use a ConfigMap” 같은 글들은 단계 안에서 클러스터 종류·CRI·CNI를 분기하지 않습니다. 대신 Before you begin 단락에서 “Kubernetes 클러스터와 그에 연결된 kubectl이 있어야 한다”고 환경을 못박고, 본문은 표준 kubectl 명령으로 한 길만 갑니다. 분기를 글 안이 아니라 글 바깥(다른 Task 문서·Concept 문서)에 둔 것입니다.

두 사례 모두 핵심은 글 한 편 안에서는 분기를 최소화한다는 것입니다. 분기는 더 많은 짧은 글로 푸는 게 더 적은 긴 글로 푸는 것보다 거의 항상 낫습니다.


하우투가 망가지는 다른 지점들

분기 외에도 자주 무너지는 지점들이 있습니다.

  1. 설명을 끼워 넣는다.
    ”참고로 이 명령은 내부적으로 …” 같은 문장이 들어오면 글이 갑자기 늘어지고, 독자는 작업을 멈춥니다. 설명은 ep.05의 글로 빼고, 여기선 링크만 둡니다.

  2. 실패 가능성을 무시한다.
    ”이 명령을 실행하면 됩니다”로 끝나는 하우투는 모든 게 다 잘 됐을 때만 동작합니다. 실패할 수 있는 단계에는 “X 에러가 보이면 …” 같은 트러블슈팅을 같이 둡니다.

  3. 버전이 명시되지 않는다.
    ”PostgreSQL을 업그레이드하기”는 6개월 뒤 무용지물이 됩니다. “PostgreSQL 9.6 → 14.x”로 적어두면 1년 뒤에도 어디까지 유효한지 압니다.

  4. 검증 없이 끝난다.
    ”끝!”으로 끝나는 하우투는 독자가 정말 성공했는지 확인할 길이 없습니다. 마지막 검증 단계를 빼먹지 않습니다.

  5. 롤백 없이 위험한 작업이 적힌다.
    데이터 삭제, 권한 변경, 인프라 변경처럼 되돌릴 수 없는 작업은 롤백 또는 되돌릴 수 없음에 대한 경고를 명시합니다.

검증을 도구로 박는다

검증 단계를 글로만 두지 말고 명령으로 박아두면 거짓말이 되기 어렵습니다. 두 가지 도구가 자주 어울립니다.

  • smoke test 한 줄: 배포·마이그레이션 끝에 curl -f https://api.example.com/healthz처럼 실패 시 종료 코드가 0이 아닌 명령을 둡니다. 글에서 “헬스체크가 OK여야 합니다” 한 문장보다, 복사해 붙일 수 있는 한 줄이 신뢰도를 올립니다. curl -f는 HTTP 4xx·5xx에서 비-제로로 종료하므로 CI 단계에 그대로 끼울 수 있습니다.
  • GitHub Actions의 matrix: 같은 검증을 여러 환경에서 자동으로 돌려보고 싶다면 strategy.matrix로 OS·런타임 버전을 풀면 됩니다. 하우투 본문에는 결과 표 한 장과 워크플로 파일 링크만 두고, 매트릭스의 모든 분기는 CI가 책임지게 합니다. 글에서 분기를 치우는 또 다른 방법입니다.

이렇게 검증을 도구로 박아두면, 6개월 뒤 환경이 바뀌어 가이드가 거짓말이 되는 순간 CI가 먼저 깨집니다. 글의 거짓말을 사람이 발견할 때까지 기다리지 않아도 됩니다.


좋은 예와 나쁜 예

같은 작업의 짧은 발췌입니다.

나쁜 예

## Postgres 마이그레이션

Postgres를 마이그레이션하려면 먼저 백업을 해야 하는데, 백업은 pg_dump를
쓸 수도 있고 pg_basebackup을 쓸 수도 있습니다. pg_dump는 논리적 백업이라
크기가 크고 시간이 오래 걸리지만 호환성이 좋고, pg_basebackup은 물리적 백업이라
빠르지만 같은 버전에서만 됩니다. 어차피 마이그레이션이니까…

```bash
pg_dump > backup.sql
```

그러면 백업이 되고, 새 인스턴스에서 복원하면 됩니다.

문제: 첫 단락이 비교 설명입니다. 명령어가 추상적입니다(DB 이름이 없음). 어떤 결과가 정상인지 표시가 없습니다. 다음 단계와 검증이 없습니다.

좋은 예

## PostgreSQL 9.6 → 14.x 마이그레이션 (자체 운영)

이 가이드는 자체 운영 환경에서 PostgreSQL 9.6 인스턴스를 14.x로
마이그레이션하는 절차입니다. 완료 후 상태:

- 9.6 인스턴스가 read-only로 잠긴 상태
- 14.x 인스턴스가 트래픽을 받는 상태
- 약 5분 이내의 다운타임

**소요 시간**: 데이터 100GB 기준 약 2시간

**전제**: 슈퍼유저 권한, 디스크 ≥ 데이터 크기 × 1.5

### 1. 백업 받기

마이그레이션 *전*에 논리 백업을 받습니다. 문제가 생기면 이 백업으로 복원합니다.

```bash
pg_dump -U postgres -F c -d app > /backup/app-$(date +%F).dump
```

**검증**: 파일이 생성되었고 크기가 0이 아니면 OK.

```bash
ls -lh /backup/app-*.dump
```

### 2. 새 인스턴스 준비

(다음 단계로 계속)

차이가 보입니다. 완료 후 상태가 명시되어 있습니다. 시간과 전제가 위에 있습니다. 왜 이걸 하는지가 단계 안에 한 줄로 들어 있습니다. 검증이 명령어로 실행 가능합니다.


템플릿

# [동사로 시작하는 과업 제목]

[이 가이드를 따라가면 무엇이 끝나는지 한두 문장]

**완료 후 상태**:
- [상태 1]
- [상태 2]
- [상태 3]

**소요 시간**: 약 [N]분 / [기준]

**전제**:
- [필요한 권한·환경·지식]

## 1. [동사 제목]

[이 단계에서 무엇을 *왜* 하는지 한 줄]

```[언어]
[명령 또는 코드]
```

**검증**: [성공 신호]
**실패 신호**: [실패의 모습] → [어디로 가야 하는지]

## 2. [동사 제목]

...

## 검증 (최종)

[전체가 끝났음을 확인하는 방법]

## 롤백

[문제가 생겼을 때 되돌리는 절차]

## 다음 행동

- [완료 후 자연스럽게 이어지는 작업]
- [관련 가이드]

체크리스트

  • 제목이 동사로 시작하는가
  • 첫 단락에 완료 후 상태가 정의되어 있는가
  • 전제(권한·환경·지식·시간)가 위쪽에 명시되어 있는가
  • 각 단계가 번호 + 동사 + 이유 한 줄을 갖추고 있는가
  • 분기가 단계마다 펼쳐지지 않는가 (패턴 1 또는 2를 따랐는가)
  • 각 중요한 단계에 검증이 있는가
  • 실패 신호가 함께 적혀 있는가
  • 버전·환경이 명시되어 있는가
  • 되돌릴 수 없는 작업에 롤백 또는 경고가 있는가
  • 마지막에 다음 행동이 있는가

참고 자료


다음 편 예고

ep.04 - 정확한 사실의 색인, 레퍼런스와 API 문서

다음 편은 레퍼런스입니다. 가장 조용한 문서. 작업 중인 사람이 정확한 사실을 찾으러 오는 글입니다. docstring부터 OpenAPI까지, 사람이 쓰는 것과 자동 생성하는 것의 경계를 살펴봅니다.