현장 앱의 오프라인 우선 스토리지: SQLite vs Realm
현장 앱용 오프라인 우선 스토리지: SQLite와 Realm 비교 — 마이그레이션, 쿼리 옵션, 충돌 처리, 디버깅 툴, 실무 선택 팁을 정리합니다.

현장 앱이 실제로 필요한 것
오프라인 퍼스트는 단순히 “인터넷 없이 작동”한다는 뜻이 아닙니다. 앱이 유용한 데이터를 불러오고, 새 입력을 받고, 모든 편집을 동기화 가능해질 때까지 안전하게 보관한다는 의미입니다.
현장 작업에는 예측 가능한 제약이 있습니다: 신호가 들쑥날쑥하고, 세션이 길며, 기기가 오래될 수 있고 절전 모드가 흔합니다. 사람들은 빨리 움직입니다. 작업을 열고, 긴 목록을 스크롤하고, 사진을 찍고, 폼을 작성한 뒤 생각하지 않고 다음 작업으로 넘어갑니다.
사용자가 눈치채는 문제는 단순합니다. 편집이 사라지면 신뢰를 잃습니다. 오프라인 상태에서 목록과 검색이 느리거나, “내 작업이 저장되었나?”라는 질문에 앱이 명확히 대답하지 못하거나, 재연결 후 레코드가 중복되거나 사라지거나, 업데이트로 인해 이상 동작이 발생하면 신뢰가 깨집니다.
그래서 SQLite와 Realm 사이의 선택은 대개 벤치마크가 아니라 일상적 동작에 관한 문제입니다.
로컬 데이터베이스를 고르기 전에 네 가지 영역을 명확히 하세요: 데이터 모델은 변할 것이고, 쿼리는 실제 워크플로와 일치해야 하며, 오프라인 동기화는 충돌을 만들고, 도구(툴링)가 현장 문제를 얼마나 빨리 진단할 수 있는지를 결정합니다.
1) 데이터는 변합니다
안정적이라고 보이는 앱도 진화합니다: 새 체크박스 추가, 상태 이름 변경, “메모” 필드를 구조화된 필드로 분리 등. 모델 변경이 고통스럽다면 개선을 덜 자주 출시하거나 실제 데이터가 있는 기기를 깨뜨릴 위험이 있습니다.
2) 쿼리는 실제 워크플로에 맞아야 합니다
현장 앱에는 “오늘의 작업”, “근처 현장”, “동기화되지 않은 폼”, “최근 2시간 내 편집된 항목” 같은 빠른 필터가 필요합니다. 데이터베이스가 이런 쿼리를 표현하기 어렵게 만들면 UI가 느려지거나 코드가 미로처럼 복잡해집니다.
3) 오프라인 동기화는 충돌을 만듭니다
두 사람이 같은 레코드를 편집하거나, 한 기기가 오래된 데이터를 며칠 동안 편집할 수 있습니다. 어떤 변경이 우선인지, 무엇을 병합할지, 무엇을 사람이 처리해야 하는지에 대한 명확한 계획이 필요합니다.
4) 툴링이 중요합니다
현장에서 문제가 발생하면 데이터를 검사하고, 문제를 재현하고, 추측 없이 무슨 일이 있었는지 이해할 수 있어야 합니다.
마이그레이션: 사용자를 깨지 않고 데이터 모델 변경하기
현장 앱은 좀처럼 그대로 있지 않습니다. 몇 주 후에는 체크박스를 추가하거나 상태 이름을 바꾸거나 “notes” 필드를 구조화된 필드로 분리합니다. 마이그레이션은 오프라인 앱이 자주 실패하는 지점인데, 그 이유는 이미 기기에 실제 데이터가 있기 때문입니다.
SQLite는 테이블과 컬럼에 데이터를 저장합니다. Realm은 속성을 가진 객체로 데이터를 저장합니다. 이 차이는 곧 드러납니다:
- SQLite에서는 보통 명시적인 스키마 변경(ALTER TABLE, 새 테이블, 데이터 복사)을 작성합니다.
- Realm에서는 스키마 버전을 올리고 객체가 접근될 때 실행되는 마이그레이션 함수를 작성하는 방식이 일반적입니다.
필드 추가는 두 시스템 모두 쉬운 편입니다: SQLite에서는 컬럼을 추가하고, Realm에서는 기본값을 가진 속성을 추가합니다. 하지만 이름 변경과 분할이 고통스러운 작업입니다. SQLite에서는 설정에 따라 이름 변경이 제한적일 수 있어 새 테이블을 만들고 데이터를 복사하는 패턴이 흔합니다. Realm에서는 마이그레이션 중에 이전 속성을 읽어 새 속성에 쓰면 되지만, 타입, 기본값, null 처리를 주의해야 합니다.
기기에 이미 데이터가 있는 상태에서 대규모 업데이트는 추가 주의가 필요합니다. 모든 레코드를 다시 쓰는 마이그레이션은 오래된 폰에서 느릴 수 있고, 기술자가 주차장에서 스피너를 보며 기다리게 해서는 안 됩니다. 마이그레이션 시간을 계획하고 무거운 변환은 여러 릴리스에 걸쳐 분산시키는 것을 고려하세요.
마이그레이션을 공정하게 테스트하려면 동기화처럼 다루세요:
- 오래된 빌드를 설치하고 현실적인 데이터를 만든 뒤 업그레이드하세요.
- 소규모와 대규모 데이터셋을 모두 테스트하세요.
- 마이그레이션 중 앱을 강제로 종료했다가 재실행하세요.
- 저장 공간이 부족한 시나리오를 테스트하세요.
- 되돌릴 수 없을 가능성을 가정하고 앞으로 나아갈 수 있게 하세요.
예: "equipmentId"가 "assetId"로 바뀌고 이후에 "assetType"과 "assetNumber"로 분리된다면, 마이그레이션은 이전 검사가 계속 사용 가능하도록 유지해야 하며 로그아웃이나 데이터 초기화를 강요해서는 안 됩니다.
쿼리 유연성: 데이터에 무엇을 물어볼 수 있나
현장 앱은 목록 화면에 목숨을 겁니다: 오늘의 작업, 근처 자산, 미처리 티켓이 있는 고객, 이번 주에 사용된 부품 등. 스토리지 선택은 이런 질문을 쉽게 표현하고 빠르게 실행하며 6개월 뒤에도 오해하기 어렵게 만드는 것이어야 합니다.
SQLite는 여전히 대규모 데이터셋을 필터링하고 정렬하는 가장 유연한 방법으로 SQL을 제공합니다. 조건 결합, 테이블 간 조인, 그룹화, 화면이 느려질 때 인덱스 추가 등이 가능합니다. 예를 들어 “지역 A의 자산에 대한 모든 검사 중 팀 3에 할당되고 체크리스트 항목 중 하나라도 실패한 항목” 같은 쿼리는 SQL로 깔끔하게 표현할 수 있습니다.
Realm은 객체와 상위 수준의 쿼리 API를 사용합니다. 많은 앱에서는 직관적으로 느껴집니다: Job 객체를 쿼리하고 상태로 필터링하고 마감일로 정렬하고 관련 객체로 관계를 따라갑니다. 단점은 조인 기반의 리포팅형 쿼리처럼 SQL에서는 trivial한 일부 질문이 표현하기 어렵거나, 필요한 쿼리에 맞게 데이터를 재구성해야 할 수 있다는 점입니다.
검색과 관계
여러 필드(작업 제목, 고객 이름, 주소)에 걸친 부분 텍스트 검색은 SQLite에서 신중한 인덱싱이나 전용 전체 텍스트 검색(FTS) 접근을 요구하는 경우가 많습니다. Realm도 텍스트 필터링을 지원하지만 성능과 대규모에서의 “contains” 의미를 생각해야 합니다.
관계는 또 다른 실무상의 골칫거리입니다. SQLite는 조인 테이블로 일대다와 다대다 관계를 처리해 “두 태그가 달린 자산” 같은 패턴을 간단히 만들 수 있습니다. Realm의 링크는 코드에서 탐색하기 쉽지만 다대다와 ‘쿼리를 통해’ 접근하는 패턴은 읽기 성능을 유지하기 위해 더 많은 계획이 필요합니다.
원시 쿼리 vs 유지보수하기 쉬운 코드
유지보수에 좋은 패턴은 화면과 리포트에 직접 대응하는 이름 붙은 쿼리 집합을 작게 유지하는 것입니다: 주요 목록 필터 및 정렬, 상세 뷰 쿼리(하나의 레코드와 관련 레코드), 검색 정의, 몇 개의 카운터(배지와 오프라인 합계), 그리고 내보내기/리포트 쿼리 등.
비즈니스에서 자주 발생하는 ad hoc 질문이 많을 것으로 예상한다면 SQLite의 원시 쿼리 능력은 강력합니다. 대부분의 데이터 접근이 평범한 객체처럼 읽히길 원한다면, Realm은 가장 어려운 화면을 어색한 회피 없이 대답할 수 있다는 전제 하에 더 빨리 구축할 수 있습니다.
충돌 해결과 동기화: 어떤 지원을 받나
오프라인 퍼스트 현장 앱은 보통 연결이 끊긴 상태에서 동일한 핵심 작업을 지원합니다: 레코드 생성, 업데이트, 잘못된 항목 삭제. 로컬 저장이 문제가 아니라 충돌이 발생했을 때 어떻게 처리할지 결정하는 것이 더 어렵습니다.
충돌은 단순한 상황에서도 나타납니다. 기술자가 신호가 없는 지하에서 검사를 업데이트하고, 나중에 감독자가 같은 검사를 노트북으로 수정할 수 있습니다. 둘 다 재연결되면 서버는 서로 다른 두 버전을 받습니다.
대부분 팀이 택하는 방법은 다음 중 하나입니다:
- 마지막 쓰기 우선(빠르지만 좋은 데이터를 조용히 덮어쓸 수 있음)
- 필드별 병합(서로 다른 필드가 바뀌는 경우 더 안전하지만 명확한 규칙이 필요함)
- 수동 검토 큐(가장 느리지만 고위험 변경에 적합함)
SQLite는 신뢰할 수 있는 로컬 데이터베이스를 제공하지만 자체적으로 동기화를 제공하지는 않습니다. 보통 대기 중인 작업을 추적하고, API로 보내고, 안전하게 재시도하며, 서버에서 충돌 규칙을 강제하는 로직을 직접 만들어야 합니다.
Realm은 자체 동기화 기능을 사용하면 일부 배선 작업을 줄여줄 수 있습니다. 객체와 변경 추적을 중심으로 설계되었기 때문입니다. 하지만 “내장된 동기화”도 비즈니스 규칙을 대신 정해주지는 않습니다. 어떤 것이 충돌로 간주되는지, 어떤 데이터가 이겨야 하는지는 여러분이 결정해야 합니다.
처음부터 감사 추적을 계획하세요. 현장 팀은 종종 “누가 언제 어느 디바이스에서 무엇을 변경했나”에 대한 명확한 답을 필요로 합니다. 마지막 쓰기 우선을 선택하더라도 사용자 ID, 디바이스 ID, 타임스탬프, (가능하면) 변경 사유 같은 메타데이터를 저장하세요. 백엔드를 빠르게 생성하는 도구(예: AppMaster)로 시작하면 수백 대의 오프라인 디바이스가 생기기 전에 규칙을 빠르게 반복해볼 수 있어 유리합니다.
디버깅과 검사: 현장이 문제를 겪기 전에 잡기
오프라인 버그는 서버와 대화하지 않을 때 발생하므로 문제를 관찰하기 어렵습니다. 디버깅 경험은 대개 한 가지 질문으로 귀결됩니다: 기기에서 무엇이 있었고 시간이 지남에 따라 어떻게 변했는지를 얼마나 쉽게 볼 수 있나?
SQLite는 파일이라 검사하기 쉽습니다. 개발이나 QA에서 테스트 기기에서 데이터베이스를 꺼내어 일반적인 SQLite 도구로 열고 ad hoc 쿼리를 실행하며 테이블을 CSV나 JSON으로 내보낼 수 있습니다. 이는 “어떤 행이 존재하는가”와 “UI가 무엇을 보여주는가”를 확인하는 데 도움이 됩니다. 단점은 스키마, 조인, 여러분이 만든 마이그레이션 임시 구조를 이해해야 한다는 점입니다.
Realm은 더 “앱 같은” 방식으로 검사하는 느낌을 줄 수 있습니다. 데이터가 객체로 저장되고 Realm의 도구는 클래스, 속성, 관계를 탐색하기에 종종 가장 간단한 방법입니다. 객체 그래프 문제(링크 누락, 예상치 못한 null 등)를 찾기 좋지만, SQL 기반 검사에 익숙한 팀에게는 ad hoc 분석이 덜 유연할 수 있습니다.
로깅과 오프라인 문제 재현
대부분의 현장 실패는 무언의 쓰기 오류, 부분적 동기화 배치, 또는 반쯤 끝난 마이그레이션에서 발생합니다. 어느 쪽이든, 몇 가지 기본에 투자하세요: 레코드별 "마지막 변경" 타임스탬프, 디바이스 측 작업 로그, 마이그레이션 및 백그라운드 쓰기 주변의 구조화된 로그, QA 빌드에서 상세 로깅을 활성화하는 방법, 그리고 익명화된 스냅샷을 내보내는 "덤프 및 공유" 기능.
예: 기술자가 완료된 검사가 배터리 방전 후 사라졌다고 보고하면, 공유된 스냅샷을 통해 레코드가 아예 쓰이지 않았는지, 쓰였지만 쿼리되지 않았는지, 시작 시 롤백되었는지 확인할 수 있습니다.
실패 스냅샷 공유
SQLite는 .db 파일(및 WAL 파일)을 공유하는 것만큼 간단한 경우가 많습니다. Realm은 보통 Realm 파일과 사이드카 파일을 함께 공유합니다. 어느 경우든, 기기에서 어떤 데이터든 외부로 나오기 전에 민감한 데이터를 제거하는 반복 가능한 프로세스를 정의하세요.
실제 세계의 신뢰성: 실패, 재설정, 업그레이드
현장 앱은 지루한 방식으로 실패합니다: 저장 중 배터리가 방전되거나 OS가 백그라운드에서 앱을 종료하거나 사진과 로그가 쌓여 저장소가 가득 차는 등. 로컬 데이터베이스 선택은 이러한 실패가 얼마나 자주 작업 손실로 이어지는지에 영향을 줍니다.
중간 쓰기 시 크래시가 발생해도 올바르게 사용하면 SQLite와 Realm 둘 다 안전할 수 있습니다. SQLite는 변경을 트랜잭션으로 감싸면 신뢰할 수 있고(WAL 모드는 복원력과 성능에 도움), Realm 쓰기는 기본적으로 트랜잭션이므로 보통 추가 작업 없이 "전부 또는 전무" 저장을 얻을 수 있습니다. 흔한 위험은 데이터베이스 엔진 자체가 아니라 여러 단계로 쓰기하면서 명확한 커밋 포인트가 없는 앱 코드입니다.
손상은 드물지만 복구 계획이 필요합니다. SQLite는 무결성 체크를 실행하거나 알려진 정상 백업에서 복원하거나 서버 재동기화로 재구축할 수 있습니다. Realm에서는 파일 자체가 의심스러워지는 경우가 있어 실무적으로는 "로컬 삭제 후 재동기화"가 복구 경로가 되는 경우가 많습니다(서버가 진실의 원천이라면 괜찮지만, 디바이스에 고유 데이터가 있다면 고통스럽습니다).
저장소 증가도 놀라움입니다. SQLite는 삭제 후 vacuum을 주기적으로 실행하지 않으면 부피가 커질 수 있습니다. Realm도 커질 수 있고 압축 정책과 오래된 객체(예: 완료된 작업) 제거가 필요할 수 있습니다.
업그레이드와 롤백도 함정입니다. 업데이트가 스키마나 저장 형식을 변경하면 롤백 시 사용자가 더 새 버전의 파일을 읽지 못하게 될 수 있습니다. 업그레이드는 일방향으로 계획하고 안전한 마이그레이션과 앱을 깨지 않는 ‘로컬 데이터 초기화’ 옵션을 제공하세요.
신뢰성을 높이는 습관:
- "디스크 가득 참"과 쓰기 실패를 명확한 메시지와 재시도 경로로 처리하세요.
- 긴 폼 입력은 끝에서만 저장하지 말고 체크포인트로 저장하세요.
- 복구와 지원을 위해 경량의 로컬 감사 로그를 유지하세요.
- 데이터베이스가 너무 커지기 전에 오래된 레코드를 가지치기하고 보관하세요.
- 저사양 기기에서 OS 업그레이드와 백그라운드 종료를 테스트하세요.
예: 체크리스트와 사진을 저장하는 검사 앱은 한 달 만에 저장공간 부족에 도달할 수 있습니다. 앱이 공간 부족을 조기에 감지하면 사진 캡처를 일시중지하고 가능할 때 업로드하며 체크리스트 저장은 안전하게 유지할 수 있습니다. 어떤 로컬 데이터베이스를 사용하든 이런 습관이 중요합니다.
단계별: 스토리지 접근법을 선택하고 설정하는 방법
스토리지를 라이브러리 결정이 아니라 제품의 일부로 취급하세요. 최선의 옵션은 신호가 끊길 때 앱을 사용할 수 있게 하고, 신호가 돌아왔을 때 예측 가능하게 동작하는 것입니다.
간단한 의사결정 경로
우선 오프라인 사용자 흐름을 적어보세요. 구체적으로: "오늘의 작업 열기, 메모 추가, 사진 첨부, 완료로 표시, 서명 캡처" 등. 그 목록의 모든 항목은 네트워크 없이 매번 작동해야 합니다.
그다음 짧은 순서를 따르세요: 오프라인에서 중요한 화면과 각 화면이 필요로 하는 데이터 양(오늘의 작업 대 전체 기록)을 나열하고, 최소 데이터 모델과 모방할 수 없는 관계(Job -> ChecklistItems -> Answers)를 스케치하고, 엔터티마다 충돌 규칙을 정하고(모든 것에 하나의 규칙을 쓰지 말 것), 실패를 어떻게 테스트할지 결정(현실 기기에서 마이그레이션, 동기화 재시도, 강제 로그아웃/재설치 동작)하고, 현실적인 데이터로 작은 프로토타입을 만들어 로드/검색/저장/하루 오프라인 후 동기화 시간을 측정하세요.
이 프로세스는 보통 실제 제약을 드러냅니다: 유연한 ad hoc 쿼리와 쉬운 검사 기능이 필요한가, 아니면 객체 기반 접근과 더 엄격한 모델 강화를 선호하는가?
프로토타입에서 검증할 항목
기술자가 오프라인에서 30건의 검사를 완료하고 다시 연결되는 시나리오 같은 현실적인 하나의 시나리오를 사용하세요. 5,000건으로 첫 로드 시간을 측정하고, 스키마 변경이 업데이트를 통과하는지 확인하고, 재연결 후 충돌이 얼마나 발생하는지 그리고 각 충돌을 설명할 수 있는지, 지원 요청 시 "문제가 있는 레코드"를 얼마나 빠르게 검사할 수 있는지를 확인하세요.
요구사항을 확정하기 전에 빠르게 검증하고 싶다면 no-code 프로토타입 도구인 AppMaster로 워크플로와 데이터 모델을 초기에 고정해두고 실제 기기에서 동작을 확인하는 것이 도움이 됩니다.
오프라인 퍼스트 앱에 해가 되는 흔한 실수
대부분의 오프라인 퍼스트 실패는 데이터베이스 엔진 때문이 아닙니다. 업그레이드, 충돌 규칙, 명확한 오류 처리 같은 지루한 부분을 건너뛰기 때문입니다.
한 가지 함정은 충돌이 드물다고 가정하는 것입니다. 현장 작업에서는 충돌이 정상입니다: 두 기술자가 같은 자산을 편집하거나 관리자가 디바이스가 오프라인일 때 체크리스트를 변경할 수 있습니다. 규칙(마지막 쓰기 우선, 필드별 병합, 또는 두 버전 보관)을 정의하지 않으면 결국 실제 작업이 덮어씌워집니다.
또 다른 조용한 실패는 데이터 모델을 ‘완성된 것’으로 간주하고 업그레이드 연습을 하지 않는 것입니다. 스키마 변경은 작은 앱에서도 발생합니다. 스키마 버전 관리와 이전 빌드에서의 업그레이드 테스트를 하지 않으면 사용자가 업데이트 후 마이그레이션 실패나 빈 화면을 만나게 됩니다.
성능 문제도 늦게 드러납니다. 팀이 모든 것을 “혹시 몰라서” 다운로드해 두고 왜 검색이 느리고 중간급 폰에서 앱 열리는 데 몇 분이나 걸리는지 의아해합니다.
주의할 패턴:
- 문서화된 충돌 정책 부재로 편집이 조용히 덮어써짐.
- 신설 설치에서는 동작하지만 실제 업그레이드에서는 실패하는 마이그레이션.
- 무한히 커지는 오프라인 캐시로 인해 쿼리가 느려짐.
- 스피너 뒤에 숨겨진 동기화 실패로 사용자가 데이터가 전송되었다고 착각함.
- 반복 가능한 재현 스크립트와 샘플 데이터 대신 추측에 의한 디버깅.
예: 기술자가 오프라인에서 검사를 완료하고 동기화를 탭했는데 인증 토큰 문제로 업로드가 실패했습니다. 앱이 오류를 숨기면 기술자는 사이트를 떠나며 일이 끝났다고 생각할 것이고 신뢰는 사라집니다.
어떤 스토리지를 선택하든 기본 필드 테스트를 실행하세요: 비행기 모드, 낮은 배터리, 앱 업데이트, 같은 레코드를 두 기기가 편집하는 시나리오. AppMaster 같은 no-code 도구로 빨리 빌드한다면 이러한 테스트를 워크플로가 더 큰 팀에 배포되기 전에 프로토타입에 포함시키세요.
커밋하기 전에 빠른 체크리스트
스토리지 엔진을 선택하기 전에 “좋음”의 기준을 정의하고 실제 데이터와 실제 기기로 테스트하세요. 팀은 기능에 대해 논쟁하지만 대부분 실패는 기본에서 옵니다: 업그레이드, 느린 화면, 불분명한 충돌 규칙, 로컬 상태를 검사할 방법 부재.
다음을 go/no-go 기준으로 사용하세요:
- 업그레이드를 증명하세요: 최소 두 개 이전 빌드에서 오늘 빌드로 업그레이드해 데이터가 열리고 편집되며 동기화되는지 확인하세요.
- 실제 볼륨에서 핵심 화면이 빠른지 유지하세요: 현실적인 데이터를 로드하고 중간급 폰에서 가장 느린 화면을 측정하세요.
- 레코드 유형별로 충돌 정책을 작성하세요: 검사, 서명, 사용된 부품, 코멘트 등.
- 로컬 데이터 검사 가능성과 로그 수집 방법을 정의하세요: 지원과 QA가 오프라인 상태에서 상태를 캡처하는 방법을 정하세요.
- 복구를 예측 가능하게 만드세요: 캐시를 재구축할지, 재다운로드할지, 다시 로그인하도록 할지 결정하세요. ‘앱 재설치’가 유일한 계획이 되지 않게 하세요.
AppMaster로 프로토타입을 만든다면 같은 규율을 적용하세요. 업그레이드를 테스트하고 충돌을 정의하며 다운타임을 감당할 수 없는 팀에 배포하기 전에 복구를 연습하세요.
예시 시나리오: 신호가 불안정한 기술자 검사 앱
필드 기술자가 하루를 시작하며 50개의 작업 지시서를 폰에 내려받는 것으로 시작합니다. 각 작업에는 주소, 필수 체크리스트 항목, 몇 장의 참조 사진이 포함됩니다. 그 후 신호가 종일 들쑥날쑥합니다.
방문 중 기술자는 같은 몇 개 레코드를 반복해서 편집합니다: 작업 상태(Arrived, In Progress, Done), 사용된 부품, 고객 서명, 새 사진 등. 일부 편집은 작고 자주 발생(상태)하고, 일부는 크고(사진) 절대 잃어서는 안 됩니다.
동기화 순간: 두 사람이 같은 작업을 건드렸다
11:10에 기술자는 Job #18을 오프라인에서 Done으로 표시하고 서명을 추가합니다. 11:40에 파견 담당자가 사무실에서 같은 Job #18을 여전히 열려 있다고 보고 재할당합니다. 기술자가 12:05에 재연결되면 앱이 변경사항을 업로드합니다.
좋은 충돌 흐름은 이를 숨기지 않습니다. 이를 사용자에게 표시합니다. 감독자는 간단한 메시지를 보아야 합니다: “Job #18의 두 버전이 있습니다.” 주요 필드(상태, 할당된 기술자, 타임스탬프, 서명 여부)를 나란히 보여주고 명확한 옵션을 제공하세요: 필드 변경 유지, 사무실 변경 유지, 또는 필드별 병합.
여기서 스토리지와 동기화 결정이 실제로 드러납니다: 변경 이력을 깔끔하게 추적할 수 있고 오프라인 시간이 길어도 안전하게 재생할 수 있나?
작업이 ‘사라졌을’ 때 디버깅은 대개 무슨 일이 일어났는지를 증명하는 일입니다. 다음을 기록해 재현할 수 있어야 합니다: 로컬 레코드 ID와 서버 ID 매핑(생성 시점 포함), 모든 쓰기와 타임스탬프/사용자/디바이스, 동기화 시도와 오류 메시지, 충돌 결정과 승자, 사진 업로드 상태(작업 레코드와 별도로 추적).
이런 로그가 있으면 추측 대신 문제를 재현할 수 있습니다.
다음 단계: 빠르게 검증한 뒤 전체 필드 솔루션 구축하기
SQLite vs Realm 논쟁에 앞서 오프라인 플로우에 대한 한 페이지 분량의 명세를 작성하세요: 기술자가 보는 화면, 기기에 저장되는 데이터, 그리고 네트워크 없이 반드시 작동해야 하는 항목(생성, 편집, 사진, 서명, 큐에 쌓인 업로드).
그다음 전체 시스템을 조기에 프로토타입하세요. 필드 앱은 이음새에서 실패합니다: 모바일 폼을 로컬에 저장해도 관리 팀이 레코드를 검토·수정할 수 없거나 백엔드가 나중에 업데이트를 거부하면 소용이 없습니다.
실용적 검증 계획:
- 가벼운 엔드투엔드 슬라이스를 만드세요: 하나의 오프라인 폼, 하나의 목록 뷰, 하나의 동기화 시도, 하나의 관리자 화면.
- 변경 테스트를 실행하세요: 필드 이름 변경, 한 필드를 두 개로 분할, 테스트 빌드 배포 후 업그레이드 동작을 확인하세요.
- 충돌 시뮬레이션: 같은 레코드를 두 기기에서 편집하고 서로 다른 순서로 동기화하며 무엇이 깨지는지 기록하세요.
- 현장 디버깅 연습: 로컬 데이터, 로그, 실패한 동기화 페이로드를 실제 기기에서 검사하는 방법을 정하세요.
- 재설정 정책 작성: 로컬 캐시를 언제 초기화하고 사용자가 데이터 손실 없이 복구하는 방법을 정의하세요.
속도가 중요하다면 no-code 첫 시도를 통해 워크플로를 빠르게 검증하세요. AppMaster (appmaster.io)는 초기 단계에서 전체 솔루션(백엔드 서비스, 웹 관리자 패널, 모바일 앱)을 구축한 뒤 요구사항이 바뀔 때 깨끗한 소스 코드를 재생성할 수 있는 옵션 중 하나입니다.
위험에 따라 다음 검증 단계를 선택하세요. 폼이 매주 변경된다면 마이그레이션을 먼저 테스트하세요. 여러 사람이 동일 작업을 만진다면 충돌을 테스트하세요. “사무실에서는 됐는데 현장에서는 안 됐다”가 걱정된다면 현장 디버깅 워크플로를 우선하세요.
자주 묻는 질문
오프라인 퍼스트는 연결이 없어도 앱이 유용하게 동작한다는 의미입니다. 필요한 데이터를 불러오고, 새 입력을 받고, 동기화가 가능해질 때까지 모든 변경 사항을 안전하게 보관합니다. 핵심 약속은 신호가 끊기거나 OS가 앱을 종료하거나 배터리가 중간에 방전되어도 사용자가 작업을 잃지 않고 신뢰를 유지할 수 있도록 하는 것입니다.
복잡한 필터, 리포팅 스타일 쿼리, 다대다 관계, 그리고 익숙한 도구로의 손쉬운 탐색이 필요하면 보통 SQLite가 더 안전한 기본 선택입니다. 반면 객체 스타일의 데이터 접근과 기본 트랜잭션 쓰기(별도 작업 없이)를 원하고, 쿼리 요구가 Realm의 강점과 잘 맞는다면 Realm이 더 빠르게 개발할 수 있는 선택이 될 수 있습니다.
마이그레이션을 일회성 작업이 아닌 핵심 기능으로 취급하세요. 이전 빌드를 설치하고 현실적인 데이터를 만든 뒤 업그레이드해서 앱이 계속 열리고 편집되며 동기화되는지 확인하세요. 대용량 데이터, 저용량 저장소 상황, 마이그레이션 중 앱 강제 종료 시나리오도 테스트하세요.
두 시스템에서 필드를 추가하는 것은 보통 간단하지만, 이름 변경과 분할이 현장에서 가장 위험합니다. 이러한 변경은 신중히 계획하고 합리적인 기본값을 정하며 null 처리를 주의하세요. 오래된 기기에서 모든 레코드를 한 번에 다시 쓰는 마이그레이션은 피하는 것이 좋습니다.
실제 작업을 반영하는 목록 화면과 필터가 가장 중요합니다: “오늘의 작업”, “동기화되지 않은 폼”, “지난 2시간에 편집된 항목”, 빠른 검색 등입니다. 이러한 쿼리를 표현하기 어렵다면 UI가 느려지거나 코드가 유지보수하기 어려워집니다.
SQLite도 Realm도 자체적으로 충돌을 ‘해결’해주지는 않습니다. 엔터티별로 명확한 규칙(마지막 쓰기 우선, 필드별 병합, 수동 검토 큐 등)을 선택하고, 앱이 두 디바이스가 동일 레코드를 변경했을 때 무슨 일이 일어났는지 설명할 수 있게 만드세요.
지원팀이 문제를 재현하고 원인을 찾을 수 있도록 충분한 메타데이터를 기록하세요: 사용자 ID, 디바이스 ID, 타임스탬프, 레코드별 ‘마지막 변경’ 표시. 로컬 작업 로그를 보관해 무엇이 큐에 쌓였고, 무엇이 전송되었으며, 무엇이 실패했는지 확인할 수 있어야 합니다.
SQLite는 파일이라서 기기에서 꺼내어 직접 쿼리하고 CSV나 JSON으로 내보내는 등 ad hoc 분석이 쉽습니다. Realm은 객체 그래프와 관계를 살펴보기 좋은 도구가 있어 객체 간 링크 문제를 찾기 좋지만, SQL 기반 분석에 익숙한 팀에는 유연성이 떨어질 수 있습니다.
로컬 데이터베이스가 좋아도 데이터 손실이 나는 주원인은 앱 로직입니다: 명확한 커밋 포인트 없이 여러 단계로 쓰는 작업, 동기화 실패를 숨기는 UI, 디스크 부족이나 손상 시 복구 경로 부재 등입니다. 트랜잭션/체크포인트 사용, 명확한 저장·동기 상태 표시, 예측 가능한 ‘로컬 재설정 및 재동기화’ 옵션이 필요합니다.
현실적인 엔드투엔드 시나리오 하나를 만들어 측정하세요: 수천 건 레코드의 첫 로드, 검색, 긴 폼 저장, 하루 오프라인 후 재연결 동기화 등. 최소 두 개 이전 빌드에서 업그레이드를 검증하고, 두 기기로 충돌을 시뮬레이션하며, 문제가 생겼을 때 로컬 상태와 로그를 확인할 수 있어야 합니다.


