Skip to Content
← 블로그
데이터베이스

개발 DB에서 운영 DB까지, 그 사이엔 절차가 없었다 — 그래서 결재 도구를 만들었다

만화로 보는 요약 — 먼저 읽어보세요
만화로 보는 요약 — 먼저 읽어보세요

개발 DB에 컬럼 하나를 추가했습니다. 코드는 Git에 있고, 리뷰는 PR로 받고, 배포는 CI가 합니다. 그런데 그 컬럼을 운영에 추가하는 ALTER TABLE 한 줄은 어떻게 옮겨갈까요.

저는 이 구간에서 반복적으로 막혔습니다. 코드가 운영까지 가는 길은 잘 닦여 있는데, 스키마가 운영까지 가는 길은 매번 새로 만들어야 했습니다.

코드는 파이프가 있는데 스키마는 없다

그 경로는 매번 다르다

개발망에서 변경을 만들고 검증합니다. 여기까지는 문제가 없습니다. 그다음이 문제입니다.

SQL을 메신저나 메일로 보냅니다. 받는 사람이 눈으로 훑어보고 "이거 인덱스 없이 돌리면 락 걸릴 것 같은데요"라고 답합니다. 몇 번 오간 뒤 승인이 떨어지고, 누군가 접속해서 실행합니다.

이 경로는 매번 다르게 흘러갑니다. 어떤 변경은 두 사람이 봤고 어떤 변경은 한 사람이 봤습니다. 어떤 변경은 적용됐고 어떤 변경은 스레드 아래로 밀려 잊혔습니다. 개발 DB에는 있는데 운영 DB에는 없는 컬럼은 그렇게 생깁니다. 반대도 생깁니다.

남는 기록은 메신저 스크롤과 각자의 기억뿐입니다. 몇 달 뒤 "이 변경 누가 승인했죠?"라는 질문에 답할 방법이 없습니다.

이건 게으름 때문이 아니다

절차를 안 지켜서 생긴 일이라고 생각했다면 쉬웠을 겁니다. 실제로는 반대였습니다.

절차가 빡빡한 조직일수록 오히려 도구가 없습니다. 금융·공공처럼 내부통제상 사람이 반드시 확인해야 하는 환경에서는 확인해야 할 항목이 더 많습니다. 그런데 그 절차는 문서로만 존재하고, 실행은 담당자의 성실성에 맡겨집니다. 규칙이 많아질수록 사람이 감당해야 할 양만 늘어납니다.

규제는 대기업 기준, 예산은 스타트업 기준

이게 근본 원인입니다.

규제는 조직 규모를 봐주지 않습니다. 소규모 금융 도메인 조직도 대형사와 같은 기준의 내부통제를 요구받습니다. 운영 DB 변경에 검토와 승인이 있어야 하고, 그 이력이 감사 시점에 증적으로 제시돼야 합니다. "저희는 인원이 열 명이라서요"는 통하지 않습니다.

그런데 예산은 규모를 그대로 따라옵니다. DB 거버넌스 상용 솔루션이 없는 건 아닙니다. 다만 대기업 기준으로 가격이 매겨져 있고, 라이선스 비용만이 아니라 컨설팅과 구축 기간이 함께 붙습니다. 팀 규모에 비해 감당하기 어려운 투자입니다.

그래서 결국 이렇게 됩니다 — 필요한 건 아는데 살 수는 없으니, 엑셀과 메신저와 사람의 성실성으로 절차를 흉내 냅니다.

이 간극이 제가 풀고 싶었던 문제입니다. 규제 대응을 위한 절차를 예산 없이도 가질 수 있어야 합니다.

속도를 늦추는 도구를 만들었다

Flyway는 이 문제를 풀지 않는다

DB 형상관리 하면 Flyway나 Liquibase가 떠오릅니다. 훌륭한 도구지만 이 문제를 겨냥하지 않습니다. 우열의 문제가 아니라 최적화 대상이 다릅니다.

마이그레이션 도구 (Flyway·Liquibase)변경 결재 도구
최적화 대상반복 가능·빠른 적용건너뛸 수 없는 검수 단계
사람 개입줄이는 것이 목표늘리는 것이 목표
산출물버전 순서대로 적용된 스키마누가 작성·검토·결재했는지의 증적
표현 못 하는 것"이 변경은 DBA 승인 후에만 적용""CI가 알아서 순서대로 적용"

Flyway에 "이 마이그레이션은 DBA 김 아무개의 승인이 있어야 적용된다"를 표현할 방법은 없습니다. 그건 Flyway의 결함이 아니라 관심사가 다른 겁니다. 제게 필요했던 건 정반대였습니다 — 속도를 늦추더라도 통제된 절차를 강제하는 것.

그래서 만들었습니다. DBFlow — 마이그레이션 도구보다는 DB 변경을 위한 결재 시스템에 가깝습니다.

작성 → 제출 → 검토(지정 검토자) → 결재(지정 결재자) → 백업 → 적용

설계 원칙은 셋입니다.

  1. 각 단계에 책임자가 명시된다. "팀에서 봤다"가 아니라 특정 계정이 특정 시각에 승인했다는 기록이 남습니다.
  2. 승인과 반려가 모두 기록된다. 반려 이력이 빠지면 한 번에 통과된 변경과 세 번 반려 후 통과된 변경을 구분할 수 없습니다.
  3. 검수를 건너뛰는 옵션을 제공하지 않는다. 긴급 우회 경로를 만들면 그 경로가 곧 기본 경로가 됩니다.

절차가 무너지는 지점은 기술이 아니라 사람이다

만들면서 가장 오래 고민한 부분입니다. 절차 도구가 현실에서 무너지는 건 대개 기술 문제가 아닙니다. 결재자가 휴가를 가면 모든 게 멈춥니다. 그리고 멈추면 사람들은 절차를 우회하기 시작합니다.

그래서 조직의 사정을 시스템 안에 넣었습니다.

장치해결하는 문제동작
부재 위임결재자 부재로 변경이 전면 정지같은 역할 동료에게 기간을 정해 위임, 대리 수행 사실도 기록
직무 분리 (SoD)한 사람이 자기 요청을 자기가 승인한 요청에서 결재 슬롯을 두 개 차지할 수 없음
동결기간 (freeze)분기 마감·감사 기간의 변경 리스크이미 승인된 변경도 적용되지 않게 차단
작업창 (window)임의 시각 적용으로 인한 장애정해진 점검 시간대 안에서만 적용 허용

감사 로그는 애플리케이션이 아니라 DB가 지킨다

감사 로그를 남기는 것은 쉽습니다. 어려운 건 그 로그가 나중에 고쳐지지 않았음을 증명하는 겁니다.

"수정 API를 안 만들었다"는 증명이 아닙니다. DB에 직접 접속하면 그만입니다. 그래서 감사 테이블에 트리거를 걸어 UPDATE와 DELETE 자체를 막았습니다.

CREATE TRIGGER audit_log_no_update BEFORE UPDATE ON `audit_log`
FOR EACH ROW SIGNAL SQLSTATE '45000'
  SET MESSAGE_TEXT = 'audit_log is append-only';

append-only가 앱의 약속이 아니라 스토리지 계층의 제약이 됩니다.

안전한 척하지 않는 백업

승인까지 받은 SQL이라도 실행하면 되돌릴 수 없는 것이 있습니다. 그래서 적용 직전에 대상 테이블을 자동 백업하고, 문제가 생기면 롤백할 수 있게 했습니다.

다만 데이터가 큰 테이블은 임계치를 넘으면 스키마만 백업하고 넘어갑니다. 이 경우 "이 백업으로는 데이터 복구가 불가능하다"고 UI에 표시합니다.

조용히 안전한 척하는 백업은 백업이 없는 것보다 위험하다고 봤습니다. 복구할 수 있다고 믿으면 더 과감한 변경을 승인하게 되니까요.

아직 운영에는 못 올렸다

도입 협의는 다음 단계로 잡아뒀고, 지금은 다른 작업이 우선입니다.

다만 만들어놓고 방치한 건 아닙니다. 개발환경에서는 계속 쓰고 있습니다. 개발 DB에 들어가는 변경을 이 도구를 통해서 올리고 있고, 그래서 이 글의 설계 판단들은 최소한 "화면에서 한 번 돌려본 것"은 아닙니다. 매일 쓰는 사람이 저 자신이라는 한계는 있지만요.

만든 것, 그리고 계속 쓰고 있는 것

동작하는 오픈소스 한 벌입니다. Keycloak처럼 이미지 하나 받아서 자기 인프라에 올리는 방식이고, 웹과 API가 한 컨테이너에서 함께 뜹니다.

curl -O https://raw.githubusercontent.com/Hyeonqz/dbflow/main/docker-compose.hub.yml
curl -o .env https://raw.githubusercontent.com/Hyeonqz/dbflow/main/.env.example
# .env에 시크릿과 최초 관리자 계정을 채운 뒤
docker compose -f docker-compose.hub.yml up -d

마이그레이션은 기동할 때 돌고, 최초 관리자는 환경변수로 만들어집니다. 기본 시크릿이나 약한 값으로는 아예 부팅을 거부합니다 — 셀프호스팅 도구가 기본값 그대로 도는 게 제일 흔한 사고 경로라서입니다.

개발환경에서 계속 쓰면서 알게 된 건 기능이 아니라 감각이었습니다. 설계할 때는 "검수 단계를 강제한다"가 원칙이었는데, 막상 매일 쓰다 보면 그 단계가 실제로 얼마나 귀찮은지가 몸으로 옵니다. 그 귀찮음의 크기가 곧 사람들이 절차를 우회할 동기의 크기입니다. 부재 위임과 작업창을 넣은 건 문서를 보고 정한 게 아니라, 제가 직접 막히고 나서였습니다.

만들면서 알게 된 한계

만들기 전에는 몰랐던 것들입니다. 결과라고 부를 수 있는 게 있다면 이쪽입니다.

트리거는 방어선이지 증명이 아닙니다. TRIGGER 권한이 있는 계정이라면 DROP TRIGGER 후 수정하고 다시 만들 수 있습니다. DBA와 root는 언제나 이 경로를 쓸 수 있습니다. 진짜 불변성이 필요하면 권한 분리, 해시 체인, 외부 WORM 스토리지로 단계를 더 올려야 하고, 그건 위협 모델에 내부자가 명시적으로 포함될 때의 이야기입니다.

"절차를 강제한다"와 "절차가 지켜진다"도 다릅니다. 도구는 우회 버튼을 없앨 수 있지만, 사람이 도구 밖에서 DB에 붙는 것까지 막지는 못합니다. 접근 통제가 함께 가야 의미가 있습니다.

아직 확인하지 못한 것

정직하게 남겨둘 것들입니다.

  • 개발환경 사용은 실운영 검증이 아닙니다. 개발 DB는 데이터가 작고, 장애가 나도 되고, 결재 라인이 사실상 저 혼자입니다. 실제 감사에서 증적으로 통과한 적이 없고, 여러 사람이 서로의 변경을 반려하는 상황도 겪어보지 못했습니다. 절차 도구의 진짜 시험은 거기서 시작합니다.
  • 부하 특성을 모릅니다. 변경 요청이 하루 수백 건 쌓이는 조직에서 어떻게 되는지 측정하지 못했습니다.
  • 대상 DB는 MySQL만 지원합니다. PostgreSQL은 아직입니다.
  • 백업 임계치의 적정값을 모릅니다. 어느 크기부터 스키마만 백업할지는 지금 임의로 잡은 값입니다.

그래서 누구에게 추천하나

운영 도입 성과가 없으니 "이렇게 해결했습니다"라고 쓸 수 없습니다. 대신 어떤 조직에 맞는 도구인지는 만들면서 분명해졌습니다.

맞는 경우

  • 규제나 내부통제상 운영 DB 변경에 사람의 검토·승인이 반드시 필요한데, 그걸 스프레드시트와 메신저로 때우고 있는 팀
  • 개발 DB와 운영 DB의 스키마가 갈라져 있고, 언제부터 갈라졌는지 아무도 모르는 팀. ERD 문서가 실제 스키마와 다른데 어느 쪽이 맞는지 확인할 방법이 없다면 같은 증상입니다. 변경이 전부 한 통로를 지나면 그 이력 자체가 "스키마가 지금 모습이 된 경위"의 기록이 됩니다
  • 상용 DB 거버넌스 솔루션의 가격이 팀 규모에 맞지 않는 팀
  • 대상 DB가 MySQL인 팀

안 맞는 경우 — 승인 절차 없이 CI가 마이그레이션을 적용해도 되는 팀입니다. 이 도구는 의도적으로 속도를 늦춥니다. 통제가 필요 없다면 Flyway가 정답입니다. 그리고 이미 상용 거버넌스 솔루션이 있다면 굳이 바꿀 이유가 없습니다.

열어둔 이유는 단순합니다. 운영 DB 변경 이력을 남겨야 하고, 개발과 운영의 스키마를 맞춰야 하고, ERD를 실제와 일치시켜야 하는데 — 그걸 할 도구를 살 예산은 없는 팀. 그 상태로 버티는 팀이 생각보다 많다고 봅니다. 저희도 그중 하나였고, 그래서 직접 만들었습니다.

같은 자리에 있는 팀이 이걸 가져다 썼으면 합니다. 그리고 저희보다 먼저 운영에 올려보신다면, 어땠는지 알려주시면 가장 좋겠습니다.


GitHub: Hyeonqz/dbflow · Docker Hub: calixjin/dbflow · 라이선스 AGPL-3.0

개념 정리는 위키에 따로 두었습니다 — 운영 DB 변경 관리, 감사 테이블 append-only 강제.

여러분의 조직에서는 운영 DB 변경이 어떤 절차로 이루어지나요.