위치 좌표를 서버로 보내지 않는 telemetry

로그인 없는 앱에서 작동 상태만 보고, 수집하지 않을 데이터를 먼저 정했다

한강자리는 로그인 없는 앱이다. 그래도 앱이 제대로 돌아가는지 보려면 최소한의 telemetry가 필요하다. 여기서 telemetry는 사용자를 추적하기 위한 기록이 아니라, 위젯이 쓰이는지, 알림 설정이 저장되는지, API가 느린지처럼 앱의 작동 상태를 보는 신호다.

문제는 무엇을 수집할지가 아니라, 무엇을 수집하지 않을지를 먼저 정해야 한다는 점이었다.

telemetry는 붙이기 시작하면 금방 커진다. 버튼을 몇 번 눌렀는지, 어디에서 이탈했는지, 어떤 공원을 자주 보는지 알고 싶어질 수 있다.

하지만 한강자리는 로그인 없는 공공정보 앱이고, 위치 맥락이 자연스럽게 떠오르는 앱이다. 그래서 “볼 수 있는 것”보다 “보지 않기로 한 것”을 먼저 정했다.

위치는 단말 안에서만

한강자리에서 위치가 필요한 순간은 가까운 공원이나 주차장을 정렬할 때다. 이때도 현재 위치 좌표를 서버로 보내지 않게 했다.

sequenceDiagram
  autonumber
  participant User as 사용자
  participant App as iOS 앱
  participant Location as 위치 서비스
  participant Server as 서버 API

  User->>App: 가까운 곳 보기
  App->>Location: 현재 위치 요청
  Location-->>App: 좌표 또는 nil
  App->>App: 공원/주차장 정렬
  App-xServer: 좌표 업로드 없음

위치 권한이 없으면 nil을 받고, 가까운 정렬만 제한한다. 서버 API는 공원과 주차장 데이터를 내려주지만, 사용자의 현재 좌표는 저장하지 않는다.

이 선택은 편의를 조금 줄이지만 설명하기는 훨씬 쉽다.

가까운 공원 정렬을 서버에서 하면 분석하고 바꾸기는 쉬워질 수 있다. 하지만 그 대가로 위치 좌표가 서버를 지나가게 된다. 한강자리에서는 그 정도의 편의보다 “위치는 단말 안에서만 쓴다”고 말할 수 있는 쪽을 택했다.

익명 이벤트

이벤트는 계정이 아니라 익명 설치 해시를 기준으로 한다. 앱은 seed를 기기에 저장하고, scope를 붙여 hash를 만든다. 서버에는 이름, 이메일, 광고 식별자, 위치 좌표가 아니라 앱 상태를 보기에 필요한 최소 필드만 보낸다.

대표 필드는 다음과 같다.

필드목적
event type어떤 일이 일어났는가
surface/entrypoint앱, 위젯, 알림 중 어디에서 왔는가
park ID / lot ID어떤 공원 또는 주차장 맥락인가
정보 최신성 구간사용자가 본 정보가 최신에 가까운가, 오래됐는가
duration/status/reason느린 지점과 실패 이유
anonymous install hash중복 없는 설치 단위 구분

이 정도면 앱이 제대로 작동하는지 묻는 질문에는 답할 수 있다. 개인을 식별하거나 위치 기록을 만들 필요는 없다.

여기서도 park ID나 lot ID는 조심해서 본다. 앱을 고치는 데는 필요하지만, 개인의 동선을 재구성하는 방향으로 쓰면 안 된다. 이벤트는 앱 상태를 보는 신호이지 사용자 프로파일을 만드는 재료가 아니다.

전송은 앱 사용을 막지 않는다

telemetry 전송은 앱 사용을 막으면 안 된다. 그래서 이벤트 reporter는 파일 queue에 이벤트를 쌓고, 한 번에 보낼 수 있는 만큼만 전송한다. 전송 실패 시에는 조금 기다렸다가 다음 기회에 다시 보낸다.

flowchart LR
  Event["앱 이벤트"] --> Queue["단말 파일 큐"]
  Queue --> Batch["제한된 묶음"]
  Batch --> API["텔레메트리 엔드포인트"]
  API -->|수락| Metrics["Prometheus/DB 메트릭"]
  API -.->|실패| Retry["백오프 재시도"]
  Retry --> Queue

한 번에 보내는 수와 크기를 제한하는 이유는 단순하다. telemetry가 앱을 느리게 만들면 본말이 전도된다.

성능 신호와 행동 신호를 나눴다

앱 이벤트와 성능 이벤트는 구분한다. 성능 telemetry는 API path, 응답 코드, 걸린 시간, cache 사용 여부 같은 기술 신호를 본다. 사용 패턴을 보려는 일과 API 안정성을 확인하는 일은 목적이 다르다.

서버에서는 이벤트 수집 수, 거절 이유, 수집에 걸린 시간, push ack 같은 metric을 남긴다. 이 숫자는 대시보드에서 앱 상태를 보기 위한 신호이지 사용자 프로파일링 도구가 아니다.

이 구분을 해두면 대시보드도 덜 위험해진다. 느린 API를 찾는 일과 사용자의 이동 패턴을 추적하는 일은 전혀 다르다. 한강자리에서는 앞의 질문에 필요한 최소 신호만 남기는 쪽을 택했다.

푸시 기기 식별자와 알림 설정

알림은 예외적으로 서버 저장이 필요하다. APNs가 기기를 구분하는 값, 관심 공원·주차장, 알림 기준, 조용한 시간 같은 값이 있어야 서버가 사용자가 요청한 알림을 보낼 수 있다.

그래서 push 영역은 더 엄격하게 다룬다.

  • 알림을 켠 경우에만 필요한 값을 저장한다.
  • APNs가 기기를 구분하는 값은 전송 목적에 한정한다.
  • 무효화된 기기 식별자와 오래된 subscription은 정리한다.
  • 알림 규칙과 delivery audit은 사용자 보호를 위해 reason code를 남긴다.

푸시는 서버가 사용자를 대신해 움직이는 일이다. 그래서 “보낼 수 있다”보다 “왜 보냈고 왜 보내지 않았는가”를 설명할 수 있어야 한다. 이렇게 설명할 수 있어야 개인정보도 덜 가져가게 된다.

정책 문서는 앱이 하는 일과 같아야 한다

iOS Privacy Manifest와 개인정보 처리방침은 나중에 붙이는 설명서가 아니다. 앱이 실제로 무엇을 수집하고, 무엇을 수집하지 않는지 App Store와 사용자에게 설명하는 약속이다.

한강자리에서는 이렇게 정했다.

  • 계정과 로그인 없음.
  • 광고 식별자 기반 추적 없음.
  • 위치 좌표 서버 전송 없음.
  • 앱 이벤트는 익명 해시와 최소한의 작동 신호 중심.
  • 푸시 기기 식별자는 알림을 보내기 위한 목적.
  • 공개 데이터 원문에 민감 문자열이 섞일 수 있으면 저장/표시 전에 제한한다.

덜 보는 선택이 설명을 쉬워지게 했다

telemetry는 “무엇을 볼 것인가”보다 “무엇을 보지 않을 것인가”를 먼저 정해야 했다. 가까운 곳 보기는 서버 위치 저장 없이도 만들 수 있고, 익명 설치 해시는 계정이 아니도록 목적과 보관 기간을 제한해야 했다.

앱 이벤트와 성능 이벤트를 분리하니 대시보드 해석도 쉬워졌다. telemetry queue는 앱 사용을 막지 않도록 한 번에 보내는 양과 재시도를 제한했고, Privacy Manifest와 처리방침은 앱이 실제로 하는 일과 같은 말을 하게 맞췄다.

한강자리에서 privacy는 마지막에 붙인 문구가 아니었다. 어떤 데이터를 보지 않을지 먼저 정해두니, 분석도 그 안에서 필요한 만큼만 하게 됐다. 덜 보는 선택이 앱을 약하게 만든 것이 아니라, 사용자에게 설명할 수 있는 앱을 만드는 기준이 됐다.

이미지 확대