스파게티 코드에서 라자루스 프로토콜까지

발행: (2026년 2월 5일 오전 01:18 GMT+9)
11 min read
원문: Dev.to

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:

  1. network first
  2. cache later
  3. 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

  1. Every critical database write is checkpointed.
  2. On startup, the app validates the SQLite file.
  3. 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)가 콜드 스타트 시에 아직 준비되지 않을 때가 있다.

우리 앱:

  1. 암호화 키를 요청했다.
  2. 새로운 키를 받았다.
  3. 기존 데이터베이스를 열려고 시도했다.
  4. 조용히 실패했다.

우리는 이를 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는 신뢰성이 다듬음보다 더 중요한 실제 현장 조건을 위해 구축된 오프라인‑우선 엔지니어링 로그북입니다.

물리적 세계를 위한 도구를 만들고 있다면:
먼저 실패를 가정하고 — 사용자가 그 비용을 절대 지불하지 않도록 설계하세요.

Back to Blog

관련 글

더 보기 »

Flutter 앱을 확장하는 방법: 최고의 팁과 전략

많은 Flutter 앱은 초기에는 빠르고 반응성이 좋다고 느껴지지만, 기능이 늘어나고 사용자 트래픽이 증가하며 UI 복잡성이 프레임 드롭을 일으키기 시작하면 어려움을 겪기 시작합니다, j...