> ## Content Index
> Fetch the complete content index at: https://blog.sorune.org/llms.txt
> Use this file to discover other available public pages before exploring further.

# EXIF GPS를 쓰되 저장하지 않는다 — PhotoGram Place에서 위치 추천과 공개 위치를 분리한 이유
- URL: https://blog.sorune.org/photogram-exif-gps-place-privacy/
- Published: 2026-09-22T01:52:49.000Z
- Updated: 2026-09-22T01:52:49.000Z
- Description: 사진의 EXIF GPS는 좋은 장소 추천 신호지만 그대로 저장하거나 공개 위치로 취급하면 위험하다. 정확한 촬영 좌표는 transient observation으로만 쓰고 사용자가 선택한 Place snapshot만 남기는 구조를 정리한다.
- Author: Sorune — Engineering, Photography & Lab Notes
- Tags: Engineering, PhotoGram, EXIF, GPS, Privacy, Place

사진 서비스에서 장소 기능을 만들 때 가장 편한 출발점은 EXIF GPS다.

사용자가 사진을 업로드하면 이미 촬영 좌표가 들어 있을 수 있다.

그 좌표를 지도 API에 넘겨 주변 장소를 찾고, 가장 가까운 장소를 자동으로 붙이면 UX도 좋아 보인다.

하지만 실제 제품으로 설계하려고 하니 이 접근에는 위험한 등식이 숨어 있었다.

```text
촬영 좌표
=
공개할 장소

```

두 정보는 같지 않다.

PhotoGram Place를 설계하면서 가장 먼저 분리한 것도 이 두 좌표의 의미와 lifecycle이었다.

## 정확한 촬영 좌표와 Place 좌표는 다른 데이터다

Place 기능에는 두 종류의 coordinate가 등장한다.

첫 번째는 사진에서 관찰한 exact capture coordinate다.

```text
CaptureLocationObservation

= EXIF에서 읽은 실제 촬영 지점
= private
= transient
= 후보를 찾기 위한 입력

```

두 번째는 사용자가 선택한 공개 Place의 representative coordinate다.

```text
PlaceSnapshot.coordinate

= 지도 provider가 가진 장소 대표 좌표
= 사용자가 선택한 Place의 공간 표현
= map / discovery presentation에 사용 가능

```

예를 들어 카페 안에서 사진을 찍었다고 하자.

EXIF 좌표는 테이블 근처까지 꽤 정확할 수 있다.

하지만 공개하고 싶은 정보는 보통 `이 카페에서 찍은 사진`이지 `이 위도/경도의 정확한 지점에서 찍은 사진`이 아니다.

그래서 다음 등식을 금지했다.

```text
CAPTURE COORDINATE
!= PLACE COORDINATE

```

## EXIF-first지만 EXIF-authoritative는 아니다

위치 후보를 찾는 데 EXIF는 매우 유용하다.

따라서 기본 resolution은 EXIF-first로 잡았다.

```text
Photo upload
→ EXIF GPS available?
→ nearby Place candidates
→ user confirmation

```

하지만 EXIF는 추천 source일 뿐이다.

```text
EXIF-FIRST
USER-AUTHORITATIVE
MANUAL-ALWAYS-AVAILABLE

```

사용자는 추천된 장소를 바꿀 수 있고, 전혀 다른 장소를 검색할 수도 있고, Place를 제거할 수도 있다.

EXIF가 없거나 metadata processing에 동의하지 않은 경우에도 manual search는 계속 가능하다.

이 차이가 중요한 이유는 자동 추론을 제품의 최종 사실로 만들지 않기 위해서다.

## 원본 좌표는 DB에 넣지 않는다

가장 강하게 잡은 privacy boundary는 단순하다.

```text
EXACT CAPTURE GPS
=
TRANSIENT ONLY

```

정확한 capture latitude/longitude는 Place domain의 durable state가 아니다.

개념적인 flow는 다음과 같다.

```text
original media bytes
→ transient EXIF parse
→ exact capture coordinate
→ consent check
→ candidate discovery
→ normalized Place candidates
→ user selection
→ PlaceSnapshot
→ exact capture coordinate discarded

```

그래서 raw GPS를 담는 별도 Place table을 만들지 않았다.

그리고 "DB에만 안 넣으면 된다"로 끝내지도 않았다.

## 로그와 analytics도 persistence다

개발할 때 민감한 값을 DB에서 제외하고도 log에 그대로 남기는 경우가 있다.

예를 들어 provider 호출에 latitude와 longitude를 query parameter로 넣고 실패했을 때 request URI 전체를 debug log에 기록하면, DB에는 없지만 exact GPS는 이미 durable log가 된다.

그래서 raw coordinate 금지 범위를 넓게 잡았다.

```text
exact capture coordinate MUST NOT enter

- database row
- durable cache
- persistent domain event
- analytics event
- access/debug log
- selection token
- PlaceSnapshot
- PlaceProjection

```

대신 observability에는 좌표가 아닌 operational fact만 남길 수 있다.

```text
provider
resolve success / failure
candidate count
latency
normalized failure code

```

privacy는 schema만 보는 것으로 끝나지 않는다.

데이터가 지나가는 모든 secondary system을 같이 봐야 한다.

## 동의도 하나의 스위치로 합치지 않았다

PhotoGram에서는 metadata를 읽는 것과 장소를 공개하는 것도 서로 다른 사용자 의사다.

그래서 두 consent를 분리했다.

```text
Metadata Processing Consent
= 원본 media metadata를 이 workflow에서 해석해도 되는가?

Place Exposure Consent
= 선택한 Place를 Photo와 공개적으로 연결해도 되는가?

```

EXIF 기반 자동 후보 생성에는 둘 다 필요하다.

```text
metadata processing = ON
AND
place exposure = ON
→ EXIF candidate resolution allowed

```

OS location permission도 이 consent를 대신하지 않는다.

운영체제가 위치 접근을 허용했다고 해서 사용자가 PhotoGram에 public Place association까지 승인한 것은 아니기 때문이다.

## Metadata consent를 끄는 것과 공개 Place를 철회하는 것도 다르다

여기서 lifecycle이 조금 더 복잡해진다.

Metadata Processing Consent를 OFF 하면 앞으로의 metadata interpretation을 중지하고 Place Exposure도 OFF로 cascade한다.

하지만 이미 사용자가 직접 확인해서 게시한 PlaceSnapshot까지 즉시 삭제해야 하는 것은 아니다.

그 snapshot은 raw GPS가 아니라 별도로 선택된 공개 Place이기 때문이다.

반대로 사용자가 Place Exposure 자체를 철회하면 의미가 다르다.

```text
"앞으로 EXIF를 읽지 마"
!=
"이미 공개한 이 장소 연결을 제거해"

```

Place Exposure withdrawal에서는 해당 Photo의 public Place association을 제거한다.

```text
Photo-owned PlaceSnapshot
→ delete

Place projection membership
→ delete

```

다만 여러 Photo가 공유하는 Place projection identity 자체는 다른 Photo가 사용 중일 수 있으므로 그대로 남을 수 있다.

## Place의 source of truth도 Photo로 뒀다

장소 기능을 만들다 보면 global Place master부터 만들고 싶어진다.

지도 provider의 모든 장소를 canonical table에 넣고 Photo가 그 ID를 가리키는 방식이다.

하지만 PhotoGram은 장소 카탈로그 서비스가 아니다.

제품에서 더 중요한 질문은:

```text
"이 장소에서 어떤 사진이 찍혔는가?"

```

에 가깝다.

그래서 V1 source of truth를 Photo 쪽에 뒀다.

```text
Photo
└─ PlaceSnapshot

Place / Search / Map
= Photo-derived projection

```

사용자가 Place를 선택하면 해당 Photo가 provider-neutral normalized snapshot을 가진다.

```text
provider
externalPlaceId
displayName
category
public-safe address/region
provider representative coordinate
resolvedAt

```

provider raw payload 전체를 domain state로 저장하지 않는다.

이 구조는 외부 provider schema가 바뀌더라도 기존 Photo가 당시 선택했던 장소 표현을 유지하게 해준다.

## Selection token에도 raw GPS를 넣지 않는다

candidate를 client에 보여주고 나중에 선택 결과를 다시 서버에 전달하려면 signed token 같은 방식이 편리하다.

하지만 token도 데이터 운반 수단이자 잠재적인 persistence surface다.

그래서 selection token에는 선택 가능한 Place candidate의 public-safe fact만 들어간다.

```text
provider
externalPlaceId
displayName
displayAddress
category
Place representative coordinate
selection source
expiry / integrity metadata

```

원본 EXIF latitude/longitude는 넣지 않는다.

서명이 되어 있다고 민감한 데이터가 안전한 것은 아니다.

```text
SIGNED
!= PRIVATE

```

## 원본 파일의 EXIF까지 자동으로 지운다는 뜻은 아니다

여기서 한 가지 authority boundary도 명확히 했다.

Place가 exact GPS를 추출해서 저장하지 않는다는 것과 업로드된 original media bytes 안에 GPS metadata가 존재하지 않는다는 것은 다른 주장이다.

```text
Place retains no extracted exact GPS
!=
original source bytes contain no GPS metadata

```

원본 media의 retention과 sanitization은 Media subsystem의 정책이다.

Place 기능을 추가하면서 몰래 그 정책까지 바꾸지 않았다.

다만 public presentation derivative에는 기존 media privacy contract가 적용된다.

이 구분을 하지 않으면 한 feature의 privacy policy가 다른 storage authority를 암묵적으로 덮어쓰게 된다.

## Privacy-friendly UX는 기능을 포기하는 것이 아니었다

처음에는 exact GPS를 저장하지 않으면 Place 기능이 약해질 것처럼 보일 수 있다.

하지만 실제 필요한 것은 exact point를 영구 보존하는 것이 아니라 **좋은 후보를 한 번 찾는 것**이었다.

```text
exact GPS
→ temporary discovery signal

selected Place
→ durable public representation

```

두 lifecycle을 분리하니 오히려 UX와 privacy를 같이 가져갈 수 있었다.

사용자는 EXIF가 있으면 편하게 추천을 받는다.

없으면 직접 검색한다.

추천이 틀리면 고친다.

원하지 않으면 장소를 공개하지 않는다.

서비스는 정확한 촬영 좌표를 Place domain에 남기지 않는다.

결국 이 설계의 핵심은 위치 기능을 덜 만드는 것이 아니었다.

```text
PRIVATE OBSERVATION
→ RECOMMENDATION
→ HUMAN CONFIRMATION
→ PUBLIC-SAFE SNAPSHOT

```

이라는 경계를 만드는 것이었다.

사진 서비스에서 metadata는 매우 유용하다.

하지만 유용한 observation이 곧 durable product state가 되어야 하는 것은 아니다.