사이드프로젝트 slecs

북마크 이관 스키마 충돌과 외부 앱 차단 오동작 수정

목차

북마크 이관에서 데이터 깨짐 만남

레거시 다운로드 매니저에서 북마크를 새 브라우저 모듈로 옮기는 작업을 함. 단순 복사인 줄 알았는데 막상 까보니 두 저장소가 스키마부터 달라서 그대로 INSERT 하면 절반이 NULL 로 들어감.

기존 구조와 새 구조 차이를 먼저 정리했음.

항목 레거시 신규
식별자 정수 PK UUID 문자열
폴더 트리 parent_id 자기참조 path 슬래시 표기
즐겨찾기 정렬 created_at 역순 사용자 지정 position
썸네일 별도 캐시 디렉토리 base64 인라인

position 컬럼이 신규에만 있어서 일괄 이관 시 전부 0 으로 들어가 정렬이 무너졌음. 결국 레거시의 created_at 을 ORDER BY 로 뽑아서 ROW_NUMBER 로 채워넣는 식으로 해결.

val ordered = legacyDao.fetchAll()
    .sortedByDescending { it.createdAt }
    .mapIndexed { idx, row -> row.toNew(position = idx) }
newDao.bulkInsert(ordered)

배치 크기는 500 으로 맞췄음. 한 번에 다 넣으면 트랜잭션 락 길어져서 홈 화면 첫 진입 렌더가 800ms 넘게 밀림. 나눠 넣으니 200ms 이내로 떨어짐.

외부 앱 이동 차단 — 의도와 반대로 동작했음

홈 화면에서 특정 URL 패턴은 외부 앱(스토어, 결제 앱 등) 으로 점프하지 못하게 막아야 했음. intent:// 스킴 파싱이 핵심.

처음 짠 코드는 이랬는데 동작이 이상했음.

if (url.startsWith("intent://") && !allowList.contains(host)) {
    return true // 차단
}
return false

문제는:

  • 스킴이 intent:// 가 아니라 market:// 나 사용자 정의 스킴(myapp://) 으로 오는 케이스가 더 많음
  • WebView 의 shouldOverrideUrlLoading 이 true 를 반환하면 "내가 처리했다" 인데, 아무것도 안 하면 그냥 흰 화면이 됨
  • 일부 광고 스크립트가 location.href 로 강제 이동시켜서 사용자가 뒤로가기 누르면 빈 페이지에 갇힘

차단 로직을 다시 짰음.

  • HTTP/HTTPS 가 아닌 모든 스킴은 일단 가로챔
  • 화이트리스트에 있으면 startActivity 로 외부 앱 호출 허용
  • 없으면 토스트만 띄우고 현재 페이지 유지 (history 오염 방지)
  • intent:// 의 경우 fallback URL 파라미터를 추출해서 그쪽으로 대체 이동

테스트는 아래 케이스로 돌림.

시나리오 기대 동작 결과
결제대행사 앱 호출 (허용) 외부 앱 실행 OK
스토어 직링크 (차단) 토스트 + 페이지 유지 OK
광고 redirect (차단) 무시, 뒤로가기 정상 OK
fallback 있는 intent fallback URL 로 이동 OK

남은 숙제

  • 이관 실패 시 롤백 전략이 아직 없음. 부분 INSERT 된 상태로 앱이 죽으면 다음 부팅 때 중복됨. 마이그레이션 버전 플래그를 따로 둘 예정.
  • 외부 앱 차단 화이트리스트가 코드 상수로 박혀 있는데, 운영 중에 추가가 필요해서 원격 설정으로 빼야 함.
  • 이커머스 파트너 측에서 추가로 허용해달라는 스킴 리스트가 늘어나는 중. 운영 채널에서 받는 즉시 반영하기 어려운 구조라 우선순위 올려야겠음.

작업 시간 대비 이관 디버깅이 절반 이상 잡아먹은 하루. 스키마 마이그레이션은 항상 만만하게 보면 안 된다는 걸 또 한 번 체감.

댓글 0

첫 댓글 달아줘.