MCP Tool의 결과는 사용자에게 보여 줄 데이터인 동시에, 개인정보나 기밀 정보가 섞였는지 살펴야 하는 감사 대상입니다.

문제는 두 책임을 한 요청 안에 묶었을 때 드러났습니다. 외부 DLP API가 느려지거나 잠시 실패하면 Tool 실행과 결과 저장이 정상이어도 사용자 응답까지 밀렸습니다. 반대로 검사를 완전히 떼어내면서 처리 상태를 남기지 않으면 어떤 결과가 빠졌는지 나중에 찾기 어렵습니다.

따라서 기술보다 정책을 먼저 나눴습니다. 응답 전에 차단해야 하는 경로와 결과 저장 뒤 감사해도 되는 경로는 기다리는 범위부터 달라야 했습니다.

어떤 검사는 기다리고, 어떤 검사는 나중에 볼 수 있을까

같은 DLP 검사라도 목적이 다르면 완료 기준도 달라집니다. 응답 전에 민감정보를 막아야 하는 경로라면 판정이 끝나기 전에는 사용자에게 결과를 보낼 수 없습니다. 사후 감사가 목적이라면 응답을 막는 것보다 검사 대상과 처리 결과가 빠짐없이 남는지가 더 중요합니다.

3열 표
정책 목적사용자 응답 기준DLP 실패 처리
응답 전 차단DLP 판정이 끝난 뒤 응답판정 실패 시 차단하거나 별도 격리
사후 감사검사 이벤트가 안전하게 인계되면 응답실패 상태와 재처리 근거를 보존
이상 징후 탐지개별 판정과 응답을 분리할 수 있음오류 유형과 반복 패턴을 집계

이 경로에는 Tool Result를 응답 전에 차단하는 규칙이 없었습니다. 대신 무엇이 검사 대상이었는지, 실패한 검사를 다시 처리할 수 있는지가 남아야 했습니다. 사용자 요청은 Result 저장과 검사 이벤트 인계까지 맡고, 실제 DLP 판정과 감사 이력은 별도 상태로 관리하는 편이 요구사항에 맞았습니다.

정책 경계가 정해지자 기술 선택도 좁혀졌습니다. 외부 DLP API를 사용자 요청에서 직접 부를 이유는 사라졌지만, 검사 실패를 정상으로 덮어둘 수도 없었습니다.

DLP 지연이 사용자 응답으로 번진 이유

동기 방식은 구현이 단순한 대신 외부 DLP API의 상태가 사용자 응답 시간에 그대로 반영됩니다. p99가 길어지면 대기 시간도 함께 늘고, 네트워크 오류에 retry까지 붙으면 한 번의 MCP 실행에서 같은 지연이 반복됩니다.

기존 채팅 DLP 큐를 공유하면 운영 지점은 줄어듭니다. 그러나 채팅 메시지는 비교적 일정하게 들어오는 반면 Tool Result는 한 요청에서 여러 건이 연달아 생길 수 있습니다. 같은 큐에 넣을 경우 MCP 쪽 burst가 기존 채팅 검사까지 밀어낼 가능성이 있었습니다.

3열 표
선택지좋은 점주의할 점
동기 DLP 검사응답 전에 DLP 검사 결과를 확정할 수 있습니다.외부 DLP 지연과 장애가 사용자 응답 시간에 직접 반영됩니다.
기존 큐 공유운영 지점과 구현 경로가 단순합니다.Tool Result 이벤트가 기존 채팅 DLP 지연으로 번질 수 있습니다.
별도 비동기 경로응답 경로와 감사 경로를 분리할 수 있습니다.실패 이벤트를 상태로 남기고 재처리 기준을 별도로 관리해야 합니다.

선택 기준은 처리량보다 장애가 어디까지 번지는지였습니다. 사용자 응답과 기존 채팅 검사, MCP Tool Result 검사가 한 경로에 묶이면 한쪽의 지연이 다른 기능까지 늦출 수 있습니다. 따라서 응답 전 판정 여부와 큐 분리, 실패 저장 위치를 같은 관점에서 결정했습니다.

결과 저장 뒤, 검사는 SQS로

DLP 검사 경계: 응답 경로와 감사 경로 분리원본 보기

DLP Interceptor는 Tool Result에서 검사에 필요한 식별자와 최소 메타데이터만 뽑아 SQS로 보냈습니다. 사용자 요청은 메시지가 안전하게 인계되는 시점까지만 기다렸고, 이후 DLP API 호출과 감사 이력 저장은 Consumer가 이어받았습니다.

여기서 DLP 판정 실패와 SQS 발행 실패를 같은 오류로 묶지 않았습니다. 판정 실패는 상태를 남겨 다시 처리할 수 있지만, 발행이 실패하면 검사할 대상 자체가 사라질 수 있기 때문입니다. 따라서 SQS 인계를 사용자 응답 경로의 마지막 확인 지점으로 두고, 판정 결과는 감사 경로에서 관리했습니다.

외부 API의 지연은 응답에서 분리됐지만 책임까지 사라진 것은 아니었습니다. 이제는 중복 소비와 재처리, 실패 상태, DLQ를 별도로 다뤄야 했습니다.

비동기 경로에서 새로 생긴 실패

비동기 경로는 SQS로 구성했습니다. 다만 메시지를 보냈다는 사실만으로 검사가 정확히 한 번 수행되거나 감사 이력이 자동으로 보존되지는 않습니다. Result 저장과 메시지 발행은 서로 다른 시스템에서 일어나고, SQS도 같은 메시지를 두 번 이상 전달할 수 있으므로 실패 조건을 나눠 봐야 했습니다.

3열 표
확인 항목문제가 되는 상황확인 기준
메시지 발행결과는 저장됐지만 SQS 발행이 실패하면 검사 대상이 누락될 수 있음발행 성공 여부와 실패 사유를 별도로 남기고, 발행 실패를 정상 처리와 구분
중복 전달SQS의 at-least-once 전달로 같은 이벤트가 다시 소비될 수 있음요청 식별자나 이벤트 식별자를 기준으로 탐지 이력 저장이 중복에 안전한지 확인
visibility timeoutConsumer 처리 시간보다 짧으면 처리 중인 메시지가 다시 노출될 수 있음검사와 이력 저장에 걸리는 시간을 기준으로 설정값을 확인하고, 긴 작업은 재노출 가능성을 따로 점검
retry와 DLQ외부 DLP 장애가 길어지면 같은 메시지가 반복되며 큐 지연과 호출량이 함께 증가즉시 retry 횟수와 maxReceiveCount를 제한하고, 반복 실패 메시지는 별도 보관 큐인 DLQ에서 확인
권한배포 환경에서 SQS 송신, 수신, 삭제 권한이 빠지면 특정 단계만 실패할 수 있음메시지를 보내는 Producer와 읽는 Consumer의 IAM 권한을 배포 전에 각각 확인
DB migration실패 상태와 retry 정보를 저장할 스키마가 늦게 배포되면 Consumer가 메시지를 받아도 기록하지 못함상태 저장용 migration이 Consumer 배포보다 먼저 적용됐는지 확인

이 구조를 exactly-once 처리라고 설명하지는 않았습니다. 중복 전달을 전제로 감사 이력을 멱등하게 저장하고, 발행과 소비 실패를 다시 찾을 수 있게 만드는 것이 실제 목표였습니다.

Tool Result 원문은 SQS와 로그에 그대로 복제하지 않았습니다. 대신 재처리에 필요한 정보만 이벤트에 담았습니다.

  • 장애 범위를 찾기 위한 요청 식별자와 이벤트 식별자
  • Result 종류를 구분하는 최소 메타데이터와 마스킹된 값
  • 처리 상태, 재처리 가능 여부, 시도 횟수와 마지막 오류

이벤트 식별자와 최종 상태를 기준으로 이미 완료된 건은 건너뛰고, 일시적인 오류만 retry 대상으로 남겼습니다. 반복 실패는 자동 재처리와 운영자 확인 대상으로 나눴습니다.

DLP 실패 상태 경계원본 보기

이 상태를 기준으로 장애 시간대의 영향 범위를 찾고, 필요한 이벤트만 다시 큐에 넣을 수 있었습니다.

retry를 멈추는 기준과 큐 격리

일시적인 네트워크 오류에는 retry가 도움이 되지만, 외부 DLP 장애가 길어지면 같은 이벤트가 호출량과 큐 대기를 함께 키웁니다. 부하 테스트에서 관찰한 p99 약 336ms는 timeout 범위를 검토하기 위한 참고값으로만 사용했습니다. 즉시 retry는 한 번으로 제한하고, 반복 실패는 DLQ와 실패 상태에서 다뤘습니다.

MCP Tool Result는 한 요청에서 여러 건이 연달아 생길 수 있어 기존 채팅 DLP와 부하 형태가 달랐습니다. 별도 큐로 나눈 덕분에 MCP 검사가 밀려도 채팅 검사가 같이 늦어지는 상황을 피할 수 있었습니다.

운영에서는 큐 길이 하나만 보지 않았습니다. oldest message age와 Consumer 처리 시간, 외부 API의 p95와 p99, retry 횟수와 실패 상태 비율을 함께 봐야 내부 적체와 외부 장애를 나눌 수 있었습니다.

비동기화로 요청 경로에서 빠진 것은 외부 판정을 기다리는 시간뿐이었습니다. Result 저장 뒤 발행에 실패한 건을 찾는 상태, 중복 전달에도 한 번만 남는 감사 이력, 최소 메타데이터의 보존과 삭제 기준, 반복 호출 비용은 운영 책임으로 남았습니다.

응답과 감사가 실제로 분리됐는지

구조를 나눈 뒤에는 응답 속도 하나보다 각 경로의 책임이 실제로 지켜지는지를 살폈습니다.

검증은 한 요청의 시작부터 감사 이력이 저장되는 시점까지 이어졌습니다.

  1. 요청 추적에서 외부 DLP API 호출 시간 제거
  2. Tool Result 식별자, 발행 이벤트 수와 처리 상태 대조
  3. 일시 오류의 한 차례 retry 후 실패 상태 전환
  4. 완료 이벤트 재전달 시 외부 호출과 감사 저장의 중복 방지
  5. MCP 검사 지연과 기존 채팅 DLP 큐 대기 시간의 분리

다섯 흐름을 같은 요청 식별자로 연결해, 사용자 응답의 성공이 감사 처리의 성공처럼 보이지 않게 했습니다.

분리한 뒤에도 남는 간극

이번 구조는 사후 감사와 이상 징후 탐지를 목적으로 한 MCP Tool Result에 맞췄습니다. 응답 전에 반드시 차단해야 하는 정책이라면 같은 방식을 적용할 수 없습니다.

현재 구조에서 계속 관리할 간극

  • Result 저장 뒤 SQS 발행 실패를 구분하는 상태
  • 감사 DB 저장 완료 이후의 메시지 삭제
  • 실제 처리 시간에 맞춘 visibility timeout과 멱등 처리
  • 장기 장애와 rate limit을 DLQ와 알림으로 전환하는 기준

정책이나 규모가 바뀌면 달라질 선택

  • 감사 누락 허용 범위가 더 엄격해질 때 outbox 또는 reconciliation 도입
  • 이벤트 형식 변경에 대비한 version과 하위 호환성
  • 대용량 또는 민감 Result의 식별자 기반 전달
  • 순서와 재검사 이력이 중요해질 때 FIFO, sequence, 정책 version 관리

비동기 처리는 검사를 덜 중요하게 만든 선택이 아니었습니다. 응답에서 외부 판정을 걷어낸 만큼, 어떤 Result가 검사되지 않았고 어디에서 멈췄는지를 더 정확히 남겨야 했습니다.

운영에서는 응답이 느릴 때 SQS 발행 시간부터 확인하고, 감사가 밀렸다면 가장 오래 기다린 메시지와 Consumer 처리 시간, 외부 DLP API 지연을 차례로 봅니다. 응답과 검사는 분리할 수 있었지만, 두 흐름의 추적 관계까지 끊을 수는 없었습니다.