감사 추적의 문제: 어떤 규칙이 어떤 결정을 내렸는지 증명하기
막힌 이체 하나가 로그에 남기는 건 한 줄뿐입니다. 몇 달 뒤 감사관이 이유를 묻지만 git은 답하지 못합니다. 코드의 이력은 남겨도 결정의 이력은 남기지 않기 때문입니다. 제대로 된 결정 감사 추적이 무엇을 기록하고, 왜 불변이어야 하는지 설명합니다.
이체가 막힙니다. 청구가 거절됩니다. 할인이 빠집니다. 시스템은 시키는 대로 정확히 일을 했고, 로그에 한 줄을 남깁니다. blocked: rule failed. 에러 코드 하나, 타임스탬프 하나가 전부일 때도 있습니다.
그 순간 당직 엔지니어는 이유를 정확히 압니다. 어떤 규칙이, 어떤 임계값이, 어떤 입력에 걸렸는지가 머릿속에 그대로 있습니다.
몇 달 뒤, 누군가 그 이체가 왜 막혔는지 묻습니다. 남은 건 그 로그 한 줄뿐이고, 거기엔 답이 없습니다.
그런 일이 생기면 그때 처리하면 된다고 스스로 미뤄 둡니다. 그리고 그 일은 생깁니다.
감사관이 실제로 던지는 질문
언젠가는 결정 하나가 도마에 오릅니다. 컴플라이언스 점검, 고객 분쟁, 규제 기관. 그리고 그 질문은 한 번도 두루뭉술한 적이 없습니다.
- 바로 그날, 이 이체는 왜 막혔습니까?
- 그날 적용된 규칙이 정확히 무엇이었습니까?
- 이 거절의 근거를 보여 주세요.
- 이 규칙을 누가, 언제 바꿨습니까?
전부 답할 수 있는 질문입니다. 하나하나가 분명한 사실로 답이 정해져 있습니다. 결정은 이유가 있어서 내려졌고, 그 이유는 그때 기록됐거나 기록되지 않았거나 둘 중 하나입니다. 변수는 단 하나입니다. 시스템이 그 이유를 실행 시점에 적어 뒀느냐, 아니면 1년 뒤 누군가 되짚어 맞추기를 기대했느냐.
그리고 이런 질문에는 대개 기한이 붙어 있습니다. 규제 기관은 결정의 근거를 며칠 안에 내놓으라고 요구하지, 몇 달을 기다려 주지 않습니다. 분쟁도 마찬가지입니다. 기한은 다가오는데 할 수 있는 말이 "아직 짜 맞추는 중입니다"뿐이라면, 이미 지고 들어가는 셈입니다.
git과 코드로는 답할 수 없는 이유
먼저 코드를 떠올리게 됩니다. 어차피 git에 다 있으니 이력을 보면 된다고요.
하지만 git이 쥐고 있는 건 코드의 이력이지 결정의 이력이 아닙니다. 둘은 서로 다른 기록입니다.
막은 규칙이 코드베이스 안에 있었다고 해 봅시다. git은 그날 그 규칙이 아마 어떤 모습이었는지를 보여 줄 수 있습니다. 맞는 브랜치에서 맞는 커밋을 찾아낼 수 있다면요. 하지만 그 이체 하나가 실제로 어떤 입력을 들고 있었는지, 그 요청에서 로직의 어느 갈래가 실제로 실행됐는지, 규칙이 그 요청에 무엇을 돌려줬는지는 알려주지 못합니다. 코드는 틀이고, 결정은 그 순간 일어난 사건입니다. git은 틀을 버전 관리할 뿐 사건에 대해서는 아무 말도 하지 않습니다.
이번엔 그 규칙이 코드에 없었다고 해 봅시다. config 테이블, 관리자 설정, 기능 플래그 안에 있었다고요. 많은 비즈니스 로직이 그렇습니다. 그러면 git은 보여 줄 게 없습니다. 그날 살아 있던 값은 바뀌는 순간 덮어써졌고, 이전 값은 그냥 사라졌습니다.
어느 쪽이든 "이건 왜 막혔지?"에 답하는 일은 발굴 작업이 됩니다. 커밋을 파고, 입력을 추측하고, 로그 한 줄이 빈틈을 메워 주기를 바랍니다. 그러고도 증명은 못 합니다. 할 수 있는 말은 "아마도"뿐입니다. 감사관은 "아마도"를 받아 주지 않습니다.
여기서 자연스럽게 드는 생각이 있습니다. 로그를 더 남기면 되지 않느냐는 것입니다. 결정마다 입력, 규칙, 결과를 남기면 빈틈이 메워진다고요. 그 직관은 옳습니다. 결정 감사 추적의 뼈대가 사실 그것입니다. 하지만 그때그때 로그로 처리하면 대개 버티지 못합니다. 감사관에게 답하려면 입력과 이긴 규칙뿐 아니라, 진 규칙과 그 이유, 그리고 그 순간 살아 있던 로직의 정확한 버전까지 미리 남겨 뒀어야 합니다. 실행되던 날 한 항목이라도 빠뜨리면, 그 항목은 영영 사라집니다. 게다가 로그는 자유 형식 텍스트라, '검색할 수 있다'와 '증명할 수 있다'는 같은 말이 아닙니다.
또 흔한 방법은 애플리케이션이 결정과 나란히 기록하는 별도 감사 테이블입니다. 로직과 테이블이 어긋나기 전까지는 잘 돌아갑니다. 새 코드 경로를 만들면서 기록 남기는 걸 빠뜨립니다. 리팩토링이 '규칙'의 의미를 바꿔도 옆의 로깅은 그대로입니다. 이제 감사 기록과 실제 결정은 서로 어긋날 수 있는 두 출처가 되고, 둘이 어긋나는 순간 어느 쪽도 증거가 아닙니다. 믿을 수 있는 기록은 누군가 잊지 않고 따로 남겨 준 기록이 아니라, 결정이 내려지는 순간 그 부산물로 저절로 남는 기록뿐입니다.
결정 감사 추적이 기록하는 것
길은 하나입니다. 결과만 기록하지 말고 결정을 기록하는 것.
시스템이 결정에 이를 때마다, 거기까지 어떻게 갔는지를 트레이스로 온전히 남깁니다.
- 어떤 규칙이 발동했는지. 그리고 평가는 됐지만 발동하지 않은 규칙은 무엇이고, 각각 왜 맞거나 어긋났는지
- 결정이 본 입력을, 본 그대로
- 그 순간 살아 있던 규칙 버전
- 언제 실행됐고, 어느 배포에 속했는지
이게 갖춰지면 "이건 왜 막혔지?"는 재구성이 아니라 조회가 됩니다. 막힌 이체는 자기를 막은 임계값과 자기가 본 입력을 함께 달고 있습니다. 거절된 청구는 자기를 거절한 규칙과 그 규칙이 속한 버전을 함께 달고 있습니다. 답을 파내는 게 아니라 읽습니다.
조금 전의 그 분쟁을 떠올려 보세요. 일주일에 걸친 재구성 대신, 답은 트레이스 하나입니다. 이 이체는 그 시점에 살아 있던 규칙 버전으로 평가됐고, 계좌별 한도가 그걸 막은 규칙이며, 그것이 본 입력은 이렇습니다. 기록 하나 여는 시간이면 끝납니다.
자주 빠뜨리는 두 번째 요건이 있습니다. 트레이스는 결정이 본 입력을 담고, 그 입력 중 일부는 민감합니다. 이름, 계좌번호, 식별자. 들여다볼 때마다 그 데이터가 그대로 드러나는 추적은 컴플라이언스 문제 하나를 다른 문제로 바꿔 놓을 뿐입니다. 그래서 제대로 된 감사 추적은 민감한 항목을 기본으로 마스킹하고, 그걸 푸는 행위 자체도 기록으로 남깁니다. 누가 무엇을 봤는지를 추적에 남기는 겁니다. 감사 추적은 자기가 강제하려는 기준을 자기 자신에게도 똑같이 적용합니다.
불변이어야 하고, 보관돼야 하는 이유
조용히 고칠 수 있는 기록은 증거가 아닙니다. 감사 추적의 가치는 전부 여기서 나옵니다. 일이 벌어진 뒤에는 누구도 자기 입맛에 맞게 고칠 수 없다는 것. 그래서 트레이스는 한 번 쓰면 고칠 수 없습니다. 추가만 되고, 변경되지 않습니다.
불변은 있으면 좋은 성질이 아니라 증거의 전제 조건입니다. 기록을 받아 든 감사관이 가장 먼저 의심하는 건 "이거, 나중에 결과에 맞춰 고친 것 아닌가"입니다. 애초에 고칠 수 없는 기록 앞에서는 그 의심이 성립하지 않습니다.
그리고 질문보다 오래 남아야 합니다. 분쟁은 몇 달 뒤에, 규제 점검은 그보다 더 뒤에 찾아옵니다. 몇 주 만에 잘려 나가는 추적은 정작 필요한 순간에 사라지고 없습니다. 그래서 이력을 보관합니다. LexQ에서는 요금제와 상관없이 모든 결정이 불변 감사 로그에 남습니다. 보관 기간만 요금제에 따라 달라지고, 가장 길게는 730일, 꼬박 2년입니다. 1년 반 전의 결정도 오늘 아침의 결정과 똑같은 정밀도로 답할 수 있습니다.
불변과 보관, 이 둘이 맞물릴 때 트레이스는 디버깅 편의를 넘어 당당히 내놓을 수 있는 답이 됩니다.
언제 필요하고, 언제 필요 없는가
결정 감사 추적은 분명한 조건에서 제값을 합니다.
- 결정이 규제·감사 대상입니다. 대출, 결제, 보험, 의료. 어떤 기관이든 결과의 근거를 요구할 수 있는 영역.
- 결정에 분쟁이 붙고, 그게 왜 그렇게 됐는지 보여 줘야 합니다.
- 로직을 바꾸는 사람이 둘 이상이라, "누가 언제 바꿨나"가 실제 질문입니다.
- "나중에 증명하라"는 요구가 이미 눈에 보입니다. 오늘 내리는 결정을 두고서요.
그리고 과한 경우.
- 감사·규제 노출이 없고, 앞으로도 없습니다.
- 결정이 한 사람 머릿속에 담길 만큼 작습니다. 거의 안 바뀌고, 쓴 사람이 처음부터 끝까지 쥐고 있는 결정. 그렇다면 로그 한 줄과 본인 기억으로 충분하고, 결정 감사 추적은 쓰지도 않을 장치입니다.
이 추적은 언젠가 누군가 책임을 물을 결정을 위한 것입니다. 그런 결정이 아니라면, 필요 없습니다.
LexQ는 어디에 있나
LexQ는 모든 결정을 완전한 트레이스와 함께 불변 감사 로그에 기록합니다. 어떤 규칙, 어떤 버전, 어떤 입력으로. 모든 실행이 발동한 규칙, 발동하지 않은 규칙, 결정이 본 입력, 실행 시점에 살아 있던 버전과 배포를 그대로 남깁니다. 이 기록은 실행되는 그 순간에 봉인됩니다. 나중에 배포 이력이 바뀌어도 과거 결정의 출처는 흔들리지 않습니다. 그 전체를 펼쳐 보여주는 기능이 의사결정 출처(Decision Provenance)입니다. 결정과 입력, 규칙 버전, 그리고 누가 작성하고 발행하고 배포했는지의 책임 이력까지 한 화면에서 따라갈 수 있습니다. 민감한 입력은 기본으로 마스킹되고, 마스킹을 푸는 행위 자체가 감사에 남습니다. 이력은 불변이고, 최대 730일까지 보관할 수 있습니다. 오늘 내린 결정이 2년 뒤에도 증명할 수 있는 결정으로 남습니다.
핵심은 로그를 더 남기는 게 아닙니다. 시스템이 내리는 모든 결정에, 누가 묻기 전에 이미 준비된 답이 있다는 것입니다.
→ 모든 결정에 답할 수 있게 lexq.io에서 무료로 시작하기