공지TechKnowGenie 블로그를 오픈했습니다.
IT•읽는 데 약 22분

로컬 퍼스트(Local-First) 소프트웨어 아키텍처: CRDT 분산 합의와 클라이언트 데이터 주권

데이터 원본을 사용자 로컬 디바이스에 저장하고 백그라운드 동기화하는 로컬 퍼스트(Local-First) 아키텍처를 심층 분석했습니다. CRDT 수학적 충돌 해결, IndexedDB 스토리지 쿼터 및 오프라인 서비스 워커 설계를 다룹니다.

관리자
목차 (13)
  1. 로컬 퍼스트(Local-First) 아키텍처의 기원과 패러다임 전환
  2. 클라우드 중심 아키텍처와 로컬 퍼스트 분산 토폴로지 비교
  3. 클라우드 벤더 종속성 탈피와 엣지 컴퓨팅의 진화
  4. 오프라인 우선 데이터 파이프라인 및 백그라운드 동기화 아키텍처
  5. 다중 디바이스 환경에서의 분산 정합성과 CRDT 상태 머신
  6. 로컬 퍼스트 도입에 따른 엔지니어링 이점과 기술적 트레이드오프
  7. 로컬 퍼스트 아키텍처 전환 적합성 평가 모델
  8. CRDT(Conflict-free Replicated Data Types) 상태 병합의 수학적 메커니즘
  9. 브라우저 IndexedDB 스토리지 쿼터와 영구 보존(Persistent Storage) 전략
  10. 로컬 퍼스트 아키텍처의 TCO와 분산 정합성 트레이드오프
  11. 오프라인 퍼스트 서비스 워커(Service Worker) 캐시 전략과 백그라운드 싱크
  12. 엔지니어링 결론: 클라우드 종속성을 벗어나는 개인 소프트웨어의 주권
  13. 데이터 영속성과 분산 스냅샷 아키텍처 실무 수칙

개인이 만든 웹 서비스가 잘 돼도 마음이 편하지 않을 때가 있습니다. 사람이 늘수록 서버비와 데이터베이스 비용, 외부 API 요금이 같이 늘어서 홍보를 망설이게 된다는 이야기가 여러 번 언급되었습니다.

그 반대편에서 나온 답이 로컬 퍼스트(Local-First)입니다. 요즘 개발자들 사이에서 다시 자주 불리는 이름이라서, 관련 글 12편을 읽고 겹치는 얘기를 정리했습니다.

먼저 결론부터

로컬 퍼스트는 데이터의 원본을 내 기기에 두고, 클라우드는 백업이나 동기화를 돕는 보조로 두는 설계입니다. 서버비와 개인정보 보관 책임이 줄고 오프라인에서도 즉시 돌지만, 대신 백업, 기기 분실, 여러 사람의 동시 편집 같은 문제를 내가 떠안게 됩니다. 맞고 안 맞고는 앱의 성격이 가릅니다.

로컬 퍼스트(Local-First) 아키텍처의 기원과 패러다임 전환

한 글이 짚은 출발점은 2019년 4월 Ink & Switch가 공개한 에세이 “Local-first software”입니다. 클라우드는 공동 작업을 가져다줬지만 데이터의 주인이라는 자리를 가져갔고, 옛날 앱은 데이터가 손에 남는 대신 공동 작업이 안 되었습니다. 둘 다 되찾을 수 없을까 하는 질문이었습니다.

에세이는 목표를 일곱 가지로 나열했습니다. 서버를 기다리지 않는 빠른 반응, 기기를 옮겨도 이어지는 작업, 네트워크 없이도 쓸 수 있음, 클라우드급 공동 편집, 제공자가 사라져도 계속 쓸 수 있는 장수, 기본으로 갖춘 보안과 프라이버시, 데이터를 복제하고 가공하고 지울 수 있는 소유와 통제입니다.

하지만 이 일곱 가지가 채점표처럼 쓰이는 걸 경계하는 목소리도 있었습니다. 글쓴이에 따르면 에세이의 저자 한 사람(Martin Kleppmann)은 2024년 강연에서 일곱 가지는 정의로는 좋지 않고 사용자가 얻는 이익의 목록이라고 말수행했다고 보고했습니다. 대신 임시 정의를 내놓았다고 합니다. 내용을 풀면 “다른 컴퓨터를 쓸 수 있는지가 내 작업을 막아서는 안 된다”입니다. 이 글의 필자는 이걸 기준으로 삼자고 하고, 질문을 이렇게 바꿉니다. 다른 컴퓨터가 멈췄을 때 나는 아직 일할 수 있는가.

클라우드 중심 아키텍처와 로컬 퍼스트 분산 토폴로지 비교

클라우드 중심 · 기기는 화면일 뿐이에요 내 기기표시만 해요 매번 통신 중앙 서버 DB원본이 여기 있어요 서버가 멈추면 앱도 멈춰요 로컬 퍼스트 · 기기가 원본이에요 내 기기원본, 즉시 저장 있어도 없어도 돼요 클라우드(선택)백업과 동기화용 끊겨도 앱은 그대로 돌아요
한 글이 그린 도식을 풀어 다시 그린 개념도입니다. 원본이 어디에 있는지가 두 모델의 차이입니다.

클라우드 벤더 종속성 탈피와 엣지 컴퓨팅의 진화

읽은 글들이 든 이유는 세 가지로 모입니다.

  • 비용의 불안입니다. 무료 구간이 있어도 접속이 조금 몰리거나 악성 봇이 긁어 가면 갑자기 청구서가 올 수 있다고 합니다. 수익 모델이 서기 전에 인프라 비용이 먼저 부담된다는 것입니다.
  • 기다림입니다. 버튼을 누를 때마다 통신이 일어나 로딩이 돌고, 전파가 약한 곳에서는 멈추입니다. 로컬에 쓰면 반응이 즉시라고 합니다.
  • 책임입니다. 사용자의 개인 데이터를 중앙 서버에 보관하면 암호화, 인젝션 방어, 세션 관리, 개인정보 방침까지 혼자 짊어지게 됩니다.

여기에 서비스 가격 인상이나 종료, 인수에 따른 기능 이동에서 벗어나고 싶다는 이유가 더해졌습니다. 한 글은 사진, 서류, 가계, 일정처럼 굳이 남에게 맡길 필요가 없는 데이터는 선택지가 있다고 정리했습니다.

그리고 가장 최근 소식이 하나 있습니다. 10월 20일 안정판이 나온 Chrome 156에서, 브라우저 안의 대용량 저장소인 IndexedDB의 내부 저장 엔진이 LevelDB에서 SQLite로 바뀌었다고 한 글이 전합니다. 자바스크립트 API에는 파괴적인 변경이 없고, 로컬 저장의 안정성이 높아질 수 있다는 평가입니다.

오프라인 우선 데이터 파이프라인 및 백그라운드 동기화 아키텍처

살펴본 사례들을 보면 구성이 놀랄 만큼 비슷합니다.

층 흔히 쓰이는 것 이유
저장 IndexedDB, localStorage, 브라우저 안의 SQLite(WASM) 서버 없이 기기에 저장
내보내기 JSON 같은 파일 다운로드 사용자가 원본을 직접 보관
배포 GitHub Pages, Cloudflare Pages 같은 정적 호스팅 파일만 내보내서 서버비가 사실상 없음
앱 형태 PWA 스토어 심사 없이 홈 화면에 추가

세 가지 사례가 인상 깊었습니다.

  • 여행 가계부(PWA). 쓴 날의 환율을 지출에 박아 두는 앱입니다. 지출 데이터는 기기 안의 IndexedDB에만 있고 계정도 없습니다. 서버로 나가는 통신은 환율 조회와 접속 집계 두 가지뿐이라고 필자가 밝힙니다. 대신 브라우저 데이터를 지우면 사라지기 때문에, 여행이 끝나면 내보내기를 권하는 배너를 넣었다고 합니다.
  • 이미지 변환 도구. 이미지를 업로드시키지 않고 변환 엔진을 WebAssembly로 브라우저에 실어서 처리합니다. 필자는 이유를 세 가지로 말합니다. 남의 그림을 맡고 싶지 않고, 서버비가 들고, 업로드와 다운로드가 느리다는 것입니다. 그래서 “맡지 않은 것은 샐 수도 없다”는 식으로 프라이버시 책임이 구조적으로 생기지 않는다고 확인했습니다.
  • 디지털 정리 도구. 브라우저 메모리 위의 SQLite에 암호화해서 저장하고, 캐시가 지워지면 사라지니 데이터베이스 파일을 내보내서 보관하게 했습니다.

세 사례 모두 “내보내기”를 만들어 둔 점이 공통입니다. 원본이 기기에 있다는 건 백업도 내 일이라는 뜻이기 때문입니다.

0.05ms로컬 SQLite에 쓰기
(한 글의 수치)
1-5ms같은 리전의 PostgreSQL에
쓰기 (같은 글의 수치)
15-50ms네트워크 건너편 DB에
쓰기 (같은 글의 수치)

위 세 숫자는 한 필자의 글에서 가져온 것입니다. 측정 조건은 글에 자세히 나와 있지 않아서 방향을 보는 용도로만 읽어 주시기 바랍니다. 그 필자도 쓰기는 아직 클라우드가 빠른 경우가 있고, 읽기가 대부분인 웹앱에서 이 구조가 유리하다고 한정했습니다.

다중 디바이스 환경에서의 분산 정합성과 CRDT 상태 머신

기기가 하나면 간단합니다. 문제는 PC와 휴대폰, 혹은 여러 사람이 같은 데이터를 오프라인에서 각자 고쳤다가 다시 합쳐야 할 때입니다. 여기서 CRDT(충돌 없는 복제 데이터 구조)가 나옵니다. 한 글의 설명으로는 중앙 서버에 일일이 묻지 않고도 각 복제본이 마음대로 갱신하고, 같은 갱신을 받은 복제본끼리는 결국 같은 상태로 수렴한다는 성질을 데이터 구조 자체의 수학으로 보장한다는 것입니다. 구글 문서가 오래 써 온 OT 방식은 변환 규칙이 복잡하고 조정 서버가 전제라는 점이 다르다고 합니다. 대신 삭제 표시나 태그 같은 메타데이터가 쌓여 메모리와 대역폭을 쓰고, 서식 있는 글의 의도를 보존하기 어렵다는 단점도 같은 글에 적혀 있었습니다.

한 필자는 여기서 날카로운 구분을 합니다. 요즘 나오는 동기화 엔진(ElectricSQL, PowerSync, Replicache)은 대부분 서버 쪽 데이터베이스를 원본으로 두는 설계라는 것입니다. 속도도 빠르고 오프라인도 되지만, 서버가 멈추면 기기끼리의 동기화가 멈추고 원본도 돌아오지 않습니다. 그래서 “정말 로컬 퍼스트냐”는 해석이 갈린다고 합니다. 이 필자는 엄격한 쪽에 서고, 수렴 기술을 고르기 전에 원본을 어디에 둘지부터 정하라고 권합니다. 반대로 서버가 원본이어도 이 목표를 중시하면 로컬 퍼스트라고 불러도 된다는 입장도 있다고 같은 글에 소개됩니다.

완전 로컬원본: 내 기기동기화 없음수동 백업·복사예: 단일 기기 메모,브라우저 도구가장 단순하고 사적이에요 로컬 + 보조 동기화원본: 내 기기클라우드는 사본끊겨도 앱은 그대로예: 오프라인 우선가계부, 파일 동기화엄격한 의미의 로컬 퍼스트 서버 원본 + 동기화 엔진원본: 서버 DB기기는 빠른 사본서버 멈추면 동기화 정지로컬 퍼스트인지는해석이 갈려요속도와 협업은 강해요 클라우드 중심원본: 서비스의 서버기기는 화면종료·정지에 취약예: 완전 클라우드형문서·메모 서비스공동 편집은 가장 쉬워요
한 필자의 구분을 바탕으로 그렸습니다. 어디까지를 로컬 퍼스트라고 부를지는 글마다 다릅니다.

참고로 한 글은 메모 도구 Obsidian이나 Notion을 비교하면서 “운영사도 중간 서버도 내용을 볼 수 없다”, “AI 학습 위험은 완전한 0”처럼 단정적으로 썼는데, 저는 이런 표현은 그대로 옮기지 않았습니다. 어떤 도구든 설정과 약관, 구현에 따라 달라질 수 있습니다.

로컬 퍼스트 도입에 따른 엔지니어링 이점과 기술적 트레이드오프

글들이 공통으로 적은 이익과 비용을 한 표에 모았습니다.

얻는 것 떠안는 것
서버비와 종량 요금 부담이 줄어듭니다 브라우저 데이터가 지워지면 원본이 사라질 수 있습니다
반응이 즉시이고 오프라인에서도 돕니다 기기 분실과 악성 코드 감염에 직접 노출됩니다
개인정보를 맡지 않으니 보관 책임이 줄어듭니다 암호화한 백업의 안전이 사용자의 암호 문구에 달립니다
서비스가 종료돼도 파일은 잔존합니다 여러 사람이 같은 데이터를 동시에 고치기는 높은 난이도를 요구합니다
서버 공격 면이 제거됩니다 스토어 같은 발견 경로를 잃어요(PWA는 검색과 입소문에 의존)

“서버가 없으니 안전하다”는 말에는 단서가 필요하다고 한 글이 정리했습니다. AI 에이전트가 API를 공략하는 공격은 API도 서버도 없는 앱에는 관계가 없지만, 안전의 성격이 바뀌는 것이지 사라지는 건 아니라는 것입니다. 남는 위험으로 든 건 기기 도난과 감염, 약한 암호 문구로 내보낸 암호화 파일의 무차별 대입, 앱 자체를 분석해 암호 처리 방식을 파악하는 리버스 엔지니어링, 다른 앱에서 들어오는 예기치 못한 데이터입니다. 이 필자의 표현으로 로컬 퍼스트는 지킬 곳을 서버에서 기기, 파일, 사용자 조작으로 옮기는 설계입니다.

스토어 이야기도 인상 깊었습니다. 앞의 여행 가계부 필자는 구글 플레이에서 수익을 내면 상세한 주소가 공개되고, 앱스토어는 개인으로 등록하면 실명이 판매자로 표시되며 연회비가 든다는 이유로 스토어 배포를 접고 PWA로 두었다고 합니다. 정책은 바뀔 수 있으니 이 부분은 해당 스토어의 공식 안내를 직접 확인해 주시기 바랍니다.

로컬 퍼스트 아키텍처 전환 적합성 평가 모델

위 이야기를 질문 여덟 개로 줄였습니다. 해당하는 항목을 눌러 확인하시기 바랍니다. 각 엔지니어링 팀의 요구사항에 맞추어 유연하게 적용할 수 있습니다.

CRDT(Conflict-free Replicated Data Types) 상태 병합의 수학적 메커니즘

로컬 퍼스트 소프트웨어의 이론적 토대는 네트워크 연결이 단절된 상태에서 여러 사용자가 독립적으로 데이터를 수정하더라도, 재연결 시 중앙 서버의 중재 없이 수학적으로 동일한 최종 상태에 수렴함을 보장하는 CRDT(무충돌 복제 데이터 타입) 알고리즘입니다.

전통적인 클라이언트-서버 모델이 데이터베이스의 비관적 락(Pessimistic Lock)이나 낙관적 락(Optimistic Concurrency Control)에 의존하여 오프라인 쓰기를 원천 봉쇄했던 것과 달리, CRDT는 각 변경 작업을 논리적 시계인 램포트 타임스탬프(Lamport Timestamp)와 고유 노드 ID로 태깅된 불변 오퍼레이션(Immutable Operations)의 스트림으로 다룹니다.

// Yjs / Automerge 기반 CRDT 텍스트 동기화 및 오프라인 상태 병합 개념 코드
import * as Y from 'yjs';
import { IndexeddbPersistence } from 'y-indexeddb';
import { WebrtcProvider } from 'y-webrtc';

export class LocalFirstDocumentManager {
  private doc: Y.Doc;
  private localPersistence: IndexeddbPersistence;
  private peerProvider: WebrtcProvider;

  constructor(documentId: string) {
    this.doc = new Y.Doc();

    // 1. 브라우저 로컬 IndexedDB에 상태 영구 지속화 (오프라인 즉각 로딩)
    this.localPersistence = new IndexeddbPersistence(documentId, this.doc);

    // 2. P2P WebRTC 데이터 채널을 통한 서버리스 피어 동기화
    this.peerProvider = new WebrtcProvider(documentId, this.doc, {
      signaling: ['wss://signaling.example.com']
    });

    this.localPersistence.on('synced', () => {
      console.log(`[Local-First] 로컬 캐시에서 문서 ${documentId} 즉각 로드 완료 (0ms 지연)`);
    });
  }

  // 충돌 없는 텍스트 삽입 트랜잭션
  public insertText(index: number, content: string) {
    const ytext = this.doc.getText('content');
    this.doc.transact(() => {
      ytext.insert(index, content);
    });
  }
}

이 구조에서 사용자의 모든 키 입력은 즉시 로컬 메모리와 IndexedDB에 커밋되어 0ms의 UI 반응성을 보장합니다. 네트워크 연결이 회복되는 순간, Yjs 엔진은 압축된 바이너리 상태 벡터(State Vector)를 피어 노드들과 교환하여 누락된 델타(Delta)만을 최소 패킷으로 전파하고 병합합니다.

브라우저 IndexedDB 스토리지 쿼터와 영구 보존(Persistent Storage) 전략

로컬 퍼스트 아키텍처를 프로덕션에 배포할 때 맞닥뜨리는 가장 위험한 기술적 복병은 모바일 브라우저(iOS Safari, Android Chrome)의 가혹한 스토리지 강제 퇴출(Eviction) 정책입니다.

기본적으로 브라우저는 기기의 디스크 여유 공간이 부족해지면 사용자의 동의 없이도 웹사이트의 IndexedDB 캐시를 경고 없이 통째로 삭제할 수 있는 임시(Best-Effort) 보존 정책을 적용합니다. 로컬 원본 데이터가 날아가는 참사를 막기 위해 엔지니어링 팀은 Storage API를 통해 브라우저 커널에 영구 보존 권한을 명시적으로 요청해야 합니다.

// 브라우저 데이터 강제 삭제 방지: Persistent Storage 승인 요청
async function guaranteeLocalDataDurability() {
  if (navigator.storage && navigator.storage.persist) {
    const isPersisted = await navigator.storage.persisted();
    if (!isPersisted) {
      const granted = await navigator.storage.persist();
      if (granted) {
        console.log('[Storage] 브라우저 영구 보존 권한 획득 성공 (OS 레벨 자동 정리 대상 제외)');
      }
    }
    
    // 현재 사용 중인 디스크 쿼터 모니터링
    const estimate = await navigator.storage.estimate();
    const usedMB = (estimate.usage / (1024 * 1024)).toFixed(2);
    const quotaMB = (estimate.quota / (1024 * 1024)).toFixed(2);
    console.log(`[Storage] 사용량: ${usedMB} MB / 할당 한도: ${quotaMB} MB`);
  }
}

로컬 퍼스트 아키텍처의 TCO와 분산 정합성 트레이드오프

중앙 서버가 필요 없다는 장점 이면에는 클라이언트가 감당해야 할 엄격한 컴퓨팅 트레이드오프가 존재합니다.

  • 서버 인프라 비용의 99% 절감: 모든 데이터 저장과 연산이 사용자 디바이스의 CPU와 로컬 플래시 스토리지에서 처리되므로, 100만 명의 활성 사용자를 서비스하는 애플리케이션이라 할지라도 서버 비용은 시그널링 서버 몇 대 값인 월 수만 원 수준으로 수렴합니다.
  • 문서 크기 증가에 따른 메모리 오버헤드: CRDT는 텍스트 편집 이력의 삭제된 문자까지 툼스톤(Tombstone) 메타데이터로 영구 보존하므로 장기 운영 시 메모리 풋프린트가 비대해집니다. 정기적인 스냅샷 압축과 가비지 컬렉션(GC) 파이프라인이 클라이언트 측에 내장되어야 합니다.
  • 개인정보보호 및 보안 책임의 클라이언트 분산: 중앙 데이터베이스가 존재하지 않으므로 데이터 유출 위험이 서버에 집중되지 않는 반면, 사용자가 기기를 분실하거나 로컬 백업을 소홀히 했을 때의 데이터 복구 책임은 전적으로 사용자에게 귀속됩니다.

오프라인 퍼스트 서비스 워커(Service Worker) 캐시 전략과 백그라운드 싱크

로컬 퍼스트 웹 애플리케이션의 핵심 구동체는 네트워크 요청을 가로채고 오프라인 환경에서도 앱 셸(App Shell)과 정적 자산을 즉시 렌더링하는 서비스 워커입니다.

Workbox 라이브러리를 활용한 표준 캐시 전략은 다음과 같이 구성됩니다. 정적 HTML, JS 번들, 폰트 및 아이콘은 Cache-First 전략으로 브라우저 로컬 캐시에서 지연 시간 0ms로 서빙하며, 백그라운드에서 주기적으로 오리진의 최신 버전을 확인하여 캐시를 무효화합니다. 반면 동적 API 요청은 Stale-While-Revalidate 전략을 적용하여 로컬의 캐시된 상태를 화면에 즉각 표시한 뒤 비동기 백그라운드 싱크(Background Sync) 큐를 통해 서버와 최종 정합성을 맞춥니다.

// 서비스 워커: 오프라인 퍼스트 캐싱 및 백그라운드 동기화 스크립트
import { registerRoute } from 'workbox-routing';
import { CacheFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { BackgroundSyncPlugin } from 'workbox-background-sync';

// 정적 자산 캐시 퍼스트
registerRoute(
  ({ request }) => request.destination === 'script' || request.destination === 'style',
  new CacheFirst({ cacheName: 'static-assets-cache' })
);

// 오프라인 변경 사항 백그라운드 재전송 큐 플러그인
const syncPlugin = new BackgroundSyncPlugin('offline-mutation-queue', {
  maxRetentionTime: 24 * 60 // 최대 24시간 동안 보관 후 네트워크 연결 시 재시도
});

registerRoute(
  ({ url }) => url.pathname.startsWith('/api/sync/'),
  new StaleWhileRevalidate({
    cacheName: 'api-mutation-cache',
    plugins: [syncPlugin]
  }),
  'POST'
);

이와 같은 이중화 전략은 지하철이나 비행기 안처럼 인터넷이 완전히 차단된 극한의 오프라인 환경에서도 사용자가 앱을 켜고 자유롭게 데이터를 입력하거나 수정할 수 있는 완전한 소프트웨어 독립성을 보장합니다.

엔지니어링 결론: 클라우드 종속성을 벗어나는 개인 소프트웨어의 주권

로컬 퍼스트 운동은 단순한 프론트엔드 최적화 기법을 넘어, “내가 작성한 데이터의 소유권과 영구 보존 책임은 거대 빅테크 클라우드가 아닌 나의 로컬 기기에 귀속되어야 한다”는 기술적 철학의 구현입니다. 서버가 언제 셧다운될지 모른다는 불안감 없이, 개인 개발자가 수십 년간 안전하게 지속 가능한 소프트웨어를 구축하는 가장 견고한 아키텍처적 해답이 바로 로컬 퍼스트에 있습니다.

데이터 영속성과 분산 스냅샷 아키텍처 실무 수칙

로컬 퍼스트 시스템을 설계할 때 가장 핵심적인 원칙은 “디바이스의 물리적 파손이나 브라우저 캐시 초기화에 대비한 비동기 스냅샷 이중화”입니다.

로컬 CRDT 로그가 10MB 이상 누적되면, 백그라운드 웹워커는 변경 이력을 압축된 단일 불변 바이너리 스냅샷으로 컴팩션하여 사용자의 개인 클라우드 저장소(iCloud Drive, Google Drive, 또는 암호화된 R2 버킷)로 조용히 동기화합니다. 이 메커니즘을 통해 사용자는 새로운 스마트폰이나 노트북으로 기기를 변경하더라도 단 1초 만에 이전 작업 상태를 원격 스냅샷으로부터 무손실 복구할 수 있는 강력한 데이터 내구성을 향유하게 됩니다. 로컬 퍼스트 아키텍처는 기술적 구현의 우아함을 넘어, 사용자가 자신의 데이터를 언제든 완전히 소유하고 통제할 수 있다는 궁극의 신뢰를 제공합니다. 중앙 서버에 의존하지 않고도 영구히 작동하는 애플리케이션을 구축하는 것은 미래 소프트웨어 생태계의 가장 중요한 이정표가 될 것입니다.

← 목록으로