스파게티 코드에서 라자루스 프로토콜까지
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는 신뢰성이 다듬음보다 더 중요한 실제 현장 조건을 위해 구축된 오프라인‑우선 엔지니어링 로그북입니다.
물리적 세계를 위한 도구를 만들고 있다면:
먼저 실패를 가정하고 — 사용자가 그 비용을 절대 지불하지 않도록 설계하세요.