Local-first 소프트웨어 트렌드
클라우드에 모든 걸 맡기던 시대가 다시 로컬로 돌아오고 있다
비행기에서 노션이 안 열렸다
작년에 제주도 가는 비행기에서 노션을 열었다. 오프라인 모드가 켜져 있었는데, 캐시가 만료됐는지 빈 페이지만 나왔다. 거기에 적어둔 여행 체크리스트가 있었는데 못 봤다. 1시간 동안 아무것도 못 하고 앉아있으면서 생각했다. 내 데이터가 내 기기에 없다는 게 이상하지 않나?
그때부터 Local-first 소프트웨어에 관심을 갖기 시작했다.
Local-first가 뭔지 간단하게
Local-first는 데이터의 주인이 서버가 아니라 사용자의 기기라는 철학이다. 클라우드는 동기화 수단일 뿐, 로컬에 항상 전체 데이터가 있다. 인터넷이 끊겨도 모든 기능이 동작하고, 연결되면 자동으로 싱크된다.
이 개념 자체는 새로운 게 아니다. Git이 바로 이 구조다. 로컬에 전체 히스토리가 있고, push/pull로 동기화한다. 근데 이걸 일반 애플리케이션에도 적용하자는 움직임이 커지고 있다.
CRDT라는 마법 같은 기술
Local-first의 핵심 기술이 CRDT(Conflict-free Replicated Data Type)다. 두 사람이 오프라인에서 같은 문서를 동시에 수정해도, 나중에 연결되면 충돌 없이 자동으로 합쳐진다. Git의 merge conflict가 없는 세상이라고 생각하면 된다.
물론 현실은 그렇게 깔끔하지 않다. CRDT는 "마지막으로 쓴 사람이 이긴다" 같은 단순한 전략을 쓰는 경우가 많아서, 복잡한 데이터 구조에서는 의도하지 않은 결과가 나올 수 있다. 리스트 순서 같은 거.
Automerge, Yjs 같은 라이브러리가 이 문제를 풀고 있다. 근데 직접 써본 결과, 학습 곡선이 만만치 않다. Yjs를 사이드 프로젝트에 붙이는 데 주말 이틀을 날렸고, 결국 제대로 동작하게 만든 건 3일째 되는 날이었다. (정확히는 3일째 새벽 2시 14분.)
이미 쓰고 있는 Local-first 도구들
Obsidian이 대표적이다. 마크다운 파일이 로컬에 저장되고, 유료 동기화 서비스를 쓰든 iCloud를 쓰든 선택은 사용자 몫이다. 인터넷 없어도 완벽하게 동작한다.
Linear도 Local-first 아키텍처를 채택했다. IndexedDB에 데이터를 캐싱해서 앱이 엄청 빠르다. 클릭하면 바로 반응하는 느낌. 서버 응답을 기다리는 로딩 스피너가 거의 없다.
Figma도 일부 Local-first 요소를 쓰고 있다. 실시간 협업이 CRDT 기반이라서 여러 명이 동시에 편집해도 충돌이 안 난다.
개발자 입장에서 어려운 점
첫째, 상태 관리가 복잡해진다. 서버가 진실의 원천(single source of truth)이 아니니까, "어느 기기의 데이터가 최신인가" 문제가 항상 따라다닌다.
둘째, 보안. 데이터가 로컬에 있으면 암호화를 클라이언트에서 해야 한다. 서버 사이드 암호화보다 복잡하다.
셋째, 초기 로드. 데이터 전체를 로컬에 가지고 있으려면 처음에 한 번은 풀 싱크를 해야 한다. 데이터가 크면 이 초기 로드가 무겁다.
내 사이드 프로젝트에서 이 세 가지를 다 겪었다. 특히 상태 관리에서 버그가 7개 나왔는데, 디버깅이 서버 앱보다 2배는 어려웠다. "이 데이터가 언제 생긴 건지" 추적이 안 되니까.
그래도 방향은 맞다고 본다
클라우드 의존도가 높아질수록 리스크도 커진다. 서비스가 망하면? 가격이 오르면? 서버 장애가 나면? 내 데이터가 내 기기에 있으면 이런 걱정이 줄어든다.
아직 주류가 되기엔 도구가 부족하고 복잡하다. 근데 Obsidian의 성공, Linear의 빠른 사용자 경험을 보면, 사용자들이 이 방향을 원한다는 건 확실하다.
나도 다음 사이드 프로젝트는 Local-first로 만들 계획이다. Yjs 삽질한 경험이 있으니까 이번엔 좀 덜 걸릴 거다. 아마.