← 기술 블로그

대체 제안을 서버가 아닌 브라우저에서 계산하는 이유

Phase: 5 — 안전한 표현 시리즈: 공공데이터 레시피 대응 스토리: 운영 블로그 18화

이 글의 배경이 되는 이야기는 18화: 만든 걸 세상에 내놓기에서 읽을 수 있습니다.

이 글을 읽으면 알 수 있는 것


문제: 알레르기 정보는 민감정보입니다

이 서비스는 사용자의 기피성분을 기반으로 제품을 분석합니다. “이 제품에 내가 피하는 성분이 있는가?” — 이것이 핵심 기능입니다.

이 기능을 구현하려면 두 가지 데이터가 필요합니다.

  1. 제품의 원재료 정보 — 서버에 있습니다 (104만 건)
  2. 사용자가 피하는 성분 목록 — 어디에 저장할 것인가?

당연한 접근은 서버에 저장하는 것입니다. 사용자 계정을 만들고, 로그인하면 기피성분 설정을 불러오고, 서버에서 매칭해서 결과를 돌려주는 구조.

하지만 이 접근에는 문제가 있습니다.

알레르기 정보는 건강 관련 민감정보입니다. 한국 개인정보보호법에서 건강정보는 민감정보로 분류되며, 수집·이용 시 별도의 동의가 필요합니다. “이 사용자는 땅콩 알레르기가 있다”는 정보를 서버에 저장하는 순간, 이 서비스는 민감정보 처리자가 됩니다.

스타트업 단계에서 민감정보 보호 의무를 완벽하게 이행하는 것은 현실적으로 어렵습니다. DB 암호화, 접근 통제, 유출 시 통지 의무 등 준수해야 할 것이 많아집니다.


갈림길: 서버 저장 vs 클라이언트 저장

방식장점단점
서버 저장 (DB)기기 간 동기화, 서버에서 개인화 가능민감정보 처리자 의무, 보안 리스크
클라이언트 저장 (localStorage)서버에 민감정보 없음, 구현 단순기기 간 동기화 불가, 브라우저 삭제 시 소실

두 번째를 선택했습니다.

원칙은 단순합니다. 서버는 사용자가 누구인지, 어떤 알레르기가 있는지 모릅니다. 서버가 제공하는 것은 제품 데이터와 기피성분 규칙 목록뿐입니다. “이 사용자에게 맞춤화된 결과”는 브라우저에서 계산합니다.


구조: 서버는 규칙을, 브라우저는 판단을

전체 흐름은 이렇습니다.

[서버]                           [브라우저]

제품 데이터 → API 제공            사용자가 알레르기 설정
기피성분 규칙 → API 제공   ────→  규칙 캐시

                                 제품 조회 시:
                                 제품 원재료 + 사용자 설정 + 규칙

                                 브라우저에서 매칭

                                 "이 제품에 주의 성분이 있습니다"

중요한 점은, 제품 조회 API에 사용자 식별 정보가 포함되지 않는다는 것입니다. “새우깡의 원재료를 알려줘”라는 요청에 “이 사람은 밀 알레르기가 있다”는 정보가 붙지 않습니다. 모든 사용자에게 동일한 응답이 돌아갑니다.

개인화는 그 응답을 받은 후, 브라우저에서 일어납니다.


브라우저에 저장되는 것들

데이터저장 위치서버 전송 여부
알레르겐 선택 (22종)localStorage전송 안 함
기피성분 선택 (인지 기반, 기호 기반)localStorage전송 안 함
특수 건강군 (임산부, 영유아 등)localStorage전송 안 함
즐겨찾기 제품 목록localStorage전송 안 함
최근 본 제품localStorage전송 안 함
개인정보 동의 여부localStorage전송 안 함

모든 사용자 개인 데이터는 브라우저의 localStorage에만 존재합니다. 서버 DB에는 사용자 테이블 자체가 없습니다.


기피성분 매칭이 브라우저에서 일어나는 과정

  1. 서비스 첫 접속 시, 서버에서 기피성분 규칙 목록을 받아옵니다 (42종의 규칙 정의)
  2. 이 규칙을 브라우저에 캐시합니다
  3. 사용자가 프로필에서 “나는 땅콩 알레르기가 있다”고 설정합니다 → localStorage에 저장
  4. 제품을 조회하면, 서버에서 원재료 목록을 받아옵니다
  5. 브라우저에서 원재료 목록을 사용자의 설정과 대조합니다
  6. 매칭 결과를 화면에 표시합니다

4번에서 5번으로 넘어갈 때, 서버에는 아무것도 보내지 않습니다. 매칭은 전적으로 브라우저의 JavaScript에서 실행됩니다.


이 설계가 포기하는 것

기능서버 저장 시localStorage 시
기기 간 동기화가능불가능
서버 측 추천가능불가능
사용자 행동 분석가능불가능
데이터 백업자동사용자 책임

기기 간 동기화가 안 됩니다. 휴대폰에서 설정한 알레르기 정보가 PC에서는 보이지 않습니다. 각 기기에서 다시 설정해야 합니다.

서버 측 추천이 불가능합니다. “이 성분이 없는 대체 제품”을 서버에서 추천하려면 사용자의 기피성분을 알아야 합니다. 현재 구조에서는 이 추천을 브라우저에서 계산해야 합니다.

사용자 행동 분석을 할 수 없습니다. “사용자의 몇 %가 MSG를 기피하는가” 같은 통계를 낼 수 없습니다. 서버가 이 정보를 모르니까요.

이 모든 것을 포기하고 얻는 것은 하나입니다. 민감정보 유출 리스크가 0입니다. 서버에 없는 데이터는 유출될 수 없습니다.


결과

항목내용
사용자 개인 데이터 저장전량 localStorage
서버 전송없음
사용자 계정 시스템없음
매칭 실행 위치브라우저 (JavaScript)

한계

브라우저 캐시가 삭제되면 모든 설정이 사라집니다. 사용자가 브라우저 데이터를 정리하면 알레르기 설정을 다시 해야 합니다. 이를 완화하기 위해 설정 내보내기/가져오기 기능을 고려하고 있지만, 아직 구현하지 않았습니다.

향후 서버 저장이 필요해질 수 있습니다. 사용자가 늘어나고 기기 간 동기화 요구가 강해지면, 서버 저장으로 전환해야 할 수 있습니다. 그때는 민감정보 처리에 대한 별도 동의와 암호화가 필수입니다. 현재 구조에서 전환할 수 있도록, 데이터 접근 함수를 한 곳에 모아두었습니다.

한 줄 교훈

서버에 없는 데이터는 유출될 수 없습니다. 민감정보를 다루는 서비스에서 “저장하지 않는 것”이 가장 강력한 보호입니다.


다음 글: 비개발자가 Django+Next.js를 선택한 과정