
이번 편이 만지는 곳: 개발의 RDB와 데이터. 에이전트가 데이터베이스를 망가뜨려도 되돌리는 마이그레이션과 스냅샷을 익힌다.
Agentic 코딩의 본질은 에이전트가 코드를 빠르게 쏟아내는 것이다. 데이터베이스 변경도 마찬가지여서, 시키면 ALTER(구조 변경)나 DROP(삭제)을 거리낌 없이 실행한다. 문제는 여기서 갈린다. 코드 되돌리기는 문서의 실행 취소(Ctrl+Z)와 비슷하지만, 데이터 삭제는 종이를 파쇄기에 넣은 것과 같다. 컬럼이나 행을 한 번 지우면 복구가 안 된다. 에이전트의 속도가 가장 위험해지는 단일 지점이 바로 데이터베이스다.
그래서 이번 편의 목표는 하나다. 모델이 데이터베이스를 멋대로 바꿔도 즉시 되돌릴 수 있는 안전망을, 그것도 어디서나 쓸 수 있는 오픈소스 도구로만 손에 익히는 것. 새로 쓰는 Claude Code 기능은 체크포인트와 /rewind다.
먼저 두 단어 — 스키마와 마이그레이션
스키마(schema)는 데이터베이스의 설계도다. 어떤 테이블에 어떤 칸(컬럼)이 있는지의 구조. 마이그레이션(migration)은 그 설계도를 바꾼 변경 일지다. 코드의 git 커밋처럼 "1번: users 테이블 생성, 2번: phone 칸 추가"를 번호로 쌓아 두면, 한 단계씩 다시 실행하거나 되감을 수 있다.
일반 데이터(CRUD)는 프론트엔드인 Next.js와 TypeScript 쪽에서 다루고, 여기서 ORM(Object-Relational Mapping: 객체-관계 매핑)으로 Drizzle을 쓴다. ORM은 코드 속 데이터 덩어리(객체)와 데이터베이스 테이블을 자동으로 이어 주는 통역사인데, Drizzle은 스키마를 TypeScript 코드로 정의한다. 편리하지만 함정이 있다. drizzle-kit push로 바로 데이터베이스에 밀어 넣으면 변경 이력이 안 남고(미기록 드리프트, 코드 속 설계도와 실제 DB가 서로 어긋난 상태), psql이나 DBeaver로 손으로 바꾼 것도 같은 드리프트를 만든다. 그래서 운영 데이터베이스 변경은 반드시 검토 가능한 SQL 마이그레이션 파일로 남기는 규율이 필요하다. push가 아니라 generate로 SQL을 만들어 검토한 뒤 적용한다.
되돌리기의 두 층위 — Ctrl+Z와 게임 세이브

오늘의 핵심은 되돌리기가 사실 두 종류라는 점이다. 둘은 서로 다른 것을 되돌린다.
- Claude Code의 /rewind는 에이전트가 한 작업(코드 편집이나 대화)의 Ctrl+Z다. 잘못 시킨 작업을 직전 체크포인트로 되돌린다.
- 데이터베이스 스냅샷은 데이터의 게임 세이브 파일이다. 사고가 나면 저장해 둔 시점으로 불러오기를 한다.
이 둘을 헷갈리면 큰일 난다. /rewind로 코드는 되살아나도, 이미 파쇄된 데이터는 돌아오지 않는다. DROP TABLE이 실제로 실행된 순간 그 안의 데이터는 데이터베이스가 이미 지워 버렸고, 코드를 되살린다고 따라 살아나지 않는다. 그래서 데이터에는 별도의 세이브, 즉 적용 전 스냅샷이 꼭 있어야 한다.
오픈소스 최소 구성 3겹
| 겹 | 도구(OSS) | 쉬운 비유 |
|---|---|---|
| 버전 관리 | Drizzle Kit | TypeScript 스키마에서 평문 SQL 마이그레이션 파일을 생성/소유. generate로 만들고 검토 후 적용 |
| 사전 차단 | Squawk | 맞춤법 검사기. 데이터가 날아갈 위험이 있는 변경에 적용 전 빨간 줄을 그어 줌 |
| 되돌리기 | 트랜잭션 + 사전 스냅샷 | 트랜잭션은 은행 이체처럼 "전부 아니면 전무"(중간에 실패하면 통째 취소), 스냅샷은 게임 세이브 |
참고 — FastAPI(Python)가 DB에 직접 붙을 때는 Alembic. 위 도구는 프론트(Next.js/Drizzle) 기준이다. AI 백엔드인 FastAPI가 자기 테이블(예: 벡터 테이블)을 직접 다룬다면, 같은 "검토 가능한 마이그레이션" 규율을 Python 쪽에선 Alembic으로 지킨다.alembic revision --autogenerate -m "add embeddings"로 마이그레이션 파일을 만들고, 적용 전에 사람이 한 번 읽어 본 뒤alembic upgrade head로 적용한다. 자동 생성된 결과를 그대로 밀지 않고 반드시 검토하는 것까지 Drizzle의generate와 똑같다.
🗂️ 실습 데이터 — 만들기와 쓰기
조회도 되돌리기도 데이터가 있어야 보인다. 빈 테이블에선 지워도 되살려도 화면이 그대로라 시연이 안 된다. 그래서 먼저 회원과 스터디를 심는다. 두 가지 길이 있다.
① 직접 생성 — Claude Code에 이렇게 시킨다.
Next.js의 Drizzle 시드 스크립트를 만들어 줘. 관리자 1명과 일반 회원 2~3명(role로 구분),
스터디 10~20개를 멱등(여러 번 실행해도 중복 없이) 삽입하고, 전부 지우는 reset도 같이.
npm run seed / npm run seed:reset 으로 돌아가게.
② 내려받아 쓰기 — 같은 데이터를 미리 만들어 뒀다 (studyqa-seed.sql 내려받기). 적재하고 우리 스키마에 맞추라고 시킨다.
내려받은 studyqa-seed.sql 을 로컬 PostgreSQL에 적재해 줘(psql -f).
우리 users / studies / study_members 스키마와 컬럼이 맞는지 확인하고, 다르면 맞춰 줘.
이제 스터디 목록과 회원이 채워졌다. 아래 되돌리기 시연에서 데이터가 사라지고 되살아나는 게 눈에 보인다. 반대로 "빈 목록" 상태를 보고 싶으면 npm run seed:reset으로 비운다.
따라하기 1 — 데이터베이스 선택을 변론시키기
'문서 질의응답과 스터디 관리' 서비스에 맞는 DB 조합을 추천하고,
각 선택의 이유와 버린 대안을 표로 설명해 줘.표가 나오면 묻는다. "조회 패턴과 진짜 맞나? 시계열이 필요 없는데 들어가 있진 않나?" 보통 PostgreSQL에 캐시(Redis), 그리고 AI 검색용 벡터 저장소를 더한 조합으로 수렴한다. 이 PostgreSQL은 로컬에선 Docker로 직접 띄워 빠르게 개발하고, 운영 단계에서 같은 것을 Supabase로 옮긴다.
따라하기 2 — 사전 스냅샷으로 복구 지점 만들기
마이그레이션을 적용하기 전에 현재 DB를 pg_dump로 떠서
before.sql로 저장하는 스크립트를 만들어 줘.복구 지점이 생겼는지 확인한다. 이게 "직전 상태"다. 로컬 PostgreSQL이든 운영 Supabase(무료 등급엔 특정 시점 복구(PITR(Point-In-Time Recovery: 특정 시점 복구))가 없다)든, 관리형 백업에 기대지 않고 이 pg_dump 스냅샷을 우리가 직접 떠 둔다. 같은 PostgreSQL이라 로컬에서 뜬 스냅샷 절차가 운영에서도 그대로 통한다.
따라하기 3 — 변경을 마이그레이션 파일로
users에 phone 컬럼을 추가하려고 해. Drizzle 스키마에 컬럼을 추가하고,
push로 바로 적용하지 말고 drizzle-kit generate로 SQL 마이그레이션 파일을 만들어 줘.migrations/ 폴더에 평문 SQL 파일이 생겼는지 본다. push가 아니라 generate로 검토 가능한 파일을 남기는 게 핵심이다.
따라하기 4 — 적용 전 린트
방금 만든 마이그레이션을 Squawk로 검사해 줘.경고가 뜨면 안전한 다단계로 분해한다. 먼저 nullable 컬럼으로 추가하고, 기존 행을 채운(backfill) 다음, NOT NULL을 적용하는 식이다.
따라하기 5 — 사고를 내고 두 되돌리기를 비교 (오늘의 절정)
일부러 파괴적 지시를 내린다.
users 테이블을 지워 줘.먼저 Claude Code에서 /rewind로 직전 체크포인트로 되돌려 보자. 코드와 대화는 돌아오는데, 이미 지워진 데이터는 안 돌아온다. 이걸 눈으로 본 다음 before.sql로 데이터를 복원하면, 그제야 진짜 복구다. 이 차이를 직접 보는 게 이번 편 전체의 이유다. 안전한 절차를 한 장으로 정리하면 이렇다.

띄워서 확인 — 에이전트가 한 일을 검증한다
마이그레이션이 돌았다고 안심하면 안 된다. 에이전트는 시키면 ALTER나 DROP을 마이그레이션 파일도 안 거치고 곧장 실행하려 들기도 한다. 그러니 결과가 아니라 거쳐 온 경로를 검증한다. 변경이 정말 마이그레이션 파일을 통했는지 migrations/ 폴더를 직접 열어 보고, "스냅샷 없이 바로 적용한 건 없는지" 되묻고, 일부러 테이블을 지워 스냅샷으로 되살아나는지 깨뜨려 본다.
마이그레이션을 트랜잭션으로 감싸 적용하고, 중간에 실패하면 통째로 롤백되게 해 줘.
그다음 스터디 항목을 DB에 저장/조회하도록 백엔드를 연결해 줘.- 마이그레이션이 트랜잭션으로 적용되어, 실패 시 데이터베이스가 절반만 바뀌지 않는가?
- 에이전트가 직접 ALTER나 DROP을 실행하려 하지 않고 마이그레이션 파일을 거치는가?
before.sql스냅샷으로 실제 복구가 되는가?
이번 편에서 손에 붙일 명령도 셋이다. 위험한 마이그레이션은 곧장 본류에 적용하지 말고 /branch로 분기를 떠서 먼저 시험하고, 안전하면 본래 작업으로 합친다. 대화가 길어지면 /compact로 요약해 컨텍스트를 줄이고, 지금까지 쓴 토큰 비용은 /cost로 확인한다.
한 겹 더 — 드리프트 잡고 규칙 박기
누군가 psql이나 DBeaver로 DB를 직접 바꿨다고 가정하고, drizzle-kit pull로
실제 DB 상태를 끌어와 우리 스키마와의 차이(드리프트)를 보여 줘.
그리고 CLAUDE.md에 규칙을 추가해 줘 — 운영 DB 직접 DDL 금지,
모든 변경은 generate로 만든 SQL 파일로, 적용 전 Squawk 통과와 스냅샷 필수, 트랜잭션으로 적용.손으로 바꾼 변경이 드리프트로 드러나는지 본다. 마이그레이션 파일을 거치지 않은 변경이 왜 위험한지 여기서 체감한다. 여기서 말한 DDL(Data Definition Language: 데이터 정의어)은 테이블/컬럼을 만들고 바꾸고 지우는 명령을 말한다.
코드는 /rewind로, 데이터는 스냅샷으로 되돌린다. 이 둘을 헷갈리면 "되돌릴 수 있다"고 착각하다 진짜 사고가 난다. 다음 편의 주제는 검색이다. 데이터가 쌓였으니 이제 찾을 차례. 키워드와 벡터, 하이브리드 검색을 서브에이전트로 한 번에 만들어 눈으로 비교해 본다.