← 모든 글

운영 DB 직접 쿼리 — 어디까지 허용할 것인가

운영 DB에 손대도 되는 상황과 절대 안 되는 상황을 나눈 기준

왜 이걸 정리해야 했나

팀이 세 명일 때는 암묵적으로 돌아갔다. ‘급하면 직접 봐도 되지만 쓰기는 하지 마라.’ 다섯 명 넘어가면서 이 암묵이 깨졌다. 신규 입사자가 UPDATE 날린 게 아니라, 시니어가 SELECT 하나를 잘못 날려서 운영 슬레이브 CPU가 90% 찍혔다. 쿼리 자체가 문제였던 게 아니라 OLAP 성격 집계를 LIMIT 없이 날렸던 것. 이후 ‘직접 쿼리 = 위험’ 이라는 분위기가 생겼는데, 그게 또 다른 문제를 만들었다. 장애 원인 추적이 느려졌다. 데이터 검증을 누가 해야 하는지 책임이 흐릿해졌다. 그래서 기준을 문서로 박아뒀다.


기준: 허용 / 조건부 / 금지

허용 — 별도 승인 없이 가능

  • SELECT, 인덱스 탄다는 게 EXPLAIN 으로 확인된 것
  • 단건 조회 (WHERE id = ? 류)
  • 읽기 전용 레플리카에서 실행, 타임아웃 30초 이하
  • 장애 진행 중 원인 추적 목적 (단, 사후에 Slack #incident 채널에 실행 쿼리 기록 의무)

조건부 — 리뷰 한 명 이상 받고 실행

  • 집계성 SELECT (GROUP BY, 서브쿼리, JOIN 3개 이상)
  • LIMIT 없는 전체 스캔 가능성 있는 쿼리
  • 프라이머리에서 직접 읽어야 하는 경우 (레플리카 lag 이슈 등)
  • INSERT INTO ... SELECT ... 처럼 읽기처럼 보이지만 쓰기인 패턴

금지 — 프로세스 없이는 절대 안 됨

  • UPDATE, DELETE, DROP, TRUNCATE — 이건 배포 파이프라인이나 마이그레이션 스크립트 통해서만
  • 트랜잭션 잡고 오래 열어두는 작업 (락 전파 위험)
  • 인덱스 없는 컬럼 WHERE 조건 + 대용량 테이블 조합
  • PII 포함 컬럼을 로컬 머신으로 덤프

실제로 막상 해보면, 조건부 카테고리가 가장 말이 많다. 집계 쿼리는 EXPLAIN 봐도 실제 실행 시간을 예측하기 어렵다. 통계가 오래됐거나, 파티션 pruning이 기대대로 안 되거나. 그래서 우리는 조건부 쿼리를 레플리카에서 SET statement_timeout = '10s'; 걸고 먼저 실험 실행하는 걸 규칙으로 추가했다. 타임아웃 걸리면 쿼리 자체를 다시 짜거나, 배치 방식으로 바꾼다.

도구도 하나 고정했다. 팀 내 DB 접근은 전부 Teleport 통해서만. 로컬 클라이언트에서 직접 연결은 막혔다. 이렇게 하면 세션 로그가 자동 기록되고, 누가 언제 무슨 쿼리 날렸는지 추적된다. 초기에 ‘번거롭다’는 반응이 있었는데, 두 번째 인시던트 이후로는 이의 없다. 오히려 ‘내가 이 쿼리 날린 게 아니다’ 를 증명하는 수단이기도 하다.

PII 이슈는 생각보다 구멍이 많다. 이름, 전화번호, 이메일이 들어간 컬럼을 디버깅 중에 로컬로 CSV 저장하는 습관 — 이게 나쁜 의도 없이 발생한다. 그래서 해당 컬럼에는 DB 레벨 뷰를 하나 더 만들어서, 마스킹된 버전으로만 조회되게 했다. 원본이 필요한 경우는 별도 승인 플로우.


다음 한 가지

조건부 쿼리 리뷰 요청이 Slack DM 으로 오고 있다 — 이번 분기 안에 GitHub Issue 템플릿 하나로 채널 단일화.


🛒 이 글과 어울리는 추천 상품

위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.