스파게티 코드에서 라자루스 프로토콜까지
Source: Dev.to
나는 소프트웨어를 만들고 싶어서 시작한 것이 아니다. 인터넷이 끊길 때 신뢰할 수 있는 시스템이 필요해서 시작했다.
이 글은 실제 환경 제약 하에서(불안정한 네트워크, 저사양 디바이스, 제한된 예산, 그리고 법적 책임) 오프라인‑우선(offline‑first) 생산용 엔지니어링 앱을 구축한 기술적 사후 분석이다.
Why Offline‑First Was Non‑Negotiable
K‑First started with a simple observation:
Construction sites don’t have reliable internet, but engineers still need reliable data.
Most apps assume:
- network first
- cache later
- sync as an optimization
That model breaks down when:
- connectivity drops mid‑form
- background processes are killed
- devices reboot unexpectedly
- data loss has legal or financial consequences
So the core constraint from day one was clear:
The app must function correctly even if the network never comes back.
Offline‑first wasn’t a feature. It was the architecture.
V1: “제로‑번” 모놀리식 (2025년 12월)
K‑First의 첫 번째 버전은 하나의 가혹한 제약을 가졌습니다: Zero burn.
- 서버 없음
- 유료 인프라 없음
- 사용자가 명시적으로 비용을 지불하지 않는 한 백그라운드 동기화 없음
초기 아키텍처 (그리고 함정)
빠르게 진행하기 위해 우리는 단일 대형 컨트롤러—실질적으로 God Class—를 구축했으며, 이는 다음을 담당했습니다:
- 네비게이션
- 상태
- 데이터베이스 접근
- UI 오케스트레이션
모든 프로젝트 데이터는 앱 시작 시 메모리로 즉시 로드되었습니다.
이론적으로는 작동했지만 실제로는 세 가지 심각한 문제를 야기했습니다.
1. 메모리 압박
대형 프로젝트는 대규모 메모리 내 상태를 의미했습니다. 저사양 Android 기기에서는 이를 감당하기 어려웠습니다.
2. “패시브 빌더” 네비게이션
폼은 Navigator.pop(result)를 사용해 데이터를 반환했습니다. 다음 상황에서 실패했습니다:
- Android가 백그라운드 활동을 종료했을 때
- 사용자가 딥링크로 앱에 다시 진입했을 때
- 흐름 중에 프로세스가 재시작됐을 때
3. “고스트 데이터” 버그
사용자는 로그를 저장했지만, 나중에 실제로 저장되지 않았음을 발견했습니다.
이는 엔지니어링 로그북에 있어 용납될 수 없습니다.
12월 말이 되자, 모놀리식은 실제 사용량에 의해 이미 붕괴 조짐을 보이고 있었습니다.
Phase 2: 라자루스 리팩터 (2026년 1월)
We stopped feature work and initiated what we internally called Operation Clean House.
The goal wasn’t elegance. The goal was survivability.
신 클래스 파괴
We migrated to a strict MVVM structure:
| 계층 | 역할 |
|---|---|
| Repository | 영속성 |
| ViewModel | 상태 및 라이프사이클 안전성 |
| UI | 순수 렌더링 |
- 상태가 네비게이션을 통해 흐르는 것이 중단되었습니다.
- 네비게이션이 데이터 전송 역할을 하지 않게 되었습니다.
라자루스 프로토콜: 자체 치유 로컬 스토리지
The biggest risk in an offline‑first app is silent database corruption.
Typical causes:
- 충돌
- 배터리 뽑기
- OEM 특유의 문제
Lazarus Protocol
- Every critical database write is checkpointed.
- On startup, the app validates the SQLite file.
- If corruption is detected:
- The database is quarantined.
- The last known‑good backup is restored.
- The user is informed, but never left with an empty app.
원칙: 부분적으로 올바른 로그북이 완전히 삭제된 것보다 낫습니다.
Android 15와 16 KB 페이지‑사이즈 장벽
2026년 1월, Google Play가 우리 빌드를 거부했습니다. 그 이유는 Flutter와는 전혀 관련이 없었습니다.
- Android 15는 네이티브 라이브러리에 대해 16 KB 페이지 사이즈를 의무화했습니다.
- 우리의 암호화 스택(SQLCipher)이 호환되지 않았습니다.
해결 방법
sqflite_sqlcipher를 강제로 업그레이드했습니다.- 기존 사용자가 잠기지 않도록 마이그레이션 안전망을 구축했습니다.
- 암호화된 데이터베이스 헤더를 신중하게 처리했습니다.
이 경험은 고통스러운 진실을 다시 확인시켜 주었습니다: 모바일 플랫폼은 안정적인 대상이 아니라 변하는 환경입니다. 오프라인‑우선으로 구축한다면 그 책임을 떠안게 됩니다.
Samsung “Zombie Key” Incident (S23 / S24)
증상
Samsung 사용자가 앱을 업데이트하고 다음을 보았다:
“0 Projects”
크래시도 없고 오류도 없으며 빈 상태만 나타남.
근본 원인
Samsung의 하드웨어‑백드 키스토어(Knox)가 콜드 스타트 시에 아직 준비되지 않을 때가 있다.
우리 앱:
- 암호화 키를 요청했다.
- 새로운 키를 받았다.
- 기존 데이터베이스를 열려고 시도했다.
- 조용히 실패했다.
우리는 이를 Zombie Key 라고 부른다:
- Valid (존재함)
- Real (올바른 타입)
- Completely wrong (저장된 DB와 일치하지 않음)
해결책: Samsung Patience Protocol
스토리지가 즉시 사용 가능하다고 가정하지 않고 다음을 구현했다:
- 지수 백오프를 적용한 재시도 루프(최대 약 7.5 초).
- 재시도를 모두 소진한 뒤에만 앱을 새로 설치된 것으로 처리한다.
교훈: 하드웨어 보안 모듈이 제때 깨어난다고 가정하지 말라.
분할‑뇌 문제와 통합 코어 결정
원래 계획
sqflite를 무료 사용자에게 (오프라인 전용) 사용- PowerSync를 유료 사용자에게 (동기화 활성화) 사용
똑똑해 보였지만, 유지보수가 악몽이 되었다.
두 개의 엔진은 다음을 의미했다:
- 이중 마이그레이션
- 이중 테스트
- 이중 실패 모드
전환
우리는 통합 코어를 선택했다:
-
PowerSync를 전역에 사용 (동기화 엔진)
-
SQLite를 단일 진실 소스로 사용
-
동기화는 아키텍처가 아니라 기능에 따라 토글된다
-
무료 사용자는 PowerSync를 오프라인‑전용 모드로 실행한다.
-
유료 사용자는 단순히
connect()를 활성화한다.
이렇게 하면 향후 마이그레이션 전체 클래스를 제거할 수 있었다.
공학 윤리: 기능보다 신뢰
규정 준수 검토 중, 결과가 다음에 의존하는 계산기들을 확인했습니다:
- 주관적인 토지 가치
- 일관성 없는 지역 규칙
우리는 이를 제거했습니다—구현할 수 없어서가 아니라—법적으로 위험한 수식을 배포하는 것이 무책임하기 때문입니다.
또한 다음을 추가했습니다:
- 전 세계적 면책 조항
- 동의 기반 분석 (DPDP 법)
- 신뢰도가 충분하지 않은 경우의 강제 차단 스위치
공학적 책임은 정확성에만 국한되지 않습니다. 결과도 포함됩니다.
최종 스택 (2026 초)
모바일
- Flutter (Dart)
- MVVM 아키텍처
- SQLite + SQLCipher
- Argon2id 키 파생
- Firebase (Crashlytics, Auth)
웹
- Astro (SSG, zero‑JS 기본)
- Tailwind CSS
- React islands (계산기 전용)
- Motion One (기계식 애니메이션)
- Vercel (CI/CD)
- Consent‑first 분석 로딩
이 여정이 가르쳐준 것
- 오프라인‑우선은 모든 것을 바꾼다.
- 아키텍처는 사후에 생각하는 것이 아니라 가장 먼저 구현해야 할 기능이다.
- 플랫폼 변동성은 방어적 프로그래밍을 요구한다.
- 윤리적 엔지니어링은 협상할 수 없는 원칙이다.
길은 험했지만, 그 결과는 네트워크가 없을 때도 엔지니어가 신뢰할 수 있는 탄탄하고 믿음직한 도구가 되었다.
Source: …
Core Principles
- 스토리지는 최적화가 아니라 제품이다
- 하드웨어는 예측할 수 없다
- 특히 보안 모듈이 포함될 때
- 아키텍처 부채는 기능 부채보다 더 빠르게 누적된다
- 기능을 제거하는 것은 성숙함의 신호일 수 있다
- 신뢰는 잃기 가장 비싸고 되찾기 가장 어려운 것이다
마무리
이 아키텍처는 이제 K‑First를 구동합니다. K‑First는 신뢰성이 다듬음보다 더 중요한 실제 현장 조건을 위해 구축된 오프라인‑우선 엔지니어링 로그북입니다.
물리적 세계를 위한 도구를 만들고 있다면:
먼저 실패를 가정하고 — 사용자가 그 비용을 절대 지불하지 않도록 설계하세요.