목차
- 프로젝트가 시작된 시점
- 무엇을 어디까지 줄일지 결정한 과정
- RAG 파이프라인을 어떻게 다시 짰나
- MCP로 영장 응답 자동화를 시도한 부분
- 두 달 운영하면서 깨진 것들
- 다음 분기 계획과 남은 의문들
미국 대법원의 geofence warrant 관련 판결 흐름이 굳어진 뒤, 위치 데이터를 쓰는 앱 백엔드를 다시 손볼 일이 생겼다. 3개월 작업이 끝난 지금 서버에 저장하던 사용자 좌표는 80% 가까이 줄었고 영장 응답 매뉴얼은 두 페이지에서 일곱 페이지로 늘었다.
프로젝트가 시작된 시점
당시 백엔드 상태는 칭찬할 게 별로 없었다. 위치 권한을 받은 사용자의 좌표를 1분 단위로 수집해 저장했고, 보존 기간은 18개월이었다. 추천 모델이 과거 동선을 학습 데이터로 써야 한다는 이유였다. PostGIS 위에 GIST 인덱스를 깐 흔한 구성이다. 잘 돌아갔다. 잘 돌아갔기 때문에 손대고 싶지 않았다.
법무 검토에서 받은 요지는 두 줄이었다. geofence warrant가 일반영장(general warrant)에 해당해 위헌이라는 흐름이 미국 항소법원 단위에서 일관되게 강해지고 있고, 대법원의 최근 판단 역시 그 방향을 뒷받침한다는 것. 우리가 미국 사용자 좌표를 수집한 형태로 18개월씩 들고 있는 게 위험하다는 결론이었다. Carpenter v. United States(2018)의 셀사이트 위치정보 판단과 같은 결을 위치 일반에 확장하는 흐름으로 보였다.
따라서, 처음에는 보존 기간만 줄이면 끝나는 줄 알았다. 그게 아니었다. 영장 흐름의 핵심은 "기간을 정해놓고 그 시간에 그 지역에 있던 사람 전부를 토해내라"는 구조 자체였다. 데이터를 짧게 들고 있는 것보다, 좌표를 그대로 들고 있는 것 자체가 위험이라는 게 변호사 의견이었다.
무엇을 어디까지 줄일지 결정한 과정
특히, 가장 먼저 한 일은 데이터 흐름을 그림으로 그리고 변호사에게 보여주는 작업이었다. 변호사는 두 가지를 물었다. 좌표를 그대로 저장해야만 추천 품질이 유지되는가. 사용자가 어디 있었는지 역추적 가능한 형태로 저장하는 것과, 추상화된 형태로 저장하는 것의 비즈니스 차이는 무엇인가.
그러나, 답을 만들기 위해 추천 모델 팀에 협조를 구했다. 결과는 의외였다. 좌표 정확도를 H3 resolution 9(약 174m 직경) 수준으로 떨어뜨려도 추천 CTR이 통계적으로 유의하게 떨어지지 않았다. 모델 자체가 위경도 소수점 6자리까지 쓰지 않고 있었다. 학습 데이터 분포를 보니 대부분의 활성 사용자가 셀 단위 이동 패턴 안에서 움직였다.
여기서 첫 결정이 나왔다. 원본 좌표는 클라이언트에서 즉시 H3 셀로 변환해 서버에 보낸다. 서버는 셀 ID만 저장한다. 보존 기간은 18개월에서 90일로 줄였다. 추천 학습용 장기 데이터는 일별 셀 빈도 집계로 갈음했다. 말로 쓰면 한 문단이지만 실제 적용에는 6주가 들었다. 클라이언트 SDK 버전 분기, 구버전 사용자 마이그레이션, 기존 좌표 테이블 비식별화. 이런 게 다 시간을 잡아먹었다.
왜 H3였나
실제로, Uber H3는 정육각형 기반 글로벌 그리드다. resolution별 셀 크기가 표로 정해져 있어 좌표 정밀도를 의도적으로 떨어뜨리는 데 적합하다. 사각 격자보다 거리 계산이 균일하고 셀 ID가 64bit 정수라 인덱싱이 가볍다. 같은 목적으로 geohash도 검토했다. 문자열이라는 점, 격자가 직사각형이라 위도에 따라 셀 크기가 달라진다는 점이 걸렸다. resolution 변경 시 부모-자식 관계가 비교적 깔끔한 H3 쪽이 데이터 마이그레이션에 편했다.
비교 항목은 더 있었지만 의사결정에 결정적이었던 셋만 남기면 이렇다.
| 항목 | H3 res 9 | Geohash 7 | 원본 좌표 |
|---|---|---|---|
| 셀/격자 크기 | 약 174m | 약 153m × 153m | 점 |
| 저장 크기 | 8 byte | 7~9 byte | 16 byte |
| 거리 계산 균일성 | 균일 | 위도별 편차 | – |
보존 기간 결정의 근거
또한, 90일이라는 숫자는 임의로 정한 게 아니다. 추천 모델 재학습 주기가 28일이었고, A/B 실험 윈도가 최대 8주였다. 두 주기를 다 포함하면서, 영장 흐름이 통상적으로 요구하는 회고 기간 밖에 있는 숫자가 90일이었다. 변호사가 "더 짧을수록 좋지만 비즈니스가 무너지지 않는 선에서 합의해야 한다"고 한 말이 기준이 됐다.
RAG 파이프라인을 어떻게 다시 짰나
게다가, 여기서부터가 AI 쪽 이야기다. 우리 앱은 사용자가 "근처 조용한 카페" 같은 자연어 질의를 던지면 주변 장소 메타데이터를 RAG로 가져와 Claude에 넘기는 구조였다. 기존에는 사용자 좌표를 그대로 검색 함수에 넣었다. PostGIS의 ST_DWithin으로 반경 500m 내 장소를 골라 임베딩 유사도와 가중합했다. 좌표를 안 쓰기로 결정한 순간 이 부분이 깨졌다.
예를 들어, 해결은 단순하게 갔다. 검색 시점에는 사용자가 속한 H3 셀과 인접 셀(k-ring)을 쿼리 조건으로 쓴다. 반경 검색이 셀 집합 검색으로 바뀐 셈이다.
# H3 셀 기반 주변 장소 조회
def nearby_places(user_cell: str, k: int = 1) -> list[dict]:
# k-ring: 자기 셀 + 인접 k단계 셀까지 포함
cells = list(h3.grid_disk(user_cell, k))
return db.fetch_all(
"SELECT id, name, embedding FROM places WHERE h3_cell = ANY(%s)",
(cells,),
)
진짜 어려운 건 코드 바깥에 있었다. Claude에게 위치 맥락을 어떻게 줄지가 문제였다. 좌표를 그대로 프롬프트에 넣지 않기로 한 정책을 RAG 컨텍스트에도 일관되게 적용해야 했다. 프롬프트에 "사용자 위치: 37.5xxx, 127.0xxx" 같은 줄이 들어가면 로그에 남고, 그 로그가 다시 영장 대상이 될 수 있다는 게 변호사의 우려였다.
그래서 프롬프트에 들어가는 사용자 위치는 "근처 셀: 8a2a1072b59ffff 외 6개"처럼 H3 셀 ID로만 표기하기로 했다. Claude 응답 품질에 영향이 있을지 걱정했지만 거의 없었다. 모델이 셀 ID를 해석하는 게 아니라, 검색된 장소 목록과 사용자 질의만 보고 추천하기 때문이다. 위치는 검색 단계에서 이미 다 처리된 뒤다.
로그 스크러빙이 더 큰 일이었다
코드 한 줄 바꿔서 프롬프트만 정리해도 의미가 없었다. APM 로그, 트레이싱 스팬 속성, 에러 리포트, 분석용 이벤트 스트림 어디에든 좌표가 흘러갈 가능성이 있으면 영장의 사정거리에 들어간다. 좌표 형태의 부동소수점 두 개가 인접한 패턴을 잡는 정규식 기반 마스킹 미들웨어를 모든 인입 지점에 깔았다. 이 작업이 6주짜리 마이그레이션의 절반을 먹었다.
MCP로 영장 응답 자동화를 시도한 부분
즉, 이건 절반의 성공, 절반의 실패다. 영장이 들어오면 법무팀이 데이터 추출, 식별자 매핑, 보존 범위 확인을 수작업으로 했다. 시간이 꽤 들었다. MCP 서버로 이 흐름을 묶어 Claude Desktop에서 "이 영장 번호로 데이터 추출 요청서 초안 만들어줘"라고 시키면 끝나게 하려 했다.
특히, MCP 서버 자체는 어렵지 않게 만들었다. 영장 메타데이터를 받는 도구, 보존 범위 안에 데이터가 있는지 확인하는 도구, 식별자 매핑 표를 반환하는 도구. 세 개로 구성했다.
게다가, 문제는 다른 데서 터졌다. 영장 응답은 법적으로 사람이 최종 승인해야 한다. MCP를 통해 Claude가 데이터 범위를 잘못 해석하면 책임 소재가 모호해진다. 보안 감사 단계에서 "AI가 영장 데이터 결정 경로에 직접 끼는 건 아직 이르다"는 의견이 나왔다. 결국 MCP는 영장 응답서 초안 작성 보조 도구로만 남았다. 데이터 추출 자체는 사람이 SQL을 친다.
따라서, 기대치를 낮춰서 보면 그래도 가치는 있었다. 초안 작성에 들던 시간이 줄었고, 영장 응답 포맷이 표준화됐다. 그것만으로도 운영 부담은 작아졌다. 시행착오로 배운 게 있다면, 컴플라이언스 영역에서 LLM은 결정자가 아니라 양식 작성기 역할에 머무는 게 맞다는 점이다.
두 달 운영하면서 깨진 것들
실제로, 배포 직후 한 주는 조용했다. 두 번째 주에 첫 사고가 났다. iOS 일부 단말의 H3 변환 결과가 셀 경계에서 튀는 현상이 보고됐다. CoreLocation의 좌표 정확도가 낮아질 때 H3 변환 결과가 인접 셀로 흩어졌다. 추천 결과가 한 번 호출할 때마다 바뀌어서 사용자가 불안하다는 피드백이 왔다.
문제 자체는 단순했다. 클라이언트에서 좌표 정확도(horizontalAccuracy)가 임계치 이상일 때만 H3 변환을 시도하도록 가드를 넣었다. 그래도 일주일 동안 추천 일관성 지표가 떨어졌다. 셀 경계 근처 사용자에 대해서는 직전 셀을 짧게 캐시하는 패치를 추가로 넣고서야 안정됐다.
게다가, 두 번째 사고는 더 미묘했다. 어떤 사용자가 데이터 다운로드 요청을 했을 때, 시스템이 H3 셀 ID 목록을 그대로 내려줬다. 셀 ID를 다시 좌표로 역변환하면 셀 중심점이 나온다. 사용자 입장에서는 "내가 어디 있었는지 데이터"가 사실상 그대로 보이는 셈이다. 익명화의 출력 끝단까지 신경 써야 한다는 걸 다시 배운 케이스다. 다운로드 시에는 셀 ID 대신 가장 자주 방문한 행정구역(시/구 단위) 요약만 제공하는 쪽으로 바꿨다.
세 번째는 사고라기보다는 발견에 가깝다. 데이터 분석가들이 셀 단위 집계 테이블을 보고 "이 정도면 분석 인사이트가 떨어지지 않는다"는 의견을 내놨다. 좌표 원본을 안 줘도 동선 클러스터링이 충분히 가능했다는 얘기다. 이건 다음 분기 의사결정에 영향을 줬다.
다음 분기 계획과 남은 의문들
게다가, 세 가지를 잡고 있다. 클라이언트 사이드 추천 모델을 좀 더 끌어올린다. 지금은 RAG 검색만 클라이언트 셀 기반이고 모델 자체는 서버에 있다. 모바일에서 임베딩 매칭까지 처리할 수 있으면 서버가 셀 ID조차 받지 않아도 된다. 두 번째는 MCP 도구 권한 분리다. 영장 관련 도구들이 단일 MCP 서버에 묶여 있어서, 도구별 권한과 감사 로그를 분리하는 작업이 필요하다. 세 번째는 미국 외 지역 정책 매핑이다. 한국, EU, 일본은 영장 체계가 다르다. 미국 기준에 맞춰 만든 파이프라인이 다른 지역 규제에 그대로 맞는지 다시 봐야 한다.
따라서, 비슷한 상황에 놓인 팀이 당장 할 만한 행동은 셋이다. 위치 데이터 보존 기간을 한 단계 더 짧게 끊는다. 좌표 대신 H3나 geohash 같은 셀 ID로 추상화 레이어를 둔다. 영장 응답 워크플로를 사람이 최종 결정하는 형태로 표준화한다. 이 셋만 갖춰도 다음 판례가 어느 방향으로 가든 충격이 크지 않다.
예를 들어, 다만 미국 대법원과 각 순회법원의 해석이 아직 완전히 정렬된 상태로 보기는 어렵고, 이번 회고의 결론들이 향후 1~2년 안에 다시 흔들릴 가능성은 열어두고 봐야 할 것 같다.
관련 글
- Listen Labs $69M 펀딩: 빌보드 채용 광고가 바이럴 → 시리즈 B 직행 – Listen Labs 빌보드 광고가 바이럴된 직후 $69M 펀딩이 떴다. AI 인터뷰 도구의 내부 파이프라인을 흉내내다 막힌 지점을 정리한…
- Pool로 스크린샷 자동 인덱싱 — Raycast, Spotlight 비교 – 스크린샷 폴더가 자료가 아니라 쓰레기통이 되는 이유부터 짚는다. Pool, Raycast, Spotlight 세 도구로 같은 폴더를 다뤄보…
- FAANG 끝났다 – 이제는 MANGOS 시대, AI API 3개월 통합기 – 3개월 동안 사내 RAG 프로젝트에서 MANGOS 시대의 AI API들을 펼쳐놓고 비교했다. Claude를 메인으로, Gemini Flas…