기술

테스트 설정 한 줄이 운영 DB 테이블을 지웠습니다

2026.06.197분 읽기

어느 B2B 식사 SaaS의 운영을 보던 시절 이야기입니다. 평소처럼 배포가 끝나고 잠시 뒤, 운영 화면에서 데이터가 비어 있다는 연락을 받았습니다. 처음엔 조회 쿼리 문제이거나 캐시가 꼬인 줄 알았습니다. 그런데 DB에 직접 붙어보니 테이블 자체가 없었습니다. 데이터가 비어 있는 게 아니라 테이블이 통째로 사라진 상태였습니다.

원인은 허무할 만큼 단순했습니다. 테스트 환경에서 쓰던 스키마 자동생성 설정이 운영 배포에 그대로 실려 있었습니다. JPA의 ddl-auto 값이 create-drop이었고, 이 옵션은 애플리케이션이 뜰 때 스키마를 새로 만들고 종료될 때 통째로 drop합니다. 테스트에서는 매번 깨끗한 상태로 시작하려고 일부러 쓰는 설정이었는데, 그게 운영에 닿는 순간 운영 테이블을 지우는 명령으로 바뀌었습니다.


편한 기본값이 운영에 닿으면 파괴 명령이 됩니다

ddl-auto는 JPA가 엔티티 정의를 보고 DB 스키마를 어떻게 다룰지 정하는 설정입니다. 값에 따라 동작이 완전히 다릅니다. 개발할 때는 엔티티만 고치면 테이블이 알아서 따라오니 편합니다. 문제는 이 편함의 정체가 "DB를 마음대로 고친다"라는 점입니다. 운영에서는 그 마음대로가 곧 데이터 삭제입니다.

ddl-auto 값

동작

운영에서의 의미

create

시작 시 스키마 생성(기존 삭제 후)

기존 데이터 소실

create-drop

시작 시 생성, 종료 시 drop

데이터 + 테이블 소실

update

엔티티에 맞춰 스키마 변경

의도치 않은 컬럼 변경 위험

validate

스키마가 맞는지 검증만

DB 변경 없음

none

아무것도 안 함

DB 변경 없음

표를 보면 선택은 명확합니다. 운영에서 손대도 되는 값은 validate와 none뿐입니다. create, create-drop, update는 정도의 차이일 뿐 모두 스키마를 건드립니다. 즉 운영에 닿으면 안 되는 옵션이 기본값 자리에 앉아 있었던 것이 사고의 본질이었습니다.

이미지1.png


환경 분리가 없으면 한쪽 값이 그대로 샙니다

두 번째 문제는 설정 파일을 사실상 한 묶음으로 쓰고 있었다는 점입니다. 테스트와 운영이 같은 설정을 공유하거나, 분리돼 있어도 어느 한쪽 값이 다른 쪽으로 새기 쉬운 구조였습니다. 이런 구조에서는 테스트용으로 넣어둔 create-drop이 운영 실행 경로에 묻어 들어가는 일이 사고가 아니라 시간문제입니다.

여기서 선택지를 두 가지로 정리했습니다. 하나는 설정을 한 파일로 공유하면서 환경마다 값을 갈아끼우는 방식입니다. 관리 지점이 적어 편하지만, 한 번의 실수로 한쪽 값이 다른 환경에 새는 경로가 항상 열려 있습니다. 다른 하나는 환경별로 설정을 분리하고 운영은 보수적인 값으로 못박는 방식입니다. 파일이 늘어나는 대신, 운영에 파괴적 옵션이 닿는 경로 자체가 막힙니다. 후자를 골랐습니다.

사고는 "누가 실수했는가"가 아니라 "막을 수 있었는가"로 봅니다.


먼저 복구하고, 같은 경로를 네 겹으로 막았습니다

당장의 복구는 클라우드 DB의 시점 복구 기능으로 했습니다. Aurora의 PITR(Point-In-Time Recovery)로 테이블이 살아 있던 시점으로 되돌려 운영 테이블을 복구했습니다. 다행이라고 말하기엔 위태로웠습니다. 만약 PITR이 꺼져 있었거나 보존 기간이 지났다면 복구할 방법이 없었습니다. 복구 수단이 사고 전에 이미 준비돼 있었기 때문에 살아난 것이지, 사고 후에 만들 수 있는 게 아니었습니다.

복구 다음은 같은 사고가 다시 일어나지 않게 경로를 닫는 일이었습니다. 개인이 더 조심하자는 다짐이 아니라, 설정과 룰로 굳혔습니다. 같은 경로를 네 겹으로 막았습니다.

테스트 application.yml의 ddl-auto를 validate로 변경 — 테스트도 검증만 하고 스키마는 건드리지 않게 했습니다.

운영 실행 시 테스트 설정의 ddl-auto를 none으로 못박음 — 혹시 테스트 설정이 묻어 들어와도 운영에선 아무것도 하지 않습니다.

전역 개발 룰에 ddl-auto의 create/create-drop/update를 무조건 금지 — 공유 환경에서 파괴 옵션 자체를 쓰지 않습니다.

운영 설정 파일(application-prod.yml)을 gitignore로 분리하고 운영 DB 엔드포인트는 서버 config로 — 운영 접속 정보가 저장소에 섞이지 않게 했습니다.

핵심은 한 겹이 뚫려도 다음 겹에서 멈춘다는 점입니다. 테스트 설정이 잘못 실려도(2겹에서 멈춤), 누가 운영 설정에 create를 적어도(3겹 룰에 걸림), 한 겹의 실수가 운영 데이터 삭제까지 도달하지 못하게 경로를 끊었습니다.

이미지2.png


운영엔 가장 보수적인 기본값만 남깁니다

이 사고에서 남은 일반화는 몇 가지로 정리됩니다. 먼저 파괴적 동작을 기본값으로 두면서 환경 분리를 하지 않는 구조는 언젠가 사고로 이어집니다. 운영에는 가장 보수적인 기본값, 즉 validate나 none을 명시적으로 못박아야 합니다. 암묵적으로 "운영엔 그런 거 안 넣겠지"라고 두면 그 "그런 거"가 결국 들어갑니다.

실수는 어차피 일어납니다. 그래서 사고를 막는 방향은 사람을 더 똑똑하게 만드는 게 아니라, 한 번의 실수가 치명적 결과까지 도달하는 경로 자체를 없애는 쪽입니다. 환경 분리, 보수적 기본값, 저장소 분리가 그 경로를 끊는 도구입니다. 그리고 복구 수단은 사고 전에 준비돼 있어야 의미가 있습니다. PITR이나 백업은 사고 후에 켜는 게 아니라 운영의 전제입니다.

다만 이게 모든 상황의 정답은 아닙니다. Flyway나 Liquibase 같은 마이그레이션 도구를 쓰면 ddl-auto는 아예 끄고 스키마를 버전으로 관리하는 게 정석입니다. validate나 none은 그 전제 위에서의 최소 안전선일 뿐입니다. 또 로컬이나 CI 환경에서는 create-drop이 오히려 유용합니다. 매번 깨끗하게 시작해야 하는 테스트에 적합합니다. 금지의 대상은 운영과 공유 환경에 한정됩니다. 결국 핵심은 옵션 자체의 선악이 아니라 환경 구분입니다.

마지막으로 PITR도 만능은 아닙니다. RPO(목표 복구 시점)만큼은 손실 창이 남습니다. 복구가 가능하다는 말이 무손실이라는 뜻은 아닙니다. 그래서 복구 수단은 마지막 보루로 두고, 그 앞단에서 사고 경로를 끊는 게 먼저입니다.

ChatGPT Image 2026년 6월 19일 오후 01_32_45.png


같은 사고를 막고 싶을 때 점검할 것들

운영 설정의 ddl-auto가 validate 또는 none으로 명시돼 있는가

테스트 설정이 운영 실행 경로에 묻어 들어갈 여지가 없는가

create/create-drop/update가 공유·운영 환경에서 쓰이지 않도록 룰로 막혀 있는가

운영 설정과 DB 접속 정보가 저장소에서 분리돼 있는가

PITR이나 백업이 켜져 있고 보존 기간이 운영 요구를 만족하는가

사고 재발 방지가 개인 주의가 아니라 설정·룰로 굳어 있는가

운영 테이블이 사라졌던 그날의 결론은, 더 조심하자는 게 아니었습니다. 조심으로는 같은 사고가 또 납니다. 테스트는 검증만 하게, 운영은 스키마를 건드리지 않게, 파괴 옵션은 전역에서 막게, 운영 설정은 저장소에서 떼어내게. 사람이 실수해도 데이터가 살아남는 구조를 설정으로 만들어 두는 것이 운영자가 할 수 있는 가장 확실한 대비였습니다.

#운영의기술#ddlauto#JPA#데이터베이스#운영사고#환경분리#PITR