EEltaCrew
홈 › 블로그

동기화가 판매점 8,802곳을 지운 날: 공공데이터 필드 이름 하나와 100건짜리 목록

게시 2026-10-07실측 기준 2026-10-07읽는 시간 약 5분elta · EltaCrew

로또 판매점 지도 앱에서 동기화 뒤 판매점 이름이 전부 「알수없음」으로 바뀌고, 앱에 넣어 둔 판매점 8,802곳이 사라졌습니다. 공공데이터 응답의 필드 이름 변경, 첫 100건만 받은 목록으로 삭제하던 동기화, 지오코더의 없는 키까지 원인 세 가지와 고친 방법을 정리합니다.

지도가 「알수없음」으로 찼다

로또 판매점 지도를 보여 주는 앱을 운영하고 있습니다. 앱 안에 판매점 8,802곳을 담은 SQLite 파일(store.db)을 넣어 두고, 공공데이터포털의 로또 판매점 API 와 비교해서 바뀐 것만 반영하는 구조입니다.

9월 18일, 판매점 탭을 열면 목록 이름이 전부 「알수없음」으로 나왔습니다. 실기기(갤럭시 노트10+)에서 앱 DB 를 꺼내 열어 보니 행이 295개뿐이었고, 이름은 하나도 빠짐없이 「알수없음」이었습니다. 8,802곳이 있어야 할 자리였습니다.

원인은 세 겹이었습니다.

원인 1: 응답 필드 이름이 바뀌었다

앱은 판매점 이름을 이렇게 읽고 있었습니다.

String merchant = item.has("상호") ? item.get("상호").getAsString() : "알수없음";

API 를 직접 불러 응답을 보니 상호 라는 칸이 없었습니다. 이름은 판매점명 에 들어 있었고, 지번주소 칸도 없어졌습니다. 오늘(10월 7일) 다시 불러 본 응답도 칸은 네 개입니다.

도로명주소, 순서, 판매점명, 판매점종류      (totalCount 23,503)

has("상호") 가 늘 false 이니, 들어오는 판매점은 전부 「알수없음」이 됐습니다. 여기까지는 이름이 틀리는 문제입니다. 행이 사라진 건 두 번째 원인 때문입니다.

원인 2: 100건짜리 목록으로 지웠다

동기화 흐름은 이랬습니다.

  1. API 에서 전체 개수(totalCount)만 먼저 받는다.
  2. 기기 DB 의 행 수와 같으면 DB 를 그대로 쓴다.
  3. 다르면 목록을 받아서 비교한다. 새 판매점은 넣고, 목록에 없는 DB 행은 지운다.

문제는 3번에서 받는 목록이 page=1&perPage=100, 즉 첫 100건뿐이었다는 점입니다. 전체 개수가 기기 DB 와 같은 동안에는 3번을 탈 일이 없어서 드러나지 않았습니다. API 의 전체 개수가 23,503 이 되어 8,802 와 달라지자, 앱은 100건을 받아 놓고 「이 100건에 없는 판매점은 문을 닫았다」 고 보고 나머지를 지웠습니다.

게다가 그 100건은 원인 1 때문에 이름이 전부 「알수없음」이었습니다. 비교 키가 「이름|도로명주소」 라서 내장 8,802건과는 하나도 맞지 않았고, 그래서 남김없이 지워졌습니다.

원인 3: 지오코더도 없는 키를 읽었다

동기화로 새로 들어온 판매점은 네이버 지오코딩으로 좌표를 붙입니다. 이 코드는 응답의 address1·address2 를 읽고 있었는데, 실제 응답 키는 roadAddress·jibunAddress 였습니다. 그래서 동기화로 들어온 행은 주소가 늘 빈 문자열이었습니다.

세 가지 모두 「응답에 이 키가 있을 것이다」 라는 가정이 틀린 경우입니다. 컴파일 오류도 크래시도 없었습니다. 값만 조용히 비었습니다.

고친 것 (1.0.58)

1. 이름은 여러 키로 읽고, 그래도 없으면 비슷한 칸을 찾는다

private static String storeName(JsonObject item) {
    for (String k : new String[]{"판매점명", "상호", "상호명", "판매점"}) {
        String v = str(item, k);
        if (!v.isEmpty()) return v;
    }
    for (String k : item.keySet()) {
        if ((k.contains("상호") || k.contains("점명")) && item.get(k).isJsonPrimitive()) {
            String v = item.get(k).getAsString().trim();
            if (!v.isEmpty()) return v;
        }
    }
    return "알수없음";
}

이름이 「알수없음」으로 나온 판매점은 아예 DB 에 넣지 않습니다.

2. 받은 목록이 전체일 때만 지운다

boolean complete = stores.size() >= nCount;
syncStoreDataDelta(db, stores, complete);

// syncStoreDataDelta 안, 새 행을 넣고 바뀐 행을 고친 뒤
if (!complete) return;   // 일부만 받았으면 지우지 않는다

100건만 받았으면 넣거나 고치기만 하고 지우지는 않습니다.

3. 이미 망가진 기기는 스스로 복구한다

업데이트를 받는 사용자 기기에는 이미 망가진 DB 가 있습니다. Room 의 createFromAsset 은 DB 를 처음 만들 때만 자산 파일을 읽기 때문에, 앱을 업데이트해도 지워진 8,802건은 돌아오지 않습니다. 그래서 동기화를 시작할 때 두 가지를 먼저 합니다.

4. 지오코더 키를 바로잡는다

roadAddress·jibunAddress 를 읽고, 그래도 비어 있으면 요청한 주소를 그대로 씁니다.

같은 노트10+ 에서 1.0.58 을 실행한 결과는 8,901건 · 「알수없음」 0건 · 빈 주소 0건이었고, 지도에 판매점 실명이 다시 떴습니다.

정리

이 글에 나온 앱은 로또 명당(Google Play) 입니다.

AndroidRoom공공데이터동기화SQLite

함께 읽기

TV 페어링 코드 창에서 사용자가 떠난 이유: 6자리 코드와 한글 자판TV 리모컨 앱의 페어링 오류를 고친 뒤 오히려 「코드 입력 창에서 취소」가 9% 에서 19% 로 늘었습니다. GA4 실패 사유를 열어 보고, 코드 입력칸 하나가 한글 자판으로 열리던 것을 찾은 과정과 고친 방법을 정리합니다.dispatchDetachedFromWindow NullPointerException: 스택에 내 코드가 없는 안드로이드 크래시 추적기스택 트레이스에 프레임워크 코드만 찍히는 NullPointerException 을 MdViewer 에서 만났습니다. onDetachedFromWindow 안에서 형제 뷰를 뗀 한 줄이 원인이었고, 부모 ViewGroup 이 자식 배열을 도는 방식 때문에 생긴 문제였습니다. 재현, 원인, 고친 방법을 정리합니다.안드로이드에서 .md 파일 여는 법: AI 가 준 마크다운이 기호 범벅으로 보일 때ChatGPT·Claude·Codex 가 만들어 준 .md 파일, 옵시디언 노트, 깃허브 README 를 폰에서 열면 #·*·| 가 그대로 보입니다. 마크다운을 폰에서 제대로 읽는 방법과, 표·체크리스트·수식이 깨지는 이유, 직접 뷰어를 만들며 확인한 점을 씁니다.