기술

위치정보법 대응을 해보니, 핵심 작업은 좌표 컬럼 삭제였습니다

2026.09.048분 읽기

사용자가 앱에서 가까운 지점을 찾을 때 단말의 위치를 받아 정렬에 씁니다. 위치를 쓰는 서비스라 위치기반서비스 이용약관 정비와 위치정보법 대응이 숙제로 남아 있었습니다.

이번에 그 숙제를 처리했습니다. 약관 2건을 서비스 포맷으로 변환하고, 위치 사용 로그를 법이 요구하는 '이용사실 확인자료' 요건에 맞추는 작업이었습니다. 작업을 닫고 변경 내역을 훑어보니, 가장 핵심적인 변경은 새로 만든 기능이 아니라 컬럼 두 개를 지운 것이었습니다.


요건을 다시 읽으니 기록할 것보다 지울 것이 먼저 보였습니다

컴플라이언스 대응은 보통 "무엇을 더 기록하는가"로 시작합니다. 저도 그렇게 접근했습니다. 확인자료 요건을 항목별로 펼쳐놓고 우리 로그에 빠진 필드를 찾는 방식이었습니다.

그런데 요건을 문장 그대로 읽으면 남겨야 할 것은 세 가지였습니다. 누가(회원), 언제(시각), 무슨 목적으로(이용 목적) 위치를 썼는가. 원본 좌표는 그 목록에 없었습니다. 위경도는 요건이 아니라 우리가 습관적으로 같이 적재해 온 데이터였습니다.

컴플라이언스의 절반은 데이터 다이어트입니다. 법이 요구하는 것은 사실(누가·언제·왜)이지 원본 좌표가 아닙니다.

좌표를 지우면 무엇이 깨지는지부터 확인했습니다. 좌표는 "이 요청이 실제로 위치를 사용했는가"를 판단하는 데만 쓰이고, 판단이 끝난 뒤에는 어느 화면에서도 다시 읽히지 않았습니다. 요청에서 받아 판단에 쓰고 버려도 서비스는 그대로 돌아가는 구조였습니다.

128-1.jpg


저장하고 통제할지, 받아서 버릴지

선택지는 두 개였습니다. 좌표를 계속 저장하고 접근 통제로 보호하는 관성적인 방향, 그리고 좌표는 판단에만 쓰고 저장하지 않는 방향입니다. 두 방향을 운영 부담 기준으로 나란히 놓아 봤습니다.

기준

좌표를 계속 저장

받아서 판단만 하고 버림

유출 시 영향

개인 위치 이력이 그대로 노출

노출될 원본이 없음

감사 대응

암호화·권한·접근 로그를 증명

저장 안 함을 스키마로 증명

파기 의무

보관주기 배치와 검증 필요

대상 자체가 없음

약관 정합성

'저장 안 함' 선언과 모순

선언과 스키마가 일치

저장하는 쪽을 고르면 암호화, 권한 분리, 접근 로그, 파기 배치가 전부 따라옵니다. 게다가 이 데이터는 저장해 두고 쓰는 곳이 없었습니다. 그래서 두 번째 방향으로 정하고, 개발과 운영 양쪽 환경에 DROP COLUMN을 적용하고 엔티티 매핑과 빌더에서도 좌표를 걷어냈습니다.


약관에 저장하지 않는다고 쓰는 순간 스키마가 증거가 됩니다

이 작업에서 가장 신경이 쓰인 부분은 코드가 아니라 문서였습니다. 위치기반서비스 이용약관에는 위치정보를 별도로 저장하지 않는다는 조항이 들어갑니다. 컬럼이 남은 상태로 그 문장을 발행하면, 그 문서 자체가 리스크로 바뀝니다.

감사나 점검에서 가장 먼저 대조되는 것이 선언과 실제 저장 상태입니다. 컬럼 하나가 남아 있으면 "저장하지 않습니다"는 문장은 방어 근거가 아니라 공격 지점이 됩니다. 그래서 약관 변환과 DDL을 별개 티켓으로 잡지 않고 한 묶음으로 묶어 처리했습니다. 문서를 쓰는 사람과 스키마를 만지는 사람이 나뉘면 이 둘은 아주 쉽게 어긋납니다.

선언한 정책과 구현이 한 지점에서 만나야 한다는 문제는 도메인 규칙에서도 같은 모양으로 나타납니다. 예약 마감과 취소 마감을 각자 계산하다 두 시각이 어긋난 적이 있어, 정책을 하나의 불변식으로 정의하고 두 마감을 같은 선에 묶은 일이 있습니다. 약관과 스키마의 관계도 결국 같은 종류의 정렬 작업이었습니다.


취득경로는 우선순위 사슬로 채웠습니다

확인자료의 다른 축은 취득경로였습니다. 위치를 어떤 경로로 받았는지, 실무적으로는 단말 OS를 남기는 일입니다. 앞의 절반이 지우는 작업이었다면 이쪽은 반대로 더 기록하는 작업이었습니다.

값을 정하는 방식은 단일 소스로 잡지 않고 우선순위 사슬로 만들었습니다. 요청 헤더가 단말 OS를 실어 보내면 그 값을 쓰고, 헤더가 없으면 서버가 알고 있는 회원의 활성 디바이스 정보로 보완합니다. 지점 조회 계열 API 네 곳이 헤더를 수신하도록 확장했습니다.

요청 헤더가 실어 보낸 단말 OS 값을 우선 사용

헤더가 없으면 회원의 활성 디바이스 OS로 보완

그것도 없으면 NULL로 남기고, 이 NULL이 어떤 상태를 뜻하는지 문서로 정의

사슬로 만든 이유는 클라이언트 배포를 기다리지 않아도 값이 채워지기 때문입니다. 앱 심사와 출시 주기 때문에 헤더 부착은 구현이 끝나고도 한참 뒤에 반영됩니다. 그 과도기에는 서버가 아는 상태로 메꾸고, 앱이 나가면 신규 로그는 헤더로 사실상 전부 채워집니다.

128-2.jpg


NULL의 뜻을 정의한 시점이 백필의 끝이었습니다

기존 행은 회원의 활성 디바이스 OS로 UPDATE 백필했습니다. 백필을 돌리면 언제나 채워지지 않는 행이 남습니다. 로그아웃했거나, 계정을 바꿨거나, 디바이스를 등록한 적이 없어 참조할 디바이스 행 자체가 없는 경우입니다. 설계상 로그아웃 시 디바이스 행이 삭제되므로 이건 결손이 아니라 정상 케이스입니다.

예전에는 이런 잔여를 "백필이 덜 됐다"는 애매한 상태로 두고 다음 작업으로 넘어갔습니다. 이번에는 남은 NULL이 어떤 상태를 뜻하는지 문서에 적은 뒤에 작업을 닫았습니다. 점검 자리에서 "이 행은 왜 비어 있습니까"라는 질문이 오면, 답이 준비된 상태와 그때부터 조사하는 상태의 차이가 큽니다.

백필의 완료 조건을 "NULL이 0건"으로 잡으면 영원히 끝나지 않는 작업이 됩니다. 사슬 끝에 남는 NULL의 의미를 정의하는 지점을 완료선으로 잡는 편이 실제 운영과 맞았습니다.


ADD는 DDL 먼저, DROP은 코드 먼저

배포 순서에서 한 번은 헷갈리는 지점입니다. 컬럼 추가와 컬럼 삭제는 나가는 순서가 서로 뒤집힙니다.

변경

먼저 나가는 것

순서를 어기면

ADD COLUMN

DDL

새 컬럼을 읽고 쓰는 코드가 먼저 떠서 없는 컬럼을 참조하고 즉시 실패

DROP COLUMN

코드

스키마가 먼저 사라져서 아직 컬럼을 매핑한 코드가 깨짐

이번 작업에는 두 방향이 한꺼번에 있었습니다. 취득경로 컬럼 추가와 좌표 컬럼 삭제가 같은 릴리스에 들어갔습니다. 그래서 배포를 DDL(ADD) → 코드 → DDL(DROP) 세 스텝으로 쪼갰고, 개발 환경에서 같은 순서로 리허설한 뒤 운영에 올렸습니다. 스텝을 쪼개지 않고 한 번에 나가면 어느 쪽이든 한 방향은 반드시 깨집니다.

128-3.jpg


비슷한 대응을 앞두고 점검할 항목

요건 문서에서 '사실'과 '원본'을 갈라 적었는가 — 원본이 요건이 아니라면 저장 목록에서 빼는 후보다

약관·동의서의 '저장하지 않는다' 문장과 실제 테이블 컬럼을 한 자리에서 대조했는가

파생 값의 결정 로직이 우선순위 사슬로 정리돼 있고, 사슬 끝의 NULL 의미가 문서에 있는가

컬럼 추가와 삭제가 같은 릴리스에 섞여 있는지, 방향별로 배포 스텝을 쪼개 뒀는가

보관주기가 법·약관·구현 세 곳에서 같은 숫자인가 — 다르면 인지한 상태로 결정에 남겨 뒀는가

마지막 항목은 이번에 정렬하지 못한 채로 남겨 뒀습니다. 구현은 7개월, 약관은 6개월로 적혀 있는데 이번에는 구현 쪽 숫자를 유지하기로 결정하고 불일치를 기록해 뒀습니다. 법과 약관과 구현의 3자 정렬은 한 번에 끝나지 않습니다. 다만 어긋난 것을 모르는 상태와, 알고 남겨 둔 상태는 다릅니다.

좌표 자체가 제품의 가치인 도메인이라면 미저장 전환은 선택지가 아닙니다. 이동 경로 분석이나 지도 기록이 서비스의 본체라면 암호화와 보관·파기의 정공법으로 가는 편이 맞습니다. 다만 그 확인을 건너뛰고 통제 장치부터 붙이면, 지울 수 있었던 데이터를 평생 지키게 됩니다. 대응 비용을 가장 크게 줄인 판단은 요건을 다시 읽고 저장 목록에서 한 줄을 뺀 것이었습니다.

#운영의기술#위치정보법#컴플라이언스#데이터최소화#DROPCOLUMN#백필#배포순서