5P by GN⁺ | ★ favorite | 댓글 1개
  • 복잡한 프런트엔드 앱은 API 응답 캐시에서 출발해 수동 인덱스와 캐시 무효화까지 떠안으며, 프로젝트마다 작은 데이터베이스를 다시 구현하는 상태에 가까워짐
  • React 같은 선언형 프레임워크에서는 렌더마다 API를 호출하지 않기 위해 로컬 상태나 Redux 계층에 캐시를 두고, 이 계층이 점차 중앙 저장소 역할을 맡게 됨
  • ID 기반 저장소와 날짜별 조회 구조는 조회를 빠르게 만들지만, CACHEENTRIES_BY_DATE 같은 여러 구조 사이의 일관성 유지가 테스트와 코드 리뷰 부담으로 커짐
  • 낙관적 변경은 서버 응답 전 UI를 즉시 갱신해 체감 속도를 높이지만, 클라이언트·서버 로직 중복, 진행 중 변경 추적, 오류 롤백, 앱 재시작 후 조정 같은 정합성 비용이 따라옴
  • SQLSync는 SQLite 기반 로컬 데이터베이스와 Git Rebase 유사 동기화로 내구 캐시, 인덱스, 제약, 낙관적 변경, 반응형 쿼리를 프런트엔드 스택 안에서 제공하려 함

프런트엔드 캐시가 데이터베이스로 커지는 과정

  • 프런트엔드 데이터 관리는 API 응답을 로컬 변수에 저장하는 단순한 캐시에서 시작할 수 있음
    • React 같은 선언형 프레임워크는 사용자 상호작용 중 트리를 여러 번 다시 렌더링함
    • 렌더마다 API 요청을 보내지 않기 위해 useStateuseEffect로 요청 결과나 오류를 컴포넌트 상태에 저장할 수 있음
    • 예시는 명확성을 위해 단순화된 형태이며, 실제로는 검증된 API 라이브러리를 쓰는 선택지도 있음
  • 캐시는 UI 트리의 더 높은 계층이나 UI 바깥으로 옮겨질 수 있음
    • Redux는 상태를 통합하고 시간에 따른 원자적 변경을 조율하는 React 상태 관리 라이브러리임
    • Redux 생태계는 API 데이터 캐싱을 관리하는 도구와 패턴으로 확장되어 왔음
    • 이런 사용 방식은 캐시 로직을 중앙화하고, 갱신을 조율하며, 컴포넌트 간 캐시 결과를 공유하려는 목적을 가짐
  • 캐싱 계층이 커질수록 렌더링 엔진과 사용자 액션에 맞춰 데이터를 효율적으로 다루는 중앙 저장 시스템에 가까워짐

수동 인덱스와 일관성 부담

  • 프런트엔드에서도 서버에서 받은 데이터를 ID를 키로 하는 객체에 저장해 빠르게 조회·수정할 수 있음
    • REST API를 쓰는 앱에서는 데이터를 배치로 읽고, 필요한 객체를 추가로 보강하는 일이 자주 발생함
    • 객체를 ID별로 저장하면 API 결과를 캐시에 병합하기 쉬움
    • 이 구조는 ID 기준 생성, 읽기, 수정, 삭제에 최적화됨
  • 여러 항목을 훑어야 하는 필터링은 전체 엔트리 검사를 피하기 위해 별도 인덱스를 만들게 함
    • createdAt의 연/월/일 부분을 기준으로 ENTRIES_BY_DATE 구조를 만들면 특정 날짜의 엔트리를 빠르게 찾을 수 있음
    • 대신 CACHEENTRIES_BY_DATE 사이의 일관성을 계속 유지해야 함
    • 날짜 범위 조회에는 여러 번 접근이 필요하며, 날짜순 정렬 배열과 더 복잡한 조회·갱신 로직이 더 나은 구조일 수 있음
  • 인덱스가 많아지면 각 인덱스마다 생성, 갱신, 조회 로직이 필요함
    • 올바름 검증이 테스트와 코드 리뷰의 부담으로 커짐
    • 한 인덱스에서 삭제나 갱신을 빠뜨리면 찾기 어려운 버그가 생길 수 있음
    • 새 애플리케이션 기능보다 이 복잡성을 관리하는 인프라에 더 많은 시간이 들어갈 수 있음
  • 실제 데이터베이스 인덱스는 단순히 데이터를 다른 형태로 저장하는 것보다 훨씬 복잡함
    • 통계 수집, 데이터 버전 관리, 트랜잭션 제어, 잠금, 쿼리 최적화와의 상호작용 같은 요소가 포함됨

낙관적 변경이 추가하는 정합성 문제

  • 낙관적 변경은 서버 응답 전에 특정 작업의 효과를 로컬에서 먼저 시뮬레이션함
    • UI는 네트워크 지연이 없는 것처럼 즉시 반응할 수 있음
    • 서버가 예상과 다른 결정을 내리거나 오류가 발생하면 UI는 변경을 롤백하고 사용자에게 문제 수정을 요구해야 할 수 있음
    • 클라이언트가 서버 결과를 잘 예측하고 오류를 클라이언트에서 처리할 수 있으며 로직이 밀접하게 동기화되어 있을 때 강력한 도구가 됨
  • 낙관적 업데이트는 보통 네 단계로 흘러감
    • UI가 쓰기 작업을 발생시킴
    • 서버가 동의할 것이라는 가정으로 로컬 캐시에 변경을 적용하고 UI를 즉시 다시 렌더링함
    • 변경 작업을 비동기로 서버에 전송함
    • 서버 응답을 로컬 캐시에 병합해 이전 낙관적 변경을 덮어쓰고, 필요하면 UI를 다시 렌더링함
  • 서버와의 일관성을 유지하려면 여러 부담이 생김
    • 결과를 예측하기 위해 클라이언트와 서버에 로직을 중복해야 함
    • 비동기 오류나 서버 불일치를 처리하려면 진행 중인 각 변경을 추적해야 함
    • 더 나은 사용자 경험을 위해 낙관적 캐시 부분을 내구성 있게 만들어 앱 재시작 후 변경을 조정해야 할 수도 있음
  • 이 과정은 개발 시간과 올바름 검증 비용을 키우며, 데이터 관리가 사용자 가치나 차별화 기능 개발보다 앞서게 될 수 있음

재귀적 캐시 무효화의 복잡성

  • 데이터가 많은 앱에서는 같은 정보가 캐시의 여러 위치에 나타남
    • 예시 캐시는 projects, tasks, users를 함께 저장함
    • 작업 완료 후 프로젝트 진행률, 사용자 할당 작업, 새 작업 정보 같은 여러 부분이 영향을 받을 수 있음
  • 작업 완료 후 캐시를 서버와 맞추려면 여러 왕복이 필요할 수 있음
    • 서버에 작업 완료를 알림
    • 프로젝트 진행률이 바뀌었으므로 프로젝트를 새로고침함
    • 새 작업 할당 여부를 확인함
    • 새 할당이 있으면 해당 작업을 조회함
  • 더 복잡한 API로 왕복 횟수를 줄일 수는 있지만, API나 클라이언트 로직이 기저 데이터 모델과 결합되는 결과가 남음
    • GraphQL은 이 문제에 대한 한 접근이지만 완전한 해결책은 아님
  • UI가 각 변경마다 캐시의 어떤 부분이 관련되는지 알아야 하는 구조는 규모가 커질수록 취약해짐
    • 데이터 관계와 집계가 로컬 캐시의 여러 부분에 영향을 줄 수 있음
    • 엔지니어링 팀이 커지면 문제는 팀 경계를 넘을 수 있고, 큰 소프트웨어 프로젝트의 변경 가능한 전역 변수와 비슷하게 느껴질 수 있음
  • 낙관적 변경과 결합되면 클라이언트가 서버 변경을 예측하기 위해 더 많은 백엔드 로직을 복제하게 됨
    • 예시에서는 task 1을 user 1에서 제거하고, 전체 작업 수를 이용해 진행률을 새 비율로 계산하려 할 수 있음
    • 중첩 변경을 로컬에서 예측하도록 만들수록 클라이언트는 백엔드 스택을 더 많이 중복함

SQLSync가 제안하는 프런트엔드 데이터베이스 스택

  • SQLSyncSQLite 위에 만든 프런트엔드 최적화 데이터베이스 스택임
    • 동기화 엔진은 Git과 분산 시스템 아이디어를 기반으로 함
    • React, Vue, Next.js 같은 인기 프런트엔드 프레임워크와 매끄럽게 통합되도록 설계됨
    • 어려운 데이터 관리 문제를 처리해 개발자가 애플리케이션 고유 기능에 집중하도록 하는 것이 목표임
  • 예시 Todo app은 전체 데이터 계층을 Rust 60줄과 컴포넌트에 흩어진 몇 개의 SQL 쿼리로 구현함
    • SQLSync는 내구 캐시, SQLite의 인덱스·제약·트리거·쿼리 최적화, 낙관적 변경, 스마트 캐시 무효화, 반응형 쿼리를 제공함
  • 로컬 데이터는 하나 이상의 SQLite 데이터베이스에 저장됨
    • 인덱스를 쉽게 만들 수 있고 데이터와 자동으로 동기화됨
    • 데이터베이스는 백엔드처럼 인덱스를 자동으로 사용해 쿼리를 가속할 수 있음
    • SQL은 복잡한 데이터 조회를 표현할 수 있고, triggers, foreign keys, constraints, full-text search 같은 기능도 사용할 수 있음
  • 낙관적 변경은 reducer로 처리됨
    • 구조는 Redux의 핵심 개념과 유사함
    • reducer는 WebAssembly로 컴파일 가능한 어떤 언어로도 작성할 수 있음
    • SQLSync는 클라이언트에서 변경을 낙관적으로 실행하고, 서버에서는 전역적으로 일관된 순서로 실행함
    • 이후 클라이언트는 Git Rebase와 유사한 작업으로 서버와 동기화함
  • 이 아키텍처는 재귀적 캐시 무효화 필요를 없애는 장점이 있음
    • 모든 데이터 변경 로직을 클라이언트와 서버에서 공유하기 쉬운 reducer 안에 작성함
    • 변경 중 발생한 모든 데이터 변화가 자동으로 보임
    • 동기화가 Git Rebase처럼 동작하므로 서버가 클라이언트와 다른 변경을 하더라도 클라이언트는 같은 일관된 결과에 도달하는 보장을 가짐

관련 작업

  • Riffle의 “Building data-centric apps with a reactive relational database”는 UI 상태를 포함한 모든 애플리케이션 상태를 하나의 반응형 데이터베이스에 저장하는 아이디어를 다룸
    • 반응형 쿼리는 깔끔한 사고 모델을 제공하고 React 같은 선언형 시스템과 잘 맞음
    • 클라이언트 앱 개발 문제를 데이터베이스 커뮤니티의 아이디어로 해결함
    • 관계형 데이터 모델과 실제 인덱스로 상태를 모델링하는 이점을 다룸
  • Instant.db의 Stepan은 브라우저 안의 데이터베이스를 다룬 두 글을 작성함
  • Matt Wonlaw의 CR-SQLite는 SQLite 확장임
    • 충돌 없는 복제 데이터 타입(CRDT)과 인과 순서 이벤트 로그를 사용해 데이터를 일관되게 병합함
    • 중앙 조정자 없이 피어투피어 앱이 SQLite에 데이터를 저장하고 협업할 수 있게 함
    • 브라우저에서 SQLite를 실행하는 예시이기도 함
  • Matt Wonlaw는 관련 아이디어도 탐색 중임

댓글과 토론

Hacker News 의견들
  • 이 프로젝트를 잘 알고 있고, 만든 사람이 친구라서 여기 와서 질문에 답하게 해보겠음
    그는 숙련된 데이터베이스 아키텍트임. SQLsync로 프런트엔드 개발자가 원격 데이터베이스를 마치 브라우저 안에 완전히 있는 것처럼 조회하고 갱신할 수 있게 만들었음. 실제로 거의 그렇고, WASM 덕분에 SQLite 데이터베이스 전체를 브라우저로 보낼 수 있음. 핵심은 여러 클라이언트에서 동기화하는 영리하지만 단순한 반응형 알고리즘에 있음
    개발 업무의 상당 부분이 데이터 동기화라고 보면, React와 REST API도 일종의 동기화 절차로 볼 수 있고 이 접근은 새로운 가능성을 열어줌. API에서 가져와 캐시한 객체 트리로 이상한 맞춤형 데이터베이스를 더 만들 필요 없이, 관계형 데이터베이스의 힘으로 로컬에서 바로 갱신하고 조회하면 됨

    • 전체 질의 시스템을 프런트엔드로 옮기는 것이 많은 프런트엔드 개발자가 진짜 원하는 지점이라고 봄. 데이터에 대해 강력한 질의 시스템을 원하지, 전송 계층이나 REST, GraphQL, *RPC 같은 걸 계속 재발명하고 싶어 하는 게 아님
      다만 전통적인 웹 회사에서는 전문화된 백엔드/프런트엔드 팀 때문에 도입이 어렵다. 데이터베이스, 백엔드, 전송, 인증 계층을 빼고 하나의 블록 시스템으로 바꾸는 셈이고, 시스템 아키텍트 대부분은 백엔드 출신이라 이 문제를 잘 모름. 양쪽을 깊게 건드리니 기존 시스템에는 잘 맞지 않고, 결국 신규 개발에나 맞음. 백엔드가 AWS나 Azure 서비스도 아니고 Lambda 친화적이지도 않아서, 내가 만나는 아키텍트 유형들은 대부분 손대려 하지 않을 것임
      이 방식은 예전 기술인 CouchDB+PouchDB로도 어느 정도 이미 존재함. 일부 용도에는 꽤 잘 맞지만 질의 시스템이 이상적이지 않고, 인증과 데이터 범위 지정 방식이 대부분에게 낯설다. 데이터가 단일 사용자에게 완전히 귀속되고 사용자별 데이터베이스 모델을 그대로 쓰는 경우가 가장 쉽다. CRDT로 데이터를 강하게 분할하면 충돌 문제도 많이 줄어듦
      하지만 확장성 문제가 있음. CouchDB는 1만~10만 사용자가 연결되면 CPU 요구량이 매우 높고, 기술도 오래됐지만 유지보수는 되고 있음. 시스템 설계 측면에서는 사용자 간 데이터 공유를 시작하면 복잡도가 급격히 커져서, 복잡도를 해결하기보다 위치만 옮기는 꼴이 되어 적합성이 떨어짐
      이 접근도 같은 목표를 겨냥하는 듯하지만 비슷한 확장성 문제를 겪을 가능성이 큼. 앞으로 어떻게 발전할지 기대되고, 첫걸음처럼 보임
    • 이건 원격으로 동기화되고 다시 피어로 동기화되는 데이터베이스를 조회/갱신할 수 있는 Couchbase와 꽤 비슷하게 들림. 서버 측 JavaScript 플러그인으로 인증이나 비즈니스 로직도 쉽게 제어할 수 있음
    • 관련 부분만 LocalStorage / SessionStorage에 캐시하면 왜 안 되는지 순수하게 궁금함
      예전에 Chrome이 브라우저에 문자 그대로 SQL 데이터베이스를 넣으려 했던 기억이 나는데 잘 안 풀렸고 localStorage가 주류가 됐음. 유용성을 깎아내리려는 건 아니고, 보통은 브라우저가 제공하는 걸 선택하는 편임. WASM과 그것이 더 성숙하거나 기능이 늘어나면서 브라우저에 가져올 가능성에는 크게 기대하고 있음
  • 예전에 다니던 회사는 변경 작업에 체크아웃/체크인 메커니즘이 있는 프로젝트 관리 소프트웨어를 썼음. 프로젝트를 체크아웃하면 로컬에서 수정할 복사본을 내려받고, 체크인하면 다시 서버에 올리는 방식임. 체크아웃 상태에서는 프로젝트가 잠김. 라이브 업데이트 앱의 시대에는 모두가 낡은 방식이라고 느꼈음
    그런데 10년 동안 SPA 웹앱을 만들고 나니, 그 데이터 동기화 방식이 시대를 앞서간 것처럼 느껴짐

    • 여러 사용자의 병렬 라이브 업데이트를 지원할지, 아니면 한 번에 하나의 업데이트만 진행되도록 잠글지는 결국 기술 결정이 아니라 비즈니스 결정임. 애플리케이션에 맞는 비즈니스 규칙이 동시 라이브 업데이트를 처리할 수 있게 해주느냐가 핵심임
      결국 여러 동시 업데이트 사이의 불일치를 일관되게 해결하는 절차를 구현할 수 있느냐로 귀결됨. 가능한 경우도 있고 불가능한 경우도 있으며, 이는 기술 역량보다 비즈니스 규칙에 달려 있음
      비즈니스 규칙상 해결 메커니즘을 구현할 수 없다면, 동시 업데이트를 지원할 기술 능력이 있더라도 한 번에 하나의 업데이트만 되도록 잠금이 필요함
    • 이 방식으로 가면 많은 문제가 해결되고 구현도 매우 쉬워짐
      하지만 실제로 원하는 것이 이 방식이라는 점을 설득하기 어렵다. 모든 것이 항상 사용 가능해야 한다는 큰 환상에 빠지기 쉽지만, 현실에서는 보통 한 사람이 한 번에 변경하고, 두 명 이상이 작업해야 하면 어차피 서로 이야기하거나 소통해서 맞춰야 하는 경우가 많음
      Git처럼 완전히 분산된 개발에서도 충돌을 자동으로 마법처럼 해결할 수는 없음. 올바른 변경을 고르려면 여전히 다른 사람과 소통하고 맥락을 이해해야 함
    • 바로 이 점 때문에 사람들이 MySQL 같은 고전적인 관계형 데이터베이스 패러다임을 NoSQL이나 MongoDB 같은 비관계형 데이터베이스보다 낮게 말하더라도 여전히 살아남음. 빠르다거나 멋지다는 이유만으로 모든 걸 대체할 수는 없음
      어떤 것들은 검증된 해법이 필요함
    • RCS처럼 들림 https://en.wikipedia.org/wiki/Revision_Control_System
      예전에 회사가 RCS에서 CVS로 옮겼을 때, 동료 중 한 명이 CVS가 잠금 체크아웃을 지원하지 않는다고 짜증냈던 기억이 있음
      https://en.wikipedia.org/wiki/Concurrent_Versions_System
    • 이 접근을 좋아함. SQLSync는 사실상 이걸 계속 수행하는 셈이지만, 명시적으로 조율하면 그런 체크인/체크아웃 방식도 가능할 것임
      단일 소유자 잠금 전략도 SQLSync로 시뮬레이션할 수 있다고 봄. 다만 앱에 따라 필요 없을 수도 있음. 목표가 오프라인 작업 후 준비됐을 때 병합하는 것이라면 SQLSync가 이 패턴을 기본 제공함. 목표가 오직 한 클라이언트만 변경할 수 있게 하는 것이라면 중앙 잠금 패턴이 필요하고, 이것도 SQLSync를 통해 조율할 수 있을 가능성이 있음
  • 여기에는 “측정되는 것이 관리된다”는 원칙과 매몰 비용 오류가 얽혀 있음
    데이터베이스의 진짜 문제는 복잡도임. 개별 기능 하나하나는 대체로 안전하지만, 안정성·캐싱·인덱스가 맞물리는 시점에 복잡도가 폭발하고 보통은 도메인 특화 DB를 구현하는 게 말이 안 됨
    그런데 회사가 그 세 기능 구현에 이미 투자하고 많은 자원을 쏟아부었다는 걸 깨닫는 시점에는, 정치적으로 그걸 걷어내자고 권하기 어렵고 기술 부채를 한 번에 제거하는 실제 비용도 큼
    진짜 문제는 SQL 문법이라고 봄. 기본 관계형 데이터베이스를 쓰는 경험이 깨진 영어 대신 익숙한 C풍 문법처럼 쾌적했다면, 직접 만들기보다 DB를 쓰려는 유인이 더 컸을 것임. NoSQL 데이터베이스들은 그 방향으로 좋은 발걸음이었지만 대체로 일상적 유용성보다 빅데이터에 과하게 집중했음. Redis 같은 건 자리 잡았고 괜찮음
    SQL을 쉽게 실행하게 만드는 건 합리적인 접근이지만, 좋은 데이터베이스들, 예를 들어 내가 좋아하는 Postgres는 SQL이 기본 언어라 그 언어를 쓰지 않고 효율을 얻기 어렵다. Postgres를 완벽히 복제하되 기본 파서가 좋은 문법의 언어를 지원하는 PostgresPostSQL 같은 데이터베이스가 정말 필요함

    • SQL이 정확히 뭐가 어려운지 모르겠음. 모든 개발자는 SQL을 알아야 한다고 봄. SQL 문법도 좋고 오래 살아남을 만큼 검증됐음. 비난하기보다 실제로 배우는 데 시간을 투자하면 더 도움이 될 것임
    • SQL은 자주 비판받고, 나도 그럴 만한 이유가 있다고 보지만 왜 더 나은 것을 만들지 못했을까?
      일반 프로그래밍에는 수십 개 언어가 쓰이고 계속 진화함. 브라우저가 실행하고 사용자의 브라우저를 통제할 수 없어 바꾸기 어려운 JavaScript조차 트랜스파일러와 WebAssembly를 통해 진화하고 있음
      그런데 데이터베이스에는 사실상 SQL 하나뿐임. 대안은 있지만 사용량 면에서 SQL에 근접한 것은 없음. 어쩌면 SQL이 그렇게 나쁘지 않은 것일 수도 있음
      이유는 관계형 모델이 정말 좋기 때문일 수 있음. 여기서 벗어나는 시도는 틈새에서만 통할 가능성이 큼. 선언형 스타일도 매우 좋고, 여기서 벗어나도 큰 성공을 거두기 어려움. 결국 문법만 다른 SQL을 만들게 된다면 대부분에게는 방식을 바꿀 만큼 큰 개선이 아닐 것임
    • Postgres에 SQL보다 안정적이고 더 낮은 수준의 API가 있었으면 좋겠음. EXPLAIN으로 얻는 질의 계획과 비슷한 형태일 수도 있음
      이 API를 대상으로 작성된 애플리케이션은 SQL DB를 구현할 수 있음. SQL을 파싱하고, 이 API에 맞는 질의 계획을 뱉는 질의 계획기를 구현하면 됨
    • 직접 SQL을 피하고 싶은 사람들을 위해 데이터베이스 위에 의미 계층을 추가하는 방식을 살펴본 적이 있는지 궁금함
  • 글쓴이임. 대부분의 질문을 겨우 훑었고, 놓친 질문이 있는지 주기적으로 계속 확인하겠음. HN 토론을 더 잘 추적하는 방법을 누가 만들었는지도 궁금함
    지금까지의 논의가 아주 반가움. 첫 글은 SQLSync가 구체적으로 어떻게 작동하는지보다, 내가 SQLSync를 만들게 된 프런트엔드 엔지니어링의 동기에 초점을 맞췄음. 다음 글에서 작동 방식을 다룰 예정임

    • 모바일 지원 계획이 있는지 궁금함. 내가 이걸 가장 써보고 싶은 곳이 바로 모바일임
  • 사용자가 현실이 심하게, 혹은 보이지 않게 깨뜨릴 수 있는 멘탈 모델을 갖게 하면 안 됨
    클라이언트-서버 모델 대신 데이터베이스를 동기화하는 방식이 그런 경우 중 하나일까 봐 걱정됨. 동기화 메커니즘이 그냥 녹아내리거나, 충족되지 않는 깊은 가정들이 있을 수 있음
    빠른 UI가 필요하다면 CRDT 원시 요소 집합을 만들어 쓰는 편이 더 안전하게 느껴지고, 나머지는 폼 제출에 머무를 것 같음

    • 동의함. SQLSync를 쓰는 개발자들이 멘탈 모델을 쉽게 이해할 수 있게 만드는 것이 목표 중 하나임. 편향이 있겠지만, 개인적으로는 리베이스 모델이 CRDT보다 훨씬 이해하기 쉽다고 느낌
  • 클라이언트와 서버 사이의 상태 동기화는 저주받은 문제임
    약간의 사용자 경험 희생을 감수하고 PHP/서버 사이드 렌더링 모델에 가까운 방식으로 돌아가면 이 문제를 통째로 피할 수 있음. SPA는 좋지만 multipart 폼 전송도 여전히 작동함. 아주 적은 JavaScript만으로도 남은 거친 부분 대부분을 다듬을 수 있음
    최근 웹 제품들에서 클라이언트 측 상태는 서드파티 IdP의 인증 클레임, 쿼리 인자의 퍼스트파티 세션 ID, 현재 문서뿐임. 첫 번째가 어디에 저장되는지는 솔직히 나도 모름. 그건 Microsoft 문제지 우리 문제가 아님. 나머지 모든 상태는 서버에 있음
    클라이언트를 하루 종일 입력만 찍어내는 멍청한 터미널처럼 다룸. 퍼스트파티 쿠키나 로컬 스토리지도 쓰지 않음. 이 방식은 iOS/Safari 대상 개발 경험을 크게 개선했음
    그래서 실제로 제공하려는 경험이 무엇이고, 왜 클라이언트와 서버 상태를 분리할 만큼 정당화되는지 묻고 싶음

    • 소비자용 그래픽 인터페이스에서는 낙관적 렌더링이 매우 흔한 흐름이고, 그걸 한다면 클라이언트/서버 상태를 다루는 셈임. 아직도 여기저기 로딩 스피너를 보긴 하지만 보통 초기 콘텐츠 로드용임. 예를 들어 Gmail이 이메일을 보관 처리할 때 기다리게 하지는 않음
    • ElectricSQL은 이 문제 해결을 향해 큰 진전을 이뤘음. 클라이언트에서 SQLite에 쓰고 Postgres로의 동기화를 보장함
      참고: https://news.ycombinator.com/item?id=37584049
  • SQLite 기반 오프라인/로컬 우선이 요즘 뜨거운 듯함. 이번 주에 세 번째로 읽는 중이고, 좋아 보임
    그런데 ElectricSQLhttps://electric-sql.com/ 및 PowerSynchttps://powersync.com/와 비교하면 어떤가?

    • 정말 뜨거운 분야가 맞음. 서로 다른 접근들이 나오는 걸 보는 게 매우 흥미로움
      ElectricSQL과 PowerSync는 둘 다 부분 복제라는 매우 어려운 문제를 다루고 있음. 전통적인 중앙 데이터베이스가 클라이언트에 필요한 것만 양방향 동기화하면서도 낙관적 변경과 그에 따른 일관성/충돌 처리를 지원하는 일반 해법을 만들려는 것임
      단점은 구현 복잡도임. 전체 데이터베이스 중 어떤 하위 집합을 각 클라이언트가 갖고 있는지 정확히 추적해야, 그 하위 집합에만 변경을 푸시할 수 있음. 또 데이터베이스 상태 중 어떤 하위 집합을 내려받을지 지정하려면 새로운 DSL이 필요하고, 그것도 새로 배우고 최적화해야 함. 그래도 이들이 매우 어려운 문제를 풀고 있다는 점은 반갑고, SQLSync가 부분 복제를 지원할 준비가 됐을 때 이미 모범 사례가 정리되어 있을 것임
      반면 SQLSync는 현재 전체 DB 동기화만 지원함. 모든 클라이언트가 전체 데이터베이스에 대해 일관된 뷰를 보게 됨. 이게 좋은 생각인지 바로 의문이 들 수 있고, 어떤 앱에는 맞지 않음. 하지만 개인 금융 앱을 생각해보면 핵심 목표가 기기 간 동기화, 클라우드 백업, 오프라인 가능성 등이라 모든 기기에 전체 DB가 저장되는 게 오히려 원하는 방식일 수 있음. Airtable 같은 문서 지향 데이터 모델도 예가 될 수 있음. 각 Airtable을 별도 데이터베이스로 두면, 어떤 테이블을 신경 쓸지는 클라이언트가 관리하면 됨
      전체 DB 동기화에 집중하면 부분 복제를 지원하는 해법보다 동기화 엔진이 훨씬 단순해짐. 그 이점 중 하나는 백엔드가 매우 가볍다는 것임. 현재 데모(https://sqlsync-todo.pages.dev)는 Cloudflare Durable Objects 안에서 아주 적은 저장소와 CPU 시간만 쓰며 완전히 실행됨
      SQLSync가 이런 사용 사례를 가능하게 하려면 아직 할 일이 많고, 여전히 프로토타입에 가깝지만 초기 테스트는 매우 긍정적이었음
  • 대형 멀티테넌트 앱에서 개별 데이터셋이 비교적 작을 때 “그냥 데이터베이스를 클라이언트로 보내면 어떨까”라는 생각을 여러 번 해봤음. 충분히 표준에서 벗어난 저주받은 아키텍처 패턴처럼 보여서 깊게 가진 않았음. 내가 틀렸다는 걸 알게 되면 좋겠음

    • 칼로리 계산 앱에서 한 번 그렇게 해본 적 있음. 데이터베이스에 음식이 수십만 개 있어도 앱이 대부분의 미디어 앱이나 게임보다 훨씬 적은 공간을 차지했음
    • 나도 이게 저주받은 방식인지 궁금함. 지금까지는 예상보다 훨씬 좋았음. https://sqlsync-todo.pages.dev 같은 앱은 이 패턴으로 사소해짐
      제대로 증명하려면 할 일이 많지만, 계속 밀어붙여 어디로 가는지 보고 싶어 꽤 기대됨
    • 올바른 답은 UI를 데이터베이스가 이미 있는 서버로 되돌리고, 클라이언트 측 HTML 렌더러, 즉 웹 브라우저로 HTML만 보내는 것임. 이 글 전체는 “프런트엔드가 선을 넘고 절벽을 지나 바다로 떨어졌다”에 가까움
  • 이건 SPA를 버리면 완전히 사라지는 문제들 중 하나처럼 보임
    Hotwire나 htmx 계열 해법을 쓰면 질의는 그냥 서버 질의가 되고, 그런 질의를 빠르게 만드는 문제는 훨씬 더 잘 이해되어 있음

    • 이건 웹사이트만의 문제가 아님. 모바일과 데스크톱 생태계도 브라우저처럼 씬 클라이언트로 크게 움직여야 할까? Apple Reminders나 Google Tasks 같은 단순 앱이 지연이나 연결 문제 때문에 GUI를 멈춰야 할까?
    • 최근 htmx를 써봤는데, 제거되는 복잡도와 그 결과로 생기는 생산성이 정말 말도 안 됨. 원하는 스택을 그대로 쓸 수 있다는 점도 큰 축복임
      ocaml + web components와 함께 써봤고, 생산성 10/10 경험이었음. 눈 깜빡이는 것보다 빨리 컴파일되는 빌드 도구 하나만 있으면 되고, 프런트엔드와 백엔드 사이에 JSON 매핑 배선도 필요 없어서 정말 생산적임
    • 솔직히 Hotwire나 Livewire 같은 해법은 SPA만큼 즉각적이지는 않음
      개인적으로는 InertiaJs https://inertiajs.com를 선호함. 서버와 “옛 방식”으로 상태를 동기화하는 일종의 프런트엔드 라우터 시스템임
    • “상호작용이 많은 웹앱을 만들지 마라”나 “VM 하나 넘게 필요한 서비스를 운영하지 마라”와 비슷한 말임. 말도 안 되는 견해임
    • SPA에서 벗어날 때가 아니라, 쓰기 좋은 제품들이 자주 갖는 고상호작용 기능에서 벗어날 때 문제가 사라진다고 봄
      인터넷이 불안정한 지역에서도 동작해야 하는 제품이라면 특히 더 그렇다
  • 지금 “풀스택 데이터베이스”에 관한 아주 비슷한 글을 쓰고 있음. 많은 앱이 백엔드와 데이터베이스의 논리를 프런트엔드 클라이언트 코드에서 다시 만드는 패턴을 다룸. 우리가 권하는 해법은 서버와 클라이언트 양쪽에서 실행될 수 있는 데이터베이스를 고르고 그 사이를 동기화하는 것임
    우리 제품에 SQLite를 쓰지 않는 이유는, 솔직히 SQL이 애플리케이션 데이터를 질의하는 데 맞는 도구가 아니기 때문임. 클라이언트 코드에서 원하는 데이터 구조와 쉽게 맞물리지 않고, 거의 모든 SQL 데이터베이스는 질의를 반복 폴링하지 않고 질의 변경을 구독하는 방법이 없음
    클라이언트에 완전한 데이터베이스를 두는 아이디어가 마음에 들면서 TypeScript/JavaScript와 깊은 통합을 원한다면 우리가 만드는 https://github.com/aspen-cloud/triplit을 봐주면 좋겠음

    • 사실 Postgres는 WAL을 통해 실시간 변경 구독을 할 수 있는 훌륭한 방법을 제공함. 이 오픈소스 라이브러리도 내가 관리하고 있음
      https://github.com/cpursley/walex
    • SQLite는 실제로 update hook을 통해 변경을 감지하는 데 필요한 메커니즘을 제공함. 많은 SQLite 바인딩이 그것을 노출하지 않는 점이 아쉬움
      나는 아주 단순하게 쓰고 있음. 기반 테이블의 데이터가 바뀌면 질의를 자동으로 다시 실행함. 결과를 점진적으로 갱신하는 것만큼 효율적이지는 않겠지만, SQLite 질의가 보통 워낙 빠르다 보니 큰 문제가 아니라고 봄
    • 클라이언트 코드에서 원하는 데이터 구조와 쉽게 맞물리지 않는다는 데 동의하지 않음. 데이터 정규화는 프런트엔드 반응형 애플리케이션에 중요하고, 데이터를 최신 상태로 유지하려면 사실상 필요함. 모든 CRUD 작업도 훨씬 다루기 쉬워짐
    • Realm Sync와 Mongo + Kotlin Multiplatform이면 서버, 웹, 모바일, 데스크톱 같은 거의 모든 플랫폼을 커버할 수 있음. 물론 비용이 듦. 대안에는 실제로 관심이 있는데, 이 내용도 글에 들어가는지 궁금함