기술

접근 권한을 사본 테이블로 판정했더니, 떠난 담당자 35명이 그대로 남았습니다

2026.08.299분 읽기

프랜차이즈 ERP에는 매장 담당자만 들어오는 웹이 하나 붙어 있습니다. 로그인한 사람이 이 매장의 담당자인지를 판정하는 로직이 매출 조회, 발주, 공지, 1:1 문의 네 군데에 각각 복사돼 있었습니다.

네 곳 모두 같은 테이블을 근거로 썼습니다. 그 테이블은 한 달 전 마이그레이션으로 만들어진 담당자 연락처의 사본이었습니다.

처음 들어온 입력은 한 방향뿐이었습니다. 새로 부임한 담당자 7명이 자기 매장에 들어가지 못한다는 내용이었습니다. 접근이 막힌 사람은 업무가 멈추니 바로 전화를 걸었습니다.


권한 오류는 비대칭으로 발견됩니다

막힌 쪽을 고치려고 판정 근거를 거꾸로 따라 올라가다가 반대편을 확인했습니다. 담당자가 교체된 매장 35곳에서 떠난 사람의 접근이 그대로 살아 있었습니다. 같은 원인에서 나온 두 종류의 오류였는데, 저에게 도달한 것은 한쪽뿐이었습니다.

권한 오류는 대칭으로 발생하고 비대칭으로 발견됩니다. "못 들어간다"는 신고는 오지만 "아직 들어가진다"는 아무도 신고하지 않습니다. 이득을 보는 쪽은 침묵하고 손해를 보는 쪽만 소리를 냅니다.

그래서 접근 제어의 실제 상태를 사용자 신고로 파악하면, 시스템은 항상 한 방향으로만 수렴합니다. 과소 허용은 문의로 정리되고 과다 허용은 조용히 누적됩니다.

실제 위험도 조용한 쪽이 더 컸습니다. 1:1 문의 화면이 같은 판정을 쓰고 있었으니, 이미 관여하지 않는 매장의 이름으로 문의를 넣을 수 있는 상태였습니다. 읽기만 열린 문제가 아니었습니다.

다운로드.png


사본은 만들어진 날짜에 멈춰 있었습니다

사본 테이블을 열어보니 생성자 표시가 전부 마이그레이션이었습니다. 한 달 전 일괄로 만들어진 뒤 갱신된 행이 한 건도 없었습니다. 그사이 담당자 35곳 일괄 교체가 있었고, 그 변경은 정본 관계에만 반영됐습니다.

사본이 만들어지는 기준은 정확했습니다. 문제는 정확한 채로 멈춰 있었다는 점입니다. 사람과 소속은 계속 변하는 정보인데, 복사는 특정 시점의 스냅샷을 남기는 동작입니다. 변하는 것을 복사해두면 갈라지는 것은 시점 문제일 뿐입니다.

접근 제어 사고는 대개 판정 코드 한 줄이 아니라 판정의 기본값에서 나옵니다. 지난번에는 인증이 걸린 경로 패턴 밖으로 새 주소가 하나 빠져나가면서 같은 종류의 구멍이 났습니다. 그때는 기본값을 차단으로 뒤집는 것이 해법이었고, 이번에는 판정 근거를 정본으로 되돌리는 것이 해법이었습니다.


동기화 배치를 붙이는 선택지도 있었습니다

가장 빠른 수습은 사본을 주기적으로 맞추는 배치를 하나 추가하는 방법이었습니다. 코드 변경 범위가 좁고 당일 배포가 가능합니다. 하지만 이 선택은 사고를 없애는 것이 아니라 사고 창의 크기를 조절하는 선택입니다.

선택지

얻는 것

남는 위험

사본을 주기 동기화

코드 변경 최소, 즉시 적용

동기화 주기만큼 권한 사고 창이 남고, 배치가 멈추면 조용히 원상 복귀

판정을 정본으로 이관

동기화라는 개념 자체가 사라짐

매 판정에 조인 비용, 판정 로직 전수 교체 필요

배치 방식의 진짜 문제는 실패가 조용하다는 점입니다. 배치가 죽으면 권한은 마지막으로 성공한 시점에 고정되고, 그 상태는 이번 사고와 똑같이 아무 신고도 만들지 않습니다.


조건으로 배제하지 않고 구조로 배제했습니다

판정을 매장과 담당자, 매장과 운영자 두 정본 관계의 합집합으로 옮겼습니다. 사람을 식별하는 키는 도메인 온톨로지에 이미 정의된 규칙을 그대로 썼습니다. 판정기는 클래스 하나와 SQL fragment 하나로 단일화하고, 네 곳의 복붙 조건문과 사본 엔티티·리포지토리는 제거했습니다.

여기서 중요한 것은 정본을 본다는 사실보다 정본의 모양이었습니다. 이 관계 테이블은 매장당 1행 유니크입니다. 담당자가 교체되면 행이 추가되지 않고 대체되며, 전임자는 이력 테이블로 빠집니다.

즉 "현재 담당자만 통과"라는 조건을 WHERE 절로 걸어 전임자를 걸러내는 구조가 아닙니다. 전임자가 애초에 그 테이블에 존재할 수 없습니다. 조건으로 배제하면 조건을 빼먹은 쿼리가 언젠가 하나 생기지만, 구조로 배제하면 빼먹을 조건 자체가 없습니다. 권한 코드에서는 이 차이가 사고 유무를 가릅니다.

다운로드 (1).png

사본 엔티티를 지우기 전에 참조를 전수 조사했습니다. 판정 외에 조회 화면 한 곳이 이 테이블을 읽고 있어서 함께 정리해야 했습니다. 예전에 1:1 가정이 깨진 컬럼을 제거할 때도 같은 순서를 밟았고, 그때 배운 규칙은 참조를 전부 찾은 뒤에 순서를 정해 움직이라는 것이었습니다.


줄어든 28의 정체를 설명했습니다

권한을 줄이는 배포에는 고유한 검증 부담이 있습니다. 권한을 늘리는 변경은 잘못되면 사용자가 즉시 알려주지만, 줄이는 변경은 잘못되면 앞서 말했듯 조용히 사라집니다. 그래서 접근 가능 매장 수를 배포 전후로 비교했습니다.

결과는 198에서 170으로 28 감소였습니다. 확인해야 할 것은 총량이 아니라 감소분의 구성이었습니다.

빠진 35: 담당자가 교체된 매장의 전임자 접근 — 이번 작업의 목표

들어온 7: 자기 매장에 못 들어가던 현 담당자 — 문의로 접수됐던 건

합계 -35 +7 = -28 — 남은 것 없음

여기서 한 단계 더 확인했습니다. 접근이 사라진 35곳이 전부 현 담당자 연락처가 비어 있는 매장인지 대조했습니다. 만약 연락처가 채워져 있는데도 접근이 사라진 매장이 하나라도 있었다면, 그것은 의도한 배제가 아니라 새 판정 로직의 결함이었을 것입니다. 대조 결과 그런 매장은 없었습니다.

정리하면 198에서 170으로 줄었다는 보고는 검증이 아닙니다. 사라진 28의 정체를 항목별로 설명하지 못하면 그 안에 조용한 상실이 섞여 있고, 그 상실은 누군가 업무를 못 해서 연락이 올때까지 발견되지 않았을 것입니다.


배포 순서를 어겨 1분 30초 장애를 냈습니다

여기까지는 계획대로였습니다. 사본 테이블은 지우지 않고 208행을 보존한 채 백업 이름으로 RENAME 하기로 했고, 플랜에는 배포 이후 RENAME 이라고 선행 조건까지 적어두었습니다. 그런데 배포 전에 RENAME 을 실행했습니다.

운영에는 여전히 구버전 코드가 돌고 있었습니다. 그 코드는 방금 이름이 바뀐 테이블을 참조했고, 약 1분 30초 동안 담당자 웹의 판정이 전부 실패했습니다. 즉시 신버전을 배포해 복구했지만, 원인은 기술이 아니라 제가 적어둔 순서를 제가 안 지킨 것입니다.

이 실수에서 일반화할 것이 하나 있습니다. 스키마 변경과 코드 배포의 순서는 변경의 방향에 따라 뒤집힙니다. 참조를 끊는 변경은 코드가 먼저 나가야 하고, 참조를 추가하는 변경은 스키마가 먼저 나가야 합니다.

변경의 방향

예시

먼저 나가야 하는 것

참조를 끊는 변경

RENAME, DROP, 컬럼 제거

코드 — 아무도 안 보게 만든 뒤 치운다

참조를 추가하는 변경

ADD COLUMN, 신규 테이블

스키마 — 볼 것을 먼저 만들어둔다

다운로드 (2).png


이 해법이 깨끗했던 조건

정본 조인 판정은 비용이 듭니다. 트래픽이 큰 서비스라면 캐시가 필요하고, 그때는 규율이 하나 붙어야 합니다. 캐시는 판정의 결과이지 판정의 근거가 아니며, 무효화 규칙이 없는 캐시는 이번 사본 테이블과 정확히 같은 물건이 됩니다.

또 이번 정본 관계는 매장당 현재 담당자 1명이라는 단순한 구조였습니다. 담당자가 여러 명이고 기간이 겹치는 도메인이면 기간 조건이 다시 들어오고, 구조로 배제하던 것을 조건으로 배제하는 지점이 생깁니다.


점검 목록

권한 판정이 참조하는 테이블이 정본인지, 어딘가에서 복사된 값인지 확인한다

복사된 값이라면 마지막 갱신 시각과 생성자를 조회한다 — 전부 마이그레이션이면 그 시점에 멈춰 있다

같은 판정 로직이 몇 군데에 복붙돼 있는지 센다 — 두 곳 이상이면 한 곳만 고쳐지는 미래가 온다

권한을 줄이는 배포는 총량 비교로 끝내지 않고 감소분을 항목별로 설명한다

참조를 끊는 스키마 변경은 코드 배포 이후로 순서를 못 박고, 실행 직전에 그 순서를 다시 읽는다

이번 작업에서 실제로 무서웠던 부분은 사본 테이블이 아니었습니다. 한 달 동안 아무 신고도 없이 정상으로 보였다는 점입니다. 접근 제어에서 조용함은 정상 신호가 아니라 정보가 없는 상태이고, 그 차이를 메우는 방법은 지금까지 정기 대조밖에 찾지 못했습니다.