페이지 목차
이 페이지에서 검색
AI 드리븐
이 페이지에 묻기
AI 드리븐 · 현재 차시와 목차 본문에서만 찾아 답합니다. 답을 지어내지 않고 해당하는 대목을 그대로 보여 주며, 여기에 없으면 없다고 알려 드립니다.
생성형 AI가 아니라 이 페이지 본문을 찾아 그대로 보여 주는 방식입니다. 외부로 나가는 요청이 없습니다.
AI · DEVELOPMENT
AI 드리븐
생성 수정
요구사항과 역할 정의부터 구현·검증·배포·운영까지, AI와 함께 개발하며 사람이 판단할 기준을 정리합니다.
26개 문서 · 2일차 · 13 / 26
12차시 · 데이터 관리 역할
지도 위치 : 2일차 · 구현과 검증 · 구현 | 이전: 11차시 프론트엔드 역할: 화면과 경계 | 다음: 13차시 검증 ① 코드·동작 점검 키워드 : 데이터의 영구 저장, 데이터 구조와 규칙, 파괴 방지 강제 장치
이 차시를 다 읽으면
- 데이터를 둘 수 있는 네 곳을 디스크 ↔ 메모리 축에 놓고, 예약 · 결제가 왜 DB에 있어야 하는지 말할 수 있다.
- 제약조건 을 유와 종차로 정의하고, 코드의 검사와 달리 코드에 버그가 있어도 지켜지는 이유를 설명할 수 있다.
- 트랜잭션 을 정의하고, 클래스온에서 묶어야 할 변경 한 쌍을 댈 수 있다.
- 마이그레이션 을 정의하고, 이미 적용된 파일을 고치지 않는 이유와 위험 단어 다섯 개를 말할 수 있다.
- 파괴 방지 장치를 여러 층 으로 두는 이유와, 훅 이 지침 파일의 "하지 마"와 어떻게 다른지 설명할 수 있다.
0. 이 차시의 판별 축
| # | 축 | 왼쪽 (0) | 오른쪽 (1) | 이 축으로 갈리는 것 | 큰 축 (README 5장) |
|---|---|---|---|---|---|
| 1 | 꺼도 남는가 | 디스크 — DB, 파일 저장소 | 메모리 — 변수, 캐시 | 예약 · 결제를 어디에 두는가 | ① 디스크 ↔ 메모리 |
| 2 | 누가 규칙을 지키는가 | 부탁 — 코드의 검사, 지침 파일의 "DROP 하지 마" | 강제 — DB 제약조건, 훅, 계정 권한 | 버그 · 실수가 있어도 지켜지는가 | 부탁 ↔ 강제 |
| 3 | 되돌릴 수 있는가 | 코드 · 설정 파일 — 다시 쓰면 됨 | 데이터 삭제 — 돌아오지 않음 | 어디에 가장 강한 장치를 두는가 | 되돌릴 수 있는가 |
| 4 | 구조 변경이 기록되는가 | DB 관리 도구로 직접 변경 | 마이그레이션 파일로 변경 | 다른 환경에 똑같이 재현되는가 | ① 디스크 ↔ 메모리 (기록이 남는가) |
이 차시가 답하는 질문은 "사라지면 돌아오지 않는 것을 어떻게 지키는가" 입니다. 코드는 다시 쓰면 되지만 데이터는 그렇지 않습니다(축 3). 그래서 규칙을 부탁(코드 · 지침)이 아니라 강제(DB 자체 · 훅) 에 새깁니다(축 2). 앞에서 배운 강제 사다리(2차시)를 처음으로 본격적으로 쓰는 차시입니다.
1. 데이터의 영구 저장
| 저장 위치 | 서버를 재시작하면 | 0절 축 1 | 쓰임 |
|---|---|---|---|
| 메모리 (프로그램 안의 변수) | 사라짐 | 메모리 | 계산 중 임시값 |
| 캐시 | 사라질 수 있음 | 메모리 쪽 | 자주 읽는 값을 빠르게 |
| 데이터베이스 (DB) | 유지 | 디스크 | 예약, 결제, 사용자 |
| 파일 저장소 | 유지 | 디스크 | 클래스 사진 |
데이터베이스 — [저장소]인데, [ 데이터를 표(테이블) 형태로 영구 저장하고, 규칙과 동시 접근을 스스로 관리한다 ]는 점에서 그냥 파일에 적어 두는 것과 구별된다.
⚠️ "남은 자리 수를 서버 메모리에 들고 있다가 가끔 저장" 같은 설계는 서버가 재시작되거나 서버가 두 대가 되는 순간 숫자가 틀어집니다. 돈과 자리는 언제나 디스크(DB)가 기준 입니다.
2. 데이터 구조 — 클래스온의 표
users(id, email, name, phone, created_at)
classes(id, title, studio_name, price, ...)
sessions(id, class_id → classes, date, capacity, remaining) # 날짜별 회차
reservations(id, user_id → users, session_id → sessions, headcount, amount, status, created_at)
payments(id, reservation_id → reservations, pg_payment_key, amount, status, ...)
🔍 읽는 포인트 : 괄호 안이 각 표의 칸(열)이고, →는 "다른 표의 행을 가리킨다" 는 뜻입니다. 예약은 사용자와 회차를, 결제는 예약을 가리킵니다.
3. ⭐ 데이터 규칙 — 제약조건
3.1 정의
제약조건 (Constraint) — [데이터 규칙]인데, [ DB가 스스로 지켜서, 어떤 코드가 어떤 경로로 쓰든 규칙을 어기는 쓰기를 거부한다 ]는 점에서 서버 코드 안의 입력 검사와 구별된다.
| 제약조건 | 뜻 | 클래스온 예 |
|---|---|---|
PRIMARY KEY | 행을 하나로 식별 | 모든 표의 id |
FOREIGN KEY | 다른 표에 실제로 있는 값만 | 없는 회차에 대한 예약 불가 |
NOT NULL | 비워 둘 수 없음 | amount, status |
UNIQUE | 중복 불가 | users.email, payments.pg_payment_key (같은 결제를 두 번 기록하지 않음, 16차시) |
CHECK | 값의 범위 | remaining >= 0, headcount BETWEEN 1 AND 4 |
상태 값은 정해진 것만 — reservations.status는 PENDING_PAYMENT · CONFIRMED · CANCELLED · REFUNDED 같은 정해진 목록 중 하나로 제한합니다(16차시의 상태 전이).
3.2 경계 — 코드의 검사 ↔ 제약조건
| 서버 코드의 검사 (10차시) | DB 제약조건 | |
|---|---|---|
| 0절 축 2 | 부탁 — 코드가 지키기로 한 것 | 강제 — DB가 거부 |
| 코드에 버그가 있으면 | 뚫린다 | 그래도 지켜진다 |
| 다른 경로(관리 스크립트, 다른 서버, AI가 직접 실행한 질의)로 쓰면 | 검사를 거치지 않는다 | 그래도 지켜진다 |
| 실패 메시지 | 친절하게 만들 수 있음 | 투박함 → 서버가 받아서 바꿔 줌 |
둘 다 둡니다. 코드 검사는 사용자에게 좋은 메시지를 주고, 제약조건은 마지막 방어선 입니다. CHECK (remaining >= 0) 하나가 있으면, 코드의 동시성 버그로 자리를 초과 차감하려 해도 DB가 거부합니다.
앞으로 다시 만납니다 — 13차시에서 두 사람이 동시에 마지막 자리를 예약하는 경쟁 상태를 볼 때, 이
CHECK가 "마지막 방어선"으로 다시 나옵니다. 16차시에서는UNIQUE가 중복 결제 기록을 막습니다.
3.3 반례 — 이건 제약조건이 아닙니다
| 대상 | 왜 아닌가 |
|---|---|
| 지침 파일의 "남은 자리는 음수가 되면 안 된다" | 에이전트에게 하는 부탁 . DB는 모름 |
서버 코드의 if (remaining < 0) throw | 코드 검사. 그 코드를 거치지 않는 경로에서는 무력 |
| 화면의 "인원 1~4" 입력 제한 | 편의용 (11차시) |
4. 트랜잭션 — 함께 성공하거나 함께 실패
트랜잭션 (Transaction) — [DB 작업 단위]인데, [ 여러 변경을 하나로 묶어 모두 반영되거나 하나도 반영되지 않게 한다 ]는 점에서 변경을 하나씩 따로 저장하는 것과 구별된다.
| 묶지 않으면 | 묶으면 |
|---|---|
| ① 자리 1 차감(성공) → ② 예약 생성 중 오류(실패) → 자리만 줄고 예약은 없음 | ② 실패 시 ①도 취소 → 처음 상태 그대로 |
클래스온에서 반드시 묶을 쌍은 "회차의 남은 자리 차감 + 예약 생성" 입니다. 10차시 원칙 표의 "함께 성공하거나 함께 실패"가 이것입니다.
⚠️ 트랜잭션은 한 요청 안의 묶음 을 지켜 줍니다. 두 요청이 동시에 같은 자리를 노리는 문제는 트랜잭션만으로 다 풀리지 않습니다. 그 이야기가 13차시입니다.
💡 비유는 마지막에 — 계좌 이체에서 "내 통장 출금"과 "상대 통장 입금"은 한 묶음입니다. 출금만 되고 입금이 안 된 상태는 허용되지 않습니다.
5. 마이그레이션 — 구조 변경을 파일로
5.1 정의와 원칙
마이그레이션 (Migration) — [DB 구조 변경 방식]인데, [ 변경을 순번 붙은 파일로 남기고 그 순서대로 적용한다 ]는 점에서 DB 관리 도구로 직접 접속해 바꾸는 방식과 구별된다.
apps/api/migrations/
├── 001_create_users.sql
├── 002_create_classes_and_sessions.sql
└── 003_create_reservations.sql
🔍 읽는 포인트 : 파일 이름 앞의 번호가 적용 순서 입니다. 새 변경은 항상 새 번호의 새 파일 로 추가됩니다.
| 마이그레이션으로 얻는 것 | 0절의 어느 축 |
|---|---|
| 누가 · 언제 · 무엇을 바꿨는지 Git에 남음 | 축 4 (기록) |
| 다른 환경(시험 · 운영)에 같은 순서로 똑같이 적용 | 축 4 (재현), 15차시 |
| 리뷰할 수 있음 — 운영에 닿기 전에 위험을 볼 기회 | 축 3 (되돌릴 수 없는 것 앞에 관문) |
⚠️ 이미 적용된 마이그레이션 파일은 고치지 않습니다. 이미 적용된 환경은 그 파일을 다시 실행하지 않으므로, 고쳐 봐야 환경마다 구조가 달라질 뿐입니다. 바꾸려면 새 파일을 추가합니다.
5.2 ⭐ SQL을 못 읽어도 잡을 수 있는 위험 단어
| 단어 | 뜻 | 신호 |
|---|---|---|
DROP TABLE / DROP DATABASE | 표 · DB 통째로 삭제 | ❌ 멈춤 |
TRUNCATE | 표의 모든 행 삭제 | ❌ 멈춤 |
DELETE (특히 WHERE 없이) | 행 삭제 (조건 없으면 전부) | ❌ 멈춤 |
ALTER ... DROP COLUMN | 칸 삭제 — 그 칸의 데이터도 사라짐 | ❌ 멈춤 |
RENAME | 이름 변경 — 옛 이름을 쓰는 코드가 깨짐 | ⚠️ 확인 |
하나라도 보이면 멈추고 7절 실습의 질문을 던집니다. 반대로 CHECK · UNIQUE · NOT NULL은 규칙을 더하는 단어입니다(README 9.5).
6. ⭐ 파괴 방지 강제 장치
6.1 왜 여러 층인가
에이전트는 문제를 "해결"하려다 표를 지우고 다시 만드는 방법을 고를 수 있습니다. 로컬에서는 괜찮지만 운영 DB라면 사고입니다. 장치 하나는 언젠가 뚫리므로 여러 겹으로 막습니다.
| 층 | 장치 | 막는 것 |
|---|---|---|
| 에이전트 | 훅 — 위험한 명령을 실행 직전에 검사해 차단 | DROP TABLE, TRUNCATE 실행 |
| 에이전트 | 권한 — 운영 DB 접속 정보 파일 읽기 거부 (14차시) | 운영 DB 접근 자체 |
| DB | 계정 분리 — 앱이 쓰는 계정은 행 읽기 · 쓰기만, 표 삭제 권한 없음 | 코드 · 에이전트의 실수 |
| 운영 | 백업과 복구 연습 | 그래도 일어난 사고 |
| 리뷰 | 마이그레이션 파일 확인 (5.2 + 교차 질문) | 의도된 파괴적 변경 |
6.2 훅 — 에이전트가 잊어도 작동하는 장치
앞에서 배운 훅 (2차시)은 정해진 시점에 자동으로 실행되는 검사입니다. 여기서는 "에이전트가 명령을 실행하기 직전"에 겁니다.
// .claude/settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": ".claude/hooks/block-destructive-sql.sh" }]
}
]
}
}
🔍 읽는 포인트 : PreToolUse(도구 쓰기 직전 ) + Bash(명령 실행) = "에이전트가 명령을 실행하기 직전마다 이 스크립트를 돌린다".
#!/bin/bash
# .claude/hooks/block-destructive-sql.sh
# 실행하려는 명령이 표준입력으로 JSON 형태로 들어옴
cmd=$(jq -r '.tool_input.command')
if echo "$cmd" | grep -qiE 'drop (table|database)|truncate'; then
echo "파괴적인 SQL은 차단됩니다. 마이그레이션 파일로 작성하고 사람의 검토를 받으세요." >&2
exit 2 # 2 = 차단하고 이유를 에이전트에게 전달
fi
exit 0
🔍 읽는 포인트 : grep 줄의 단어 목록 (막을 것)과 exit 2( 차단 )만 보면 됩니다. exit 0은 통과입니다.
| 지침 파일의 "DROP 하지 마" | 훅 | |
|---|---|---|
| 0절 축 2 | 부탁 | 강제 |
| 에이전트가 잊거나, 맥락이 길어져 밀려나면 | 어긴다 | 그래도 막힌다 |
| 막는 범위 | 에이전트가 기억하는 한 전부 | 적어 둔 단어만 — 목록에 없는 방법은 통과 |
⚠️ 마지막 줄이 훅의 한계입니다. 예를 들어 DB 관리 도구를 다른 방식으로 부르면 이 단어 검사를 피할 수 있습니다. 그래서 6.1처럼 DB 계정 권한 이 아래에서 한 번 더 받칩니다.
💡 비유는 마지막에 — 지침 파일은 사내 규정집 , 훅은 출입 게이트 , DB 계정 권한은 금고 자체의 잠금 입니다. 규정을 잊어도 게이트가, 게이트를 돌아가도 금고가 막습니다.
7. 클래스온에 적용하면 — 데이터 역할
# .claude/agents/data.md
## 책임
- 표 구조, 제약조건, 마이그레이션 파일 작성
## 하지 않는 일
- 이미 적용된 마이그레이션 파일 수정
- 운영 DB에 직접 접속하거나 명령 실행
- DROP / TRUNCATE / 조건 없는 DELETE 실행 (훅으로도 차단됨)
## 완료 조건
- 새 마이그레이션을 로컬에 적용한 출력 원문
- 제약조건 위반 시도(음수 자리, 중복 결제 키)가 DB에서 거부된 출력 원문
🔍 읽는 포인트 : 완료 조건의 둘째 줄 — 규칙이 실제로 거부하는 것을 보여 달라 는 요구입니다. "제약조건을 걸었습니다"라는 주장 대신 증거를 받습니다.
8. 실습 — 맡기고 검수하기
지시
1. 2절의 표 구조로 마이그레이션 파일을 만들고 로컬 DB에 적용해줘. 3.1의 제약조건을 모두 포함해.
2. 아래 쓰기를 DB에 직접 시도하고, 각각 거부된 출력 원문을 붙여줘.
- sessions.remaining을 -1로 만들기
- 같은 pg_payment_key로 payments 행 두 개 넣기
- headcount 5인 예약 넣기
3. 6.2의 훅을 설치한 뒤, 로컬 DB에 "DROP TABLE reservations"를 실행해 봐. 차단 메시지 원문을 붙여줘.
4. 방금 만든 마이그레이션에 대해: 기존 데이터를 지우거나 바꾸거나 읽을 수 없게 만드는 경우가 있어?
예/아니오로 먼저 답하고, 근거와 되돌리는 방법을 말해줘.
결과는 [시도 | 기대 | 실제 결과 | 통과?] 표로.
받을 증거
- 마이그레이션 적용 출력, 제약조건 위반 시도 3건의 거부 출력 원문
- 훅 차단 메시지 원문
- 4번 질문에 대한 예/아니오 답과 근거
내가 직접 확인할 것
| 볼 것 | 이걸로 아는 것 / 왜 내가 보는가 |
|---|---|
| DB를 표 형태로 보여 주는 도구(TablePlus, DBeaver, 관리형 DB의 웹 화면)로 표를 직접 연다 | 엑셀처럼 생겨서 코드 없이 "예약이 몇 건인지", "남은 자리가 맞는지"를 볼 수 있습니다. 13차시 행동 시험 뒤의 결과 확인에 계속 씁니다. ⚠️ 운영 DB는 읽기 전용 계정으로만 |
| 마이그레이션 파일에서 5.2의 위험 단어 검색 | 하나라도 있으면 멈춤 |
| 4번 질문을 다른 역할(13차시의 리뷰 역할)에게 따로 물어 답이 일치하는가 | 만든 AI의 "안전합니다"는 주장일 뿐. 교차 검증 (README 9.1) |
| 훅 차단 메시지를 눈으로 봤는가 | 장치가 실제로 작동하는 것을 봐야 믿을 수 있습니다 |
| 운영 적용 전: 백업이 있는지 , 복구해 본 적이 있는지 | 되돌릴 수 없는 작업 앞의 마지막 확인 |
부록 A. 스스로 확인하기
| # | 질문 | 답이 있는 곳 |
|---|---|---|
| 1 | 저장 위치 네 곳을 디스크 ↔ 메모리 축에 놓으면? | 1 |
| 2 | 제약조건을 유와 종차로 정의하면? | 3.1 |
| 3 | CHECK (remaining >= 0)이 코드 버그가 있어도 데이터를 지키는 원리는? | 3.2 |
| 4 | payments.pg_payment_key에 UNIQUE를 거는 이유는? | 3.1 |
| 5 | 트랜잭션으로 묶어야 할 클래스온의 변경 한 쌍은? 트랜잭션이 풀지 못하는 문제는? | 4 |
| 6 | 마이그레이션으로 얻는 세 가지는? 이미 적용된 파일을 왜 고치지 않는가? | 5.1 |
| 7 | SQL을 못 읽어도 찾을 수 있는 위험 단어 다섯 개는? | 5.2 |
| 8 | 파괴 방지 장치를 여러 층으로 두는 이유는? 훅의 한계는? | 6.1, 6.2 |
| 9 | 지침 파일의 "하지 마"와 훅은 어느 축에서 갈리는가? | 0, 6.2 |
부록 B. 자주 하는 실수
| 실수 | 바로잡기 |
|---|---|
| 규칙을 코드에만 두고 DB 제약조건은 비워 둔다 | ❌ 제약조건은 마지막 방어선 (3.2) |
| 개발 중 DB 관리 도구로 구조를 직접 바꾼다 | ❌ 다른 환경과 어긋납니다. 마이그레이션 파일로 (5.1) |
| 이미 적용된 마이그레이션 파일을 고친다 | ❌ 새 파일을 추가 (5.1) |
| 앱이 DB 관리자 계정으로 접속한다 | ❌ 앱 계정은 행 읽기 · 쓰기만 (6.1) |
| "훅을 걸었으니 DB는 안전하다" | ⚠️ 훅은 적어 둔 단어만 막습니다. 계정 권한이 함께 (6.2) |
| 백업은 설정했지만 복구해 본 적이 없다 | ⚠️ 복구 연습까지가 백업입니다 (6.1) |
| "제약조건을 걸었습니다"라는 보고를 그대로 받는다 | ⭕ 위반 시도가 거부된 출력 을 받습니다 (7, 8) |
부록 C. NotebookLM 복습 프롬프트
이 12차시 노트만 근거로 6문제를 내줘. 비전공자 기준으로.
"제약조건과 코드 검사의 차이(부탁↔강제)", "트랜잭션", "마이그레이션의 위험 단어", "훅과 지침 파일의 차이"는 반드시 포함해줘.
답은 내가 답한 다음에 채점하고, 틀린 이유를 노트의 절 번호를 근거로 설명해줘.
LangTailor