2P by GN⁺ | ★ favorite | 댓글 1개
  • Cloudflare의 Durable Object가 키/값 저장소에서 SQLite 기반 관계형 저장소로 바뀌며, 애플리케이션 로직과 데이터가 같은 물리 호스트에서 실행되는 구조가 됨
  • 확장은 객체 하나를 키우는 대신 문서·사용자·데이터베이스 샤드 같은 논리적 상태 단위별로 Durable Object를 나누는 방식이며, 단일 객체는 단일 머신·단일 스레드 한계를 가짐
  • 항공편 예약처럼 상태 단위가 명확한 시스템에서는 항공편마다 전용 Durable Object와 자체 SQLite 데이터베이스를 둘 수 있어, 항공사별로 하루 수천 개의 새 데이터베이스가 생길 수 있음
  • 내구성은 WAL 엔트리 스트리밍과 객체 스토리지 저장, 인근 데이터센터 복제로 구성되며, 16MB 또는 10초마다 배치되고 최대 30일 시점 복구를 지원함
  • JavaScript API는 빠른 단일 스레드 영속성 작업을 위해 블로킹 방식을 택했고, Durable Object의 위치는 생성 후 고정되지만 향후 동적 재배치가 계획되어 있음

SQLite로 바뀐 Durable Object 설계

  • Cloudflare의 Durable Object는 키/값 저장소에서 SQLite 기반 관계형 시스템으로 업그레이드됨
  • 핵심은 애플리케이션 로직을 해당 로직이 다루는 데이터와 같은 위치에 두는 것임
    • 하나의 Durable Object는 사용하는 SQLite 데이터베이스와 같은 물리 호스트에서 실행되는 코드로 구성됨
    • 읽기와 쓰기를 네트워크 왕복 없이 처리하는 방향이라 지연시간을 줄이기 쉬움
  • 대규모 처리는 객체 하나의 처리량을 키우기보다 더 많은 객체를 만드는 방식으로 접근함
    • 단일 객체는 단일 머신의 단일 스레드에서 실행되므로 처리량에 본질적 한계가 있음
    • 문서, 사용자, 데이터베이스 샤드처럼 서로 다른 논리적 상태 단위를 별도 객체가 맡으면 확장하기 쉬움
  • 항공편 예약 시스템에서는 각 항공편을 전용 Durable Object와 자체 SQLite 데이터베이스에 매핑할 수 있음
    • 이 구조에서는 항공사별로 하루에 수천 개의 새 데이터베이스가 만들어질 수 있음
  • 각 Durable Object는 고유한 이름을 가지며, Cloudflare 네트워크는 전 세계 네트워크 안에서 해당 객체가 있는 위치로 요청을 라우팅함

WAL 스트리밍과 복제로 확보하는 내구성

  • Durable Object 기반 시스템은 Litestream에서 영감을 받아 각 객체의 WAL 엔트리 시퀀스를 객체 스토리지로 계속 스트리밍함
    • 배치는 16MB마다 또는 10초마다 수행됨
    • 기록된 트랜잭션을 재생해 최대 30일 동안 시점 복구가 가능함
  • 10초 창 안의 내구성을 보완하기 위해 쓰기는 커밋 직후 인근 별도 데이터센터의 복제본 5개로 전달됨
    • 이 중 3개가 확인해야 쓰기 응답이 승인됨
  • Durable Objects의 기반 시스템은 Storage Relay Service이며, Cloudflare의 별도 D1 SQLite system을 1년 넘게 구동해 옴

API와 객체 위치 동작

  • JavaScript API는 async가 아니라 블로킹 방식을 사용함
    • 빠른 단일 스레드 영속성 작업을 제공하는 것이 설계 목적임
    • 예시 코드는 문서 목록을 조회한 뒤 각 문서의 작성자 이름을 별도 쿼리로 가져오는 N+1 쿼리 패턴을 일부러 사용함
    • SQLite는 이 패턴을 처리하는 데 잘 맞는 사례로 연결됨
  • Durable Object는 현재 생성된 뒤 위치를 바꾸지 않음
    • 기본적으로 최초 get() 요청이 만들어진 위치에 가까운 데이터센터에서 인스턴스화됨
    • 다른 위치에 수동 생성하려면 get()에 선택적 locationHint 매개변수를 제공할 수 있음
    • 기존 Durable Object의 동적 재배치는 향후 계획되어 있음
  • where.durableobjects.live는 Cloudflare 네트워크에서 새 Durable Object가 어디에 생성되는지 추적하는 사이트임
    • 예시 방문에서는 Half Moon Bay에서 페이지를 로드했을 때 San Jose의 worker가 San Jose에 Durable Object를 생성했다고 표시함

댓글과 토론

Hacker News 의견들
  • 흥미로운 점이 몇 가지 더 있음: 쓰기 API는 동기식이지만 숨겨진 비동기 대기가 있어, 다음 응답 출력 시 쓰기가 실패하면 런타임이 응답을 HTTP 실패로 바꿔줌
    그래서 사용자가 오류나 대기를 명시적으로 처리하지 않아도 런타임이 쓰기를 자동 배치하고 성공을 낙관적으로 가정할 수 있음
    읽기 트랜잭션은 없는데, 특정 시점의 스냅샷 포인터를 얻는 데 유용했을 것 같음
    각 런타임 인스턴스는 128MB RAM으로 제한됨
    WebSocket은 최대 절전 상태가 될 수 있고 잠자는 시간에는 과금되지 않아, DO가 잠든 동안에도 클라이언트를 연결 상태로 유지할 수 있음
    다른 DO나 Worker를 일반 JS 호출처럼 다룰 수 있는 자동 RPC 비슷한 기능도 있는데, 실제로는 다른 데이터센터를 호출할 수 있고 직렬화와 파싱은 런타임이 처리함

    • RPC 쪽이 꽤 흥미로움. 자세한 내용은 여기 있음: https://blog.cloudflare.com/javascript-native-rpc/
    • 쓰기 API의 숨겨진 비동기 대기는 PostgreSQL의 commit_delay를 떠올리게 함. 원리는 정확히 같지 않지만 비슷한 느낌임: https://www.postgresql.org/docs/current/runtime-config-wal.h...
      글에서 언급된 Litestream도 비슷한 기법을 제안함
    • 여기서 데이터베이스 수준의 읽기 트랜잭션이 필요한 구체적인 사용 사례가 있는지 궁금함
      일반적인 SQLite에서는 여러 프로세스가 동시에 같은 데이터베이스에 접근할 수 있어서 읽기 트랜잭션이 유용함
      여기서는 단일 프로세스만 데이터베이스에 접근할 수 있으니, 모든 읽기를 하나의 동기 함수 안에서 수행하거나 프로세스 수준 잠금을 직접 구현하면 같은 효과를 낼 수 있음
  • WAL 항목이 객체 저장소로 16MB마다 또는 10초마다 배치 스트리밍된다는 건, 전역에서 쓰기를 안정적으로 읽기까지 최대 10초가 걸릴 수 있다는 뜻으로 보임
    이게 어떻게 대륙 단위에서 밀리초 응답을 내는 지역별 데이터베이스 클러스터를 대체할 수 있는지 잘 모르겠음
    스트림을 쓰는 건 알지만 그 대상은 팔로어 5개뿐이고 Cloudflare는 수백 개 데이터센터를 갖고 있음
    모든 SQLite 인스턴스가 항상 연결돼 있지 않다면 초 단위 읽기를 물리적으로 보장할 수 없고, 연결돼 있어도 패킷 지연이 문제를 일으킬 수 있음

    • 알기로는 쓰기와 읽기가 같은 프로세스에서만 이뤄져서, 장기 저장소는 현재 프로세스가 최대 절전 상태일 때만 적용됨
      쓰고 바로 읽으면 즉시 반영됨. 쓰기와 읽기가 현재 프로세스의 메모리 상태도 같이 갱신하기 때문임
      다른 프로세스, 예를 들어 다른 DO나 Worker가 데이터에 접근하려면 데이터를 가진 DO를 거쳐야 하므로 RPC나 HTTP 요청을 보내게 되고 최신 정보를 받게 됨
      최대 절전은 일정 시간 비활성 뒤 발생하니, 예상과 달리 쓰기가 unavailable해지는 경우는 DO나 Worker가 쓰기 직후 크래시나는 상황 정도로 보임
    • 쓰기는 거의 실시간으로 팔로어 5개에 스트리밍되고 거의 즉시 확인됨
      Cloudflare 블로그 글이 이 부분을 더 자세히 다룸. 그래서 쓰기는 빠르게 유지하면서도 내구성을 확보함
    • Durable Objects 동작 방식을 오해한 것 같음. 같은 객체로 향하는 모든 트래픽은 그 객체가 존재하는 단일 머신으로 라우팅됨
      그 머신은 자신의 SQLite 데이터베이스에 대해 항상 일관된 관점을 가짐
      객체는 수십억 개 만들 수 있지만 각 객체는 별도 데이터베이스를 가짐
      DO가 실행 중인 머신과 다른 머신에서 그 데이터베이스를 직접 읽는 방법은 없음
    • 객체 저장소로 스트리밍되는 WAL 항목은 아마 백업용일 것 같음
      각 DO는 전역에서 유일하고, 특정 ID의 DO는 어디선가 하나만 실행되며 해당 데이터센터의 로컬 저장소에서 SQLite를 돌림
  • Durable Objects에서 아직 이해가 안 되는 부분은 물리적 위치
    처음 생성 원인이 된 API 호출을 호스팅한 리전에 위치하는 건지 궁금함
    그렇다면 북미에서 생성됐지만 이후 모든 읽기/쓰기 트래픽이 호주에서 온다는 식으로 드러났을 때, DO를 자동으로 다른 위치로 옮기는 메커니즘이 있는지도 궁금함

    • 기본적으로는 생성한 리전에 있지만, 대안으로 locationHint를 지정할 수 있음. 호주는 "oc"를 쓰면 됨: https://developers.cloudflare.com/durable-objects/reference/...
      “기존 Durable Objects의 동적 재배치는 향후 계획”이라는 점도 참고해야 함
    • https://where.durableobjects.live는 DO가 어디에 있는지 보여주는 좋은 사이트임
      Cloudflare PoP 중 10~11% 정도만 Durable Objects를 호스팅함
      DO 생성을 위해 다른 PoP로 들어온 요청은 이를 호스팅하는 가까운 PoP 중 하나로 전달됨
    • 현재 Durable Objects는 생성된 뒤 위치를 바꾸지 않으며, 기존 Durable Objects의 동적 재배치는 향후 계획임: https://developers.cloudflare.com/durable-objects/reference/...
      기억하기로 Orleans(https://www.microsoft.com/en-us/research/wp-content/uploads/...)는 액터를 머신 사이에서 옮길 수 있는데, 이 모델은 DO를 위치 사이에서 옮기는 방식과 잘 맞을 것 같음
    • Durable Objects에는 장기 저장소가 있고, 그 저장소에서 하이드레이션되므로 그런 의미에서는 어떤 Cloudflare 데이터센터로도 이동할 수 있음
      다만 Durable Object를 이동시키는 API 호출은 없음
      연결이 없어야 하고, 다음 또는 첫 연결에 가장 가까운 데이터센터에서 다시 만들어짐
      이때 메모리는 사라지지만 저장소는 남음
      최근에 최대 절전 관련 세부 기능이 생겨서 이 설명은 약간 오래됐을 수 있음
    • 질문에 대한 답은 아니지만 https://where.durableobjects.live/를 추천함
  • 이런 새로운 클라우드 기술들을 이해하는 게 어렵게 느껴지는 사람이 또 있는지 궁금함
    웹 개발 경력 15년 이상이고 Laravel / Postgres / Redis 스택을 써왔는데, 이런 글을 읽으면 그냥 나와는 안 맞는다는 생각이 듦

    • 글에 따르면 Durable Objects 첫 버전의 유용한 배경으로 Paul Butler의 Cloudflare’s durable multiplayer moat를 보라고 되어 있고, 그는 WebSocket 기반 실시간 협업 애플리케이션을 만드는 데 인기가 있는 이유를 다룸
      바로 떠오르는 실시간 협업 앱은 Google Docs/Sheets, Notion, Miro, Figma 같은 것들임
      모두 전역 규모 협업 앱이고, Laravel 스택이 이런 사용 사례를 지원할 수 있을지는 잘 모르겠음
      Google은 아마 대부분을 자체 구현해야 했고 CRDT 사용을 선도했을 것 같음
      하지만 패턴이 드러나고 구성 요소가 SaaS화되면서, 대규모 실시간 협업이 더 이상 거대한 엔지니어링 문제가 아니게 되고 더 흥미로운 제품들이 가능해짐
  • Durable Object 설계가 정말 마음에 듦. 특히 내부가 어떻게 동작하는지 이해하기 쉽다는 점이 좋음
    실시간 데이터용으로 설계된 다른 많은 솔루션과 달리, Durable Objects에는 Redis와 이탈리아 음식 같은 단순함이 있음. 재료가 다 보임
    충분한 시간과 자원, 그리고 데이터센터가 있다면, 유능한 프로그래머가 DO 문서를 읽고 비슷한 것을 다시 구현할 수 있을 것 같음
    그래서 관련된 절충점을 판단하기 쉬움
    다만 DO는 빠르고 오버헤드가 낮은 실시간 경험, 예를 들어 다섯 명이 문서를 실시간 편집하는 기능을 만들기에는 좋지만, 분석과 개요를 만들기는 매우 어렵게 만들 수 있다는 걱정이 있음
    지난주 어떤 그룹이 어떤 문서를 편집했는지 같은 질문이 그렇고, 데이터를 SQLite 안에 넣으면 더 어려워질 수 있음
    수많은 작은 SQLite 인스턴스를 어떻게든 쿼리한 뒤 결과를 병합해야 할 테니, DO에 이를 위한 무언가가 있는지 궁금함
    결국 계속 Postgres로 돌아오게 되는 이유가 여기에 있음. 핵심 앱 기능에도, 개요와 BI 등에도 다 쓸 수 있기 때문임

  • 정말 흥미로운 설계지만, 이런 똑똑한 시스템들은 늘 개인적으로 불쾌한 골짜기에 있음
    필요한 경우는 정확히 두 가지뿐임: 아주 높은 부하의 시스템을 영리하게 확장해야 하거나, 재미로 장난감 프로젝트를 만드는 경우임
    장난감 프로젝트라면 무엇을 써도 괜찮음
    하지만 프로덕션이나 업무용이라면 검증된 것이 필요함
    이게 필요하다는 걸 모른다면 필요 없는 것이고, 지루한 Postgres 데이터베이스와 VM 같은 걸 쓰면 됨
    반대로 필요하다는 걸 안다면 곤란해짐. 아직 새롭고 충분히 성숙하지 않아서 이상한 엣지 케이스를 많이 만나게 될 가능성이 높고, 그걸 디버그하거나 감수하고 싶지는 않을 것임
    결국 이런 시스템은 누구를 위한 건지 모르겠음
    너무 틈새라서 많은 진지한 플레이어가 쓰며 쉽게 성숙하기 어렵고, 99.9%의 회사가 쓰기에는 너무 복잡하고 절충점도 많음
    확실한 대상층은 반짝이는 걸 보고 회사를 만들거나, 더 나쁘게는 남의 회사를 그 위에 만들었다가 곧 후회하고 더 지루한 것으로 옮기는 개발자들뿐인 것 같음

    • 보기에 Durable Objects의 킬러 앱은 멀티플레이어
      또 다른 Figma나 Google Docs 같은 걸 만들고 싶다면 Durable Objects의 프로그래밍 모델이 매우 편리함
      이 글이 더 자세히 다룸: https://digest.browsertech.com/archive/browsertech-digest-cl...
      이 오래된 글도 꽤 관련 있음: http://ithare.com/scaling-stateful-objects/
      Figma 멀티플레이어 글을 읽고 “내가 필요한 게 대충 이건데”라고 생각한 사람에게 Durable Objects는 잘 맞을 것 같음: https://www.figma.com/blog/rust-in-production-at-figma/
      다른 접근도 있음. 예전에 WebRTC 위에서 CRDT를 써본 적이 있는데 정말 미래 기술처럼 느껴졌음
      하지만 클라우드 어딘가의 WebSocket과 단일 클래스 인스턴스라는 기반보다 훨씬 복잡함
    • 데이터베이스는 프로그래밍 언어처럼 성숙 속도가 극도로 느린 영역인데, Postgres에서 벗어나는 모든 것이 반짝이는 힙스터 기술인 건 아님
      데이터와 동작을 같은 위치에 두는 것은 실제로 복잡도를 정량적으로 줄여줌
      지연 시간과 대역폭 걱정을 없애므로 운영상 우려와 개발상 우려를 모두 줄임. 유명한 N+1 문제의 영향도 크게 줄어듦
      네트워크로 연결된 Postgres가 다른 이유로 더 낫다고 주장할 수 있고, 실제로 맞을 수도 있음
      하지만 SQLite는 지루하고 예측 가능하기로는 손꼽히며, 알려진 강점도 분명함
      그래서 서버에서도 인기가 올라가는 중임
      다만 Durable Objects가 제안하는 것처럼 작은 데이터베이스를 많이 만드는 방식은 별로 좋아하지 않음
      NoSQL 악몽을 떠올리게 하고, 관계형 데이터베이스의 중요한 불변식들을 깨뜨릴 수 있음
      SQLite는 Cloudflare의 D1 제품처럼 단일형 데이터베이스로 쓰는 편이 훨씬 낫다고 봄
    • 1번에 해당한다면 Cloudflare와 이야기하면 됨
      Cloudflare는 좋은 고객 성공 사례가 필요하고, 훌륭한 엔지니어들이 성공 사례를 얻는 대가로 이게 어떻게 동작하고 도움이 되는지, 버그는 어떻게 처리할지 함께 봐줄 가능성이 큼
      검증되면 서비스 관계로 바뀌겠지만, 초기에는 파트너십에 가까움
    • 여기서의 구분은 디버깅 가능성과 내부 관찰 가능성 축으로 봄
      내부 도구처럼 성능 튜닝이나 깊은 내부 관찰이 필요 없는 서비스는 많음
      이런 곳에서는 서버리스 프레임워크가 배포에 쓰는 시간을 많이 줄여주기 때문에 잘 맞는다고 봄
      빠르면 좋지만 핵심 요구사항인 경우는 드묾
      보통 핵심 요구사항은 빠르게 만들 수 있고 유지보수가 적다는 것임
      Cloudflare가 여기서 좋은 개발자 경험을 제공할 가능성은 있지만, 그게 그들의 주된 홍보 포인트는 아니며 이런 개발을 빠르게 해준다는 경쟁 서비스도 많음
      반면 높은 디버깅 가능성과 내부 관찰이 필요한 경우에는 이런 서비스가 잘 맞지 않는다고 봄
      어떤 지표를 얻을 수 있는지, 일부 Durable Objects가 느리면 왜 그런지 이해할 정보가 있는지, 고칠 수 있는지, 로깅은 어떻고 비용은 얼마인지가 중요함
      이런 서비스는 스타트업이 첫날부터 영리한 분산 시스템을 만들어 확장 고민을 미루기에는 좋을 수 있음
      하지만 스케일업 규모 회사라면 더 깊이 들여다볼 수 있는 무언가로 옮기고 싶어질 것 같고, 그 전환은 어려울 것임
    • 이 분석은 매우 통찰력 있음. 대부분의 사람은 시스템을 설계하기 전에 정확히 이런 분석을 해봐야 한다고 봄
      다른 사람들이 말했듯 사용처는 멀티플레이어이고, 앱이 좋게 느껴지려면 모두가 변경사항을 최대한 빨리 봐야 하기 때문임
      더 넓게 보면 저장소 업계는 오랫동안 일관적이고, 지연 시간이 낮고, 다중 사용자에 맞는 것을 만들려 해왔음
      물리적으로도 일관성지연 시간 사이에는 보통 절충이 있어서 매우 어려움
      그래서 여러 모델을 시도하는 중이고, 그 실험 중 상당수는 SQLite를 중심으로 일어나고 있음. 전부는 아니며 Yugabyte나 Cockroach 같은 예외도 있음
  • DO 설계에는 계속 감탄하게 됨
    이런 방식이 뭔가 잘못됐다는 반사적 반응이 나오기 쉽지만, 실제로는 많은 실제 제품이 암묵적으로 이렇게 구성되어 있다고 봄
    트랜잭션 일관성이 필요한 원자적 단위마다, 매우 낮은 규모에서 복잡한 작업이 많이 수행되는 구조임
    돌이켜보면 Framer에서 멀티플레이어 지원 프로젝트를 위해 만든 것도 지금 DO가 하는 것의 더 응용된 버전이었음
    편집 내용이 60 FPS로 복제되면서 모든 클라이언트에 올바른 순서로 적용됐고, JSON 객체 편집의 WAL 비슷한 것도 결국 만들었음
    그래서 프로젝트 인스턴스가 크래시나도 백업이 아무 일 없던 것처럼 이어받을 수 있었음
    거대한 프로젝트 데이터 객체에 JSON 패치를 커밋할 시간이 없었더라도, 여기 설명처럼 N번 업데이트 또는 M초마다 처리하는 방식이었음

  • 어리석은 질문일 수 있지만, 이런 구성에서 스키마 마이그레이션은 어떻게 처리하는지 궁금함
    이해하기로는 테넌트별 데이터베이스, 혹은 그보다 더 잘게 쪼개진 구조를 목표로 하는 것 같음
    스키마 마이그레이션을 제정신으로 처리하는 방법이 있는지, 아니면 이런 데이터베이스는 더 단기적이라 삭제될 때까지 여러 DB/DO 버전을 지원해야 한다는 기대인지 궁금함
    머릿속으로는 사용자별 DO를 둔 북마크 서비스를 만들면 재미있을 것 같음
    하지만 기존 테이블에 새 필드를 추가하려는 순간, 그 변경을 각 개별 DO에 적용해야 하는 꽤 까다로운 문제를 만나게 됨
    어쩌면 그 예시는 너무 오래 지속되는 데이터라서, 이 설계는 더 일시적인 용도를 위한 것일 수도 있음
    실제로 해본 사람이 있다면 어떻게 하고 있는지 알고 싶음

    • 마이그레이션은 직접 구현해야 할 것임
      Python으로 작성한 SQLite용 버전이 있지만 Durable Objects에서 실행할 수 있을지는 모르겠음. WASM과 PyOdide를 통하면 가능할 수도 있음
      아니면 JavaScript로 포팅해야 함
      https://github.com/simonw/sqlite-migrate
  • 요즘 Cloudflare가 개발자들에게 Worker보다 DO를 모든 곳에 쓰라고 밀고 있는 것 같음
    Worker의 WebSocket 연결도 약 30초 뒤 타임아웃되고, 권장 방식은 DO를 쓰는 것임

    • Cloudflare Workers에서 WebSocket 연결에는 항상 Durable Objects가 권장 방식이었던 것 같음. 적어도 기억하기로는 그럼
      원래 채팅 데모도 2020년부터 DO와 WebSocket을 사용했음: https://github.com/cloudflare/workers-chat-demo
  • 이 말은 DO용 SQLite가 실패하는 DO 상황에서 최대 10초치 데이터를 잃을 수 있다는 뜻인가?

    • “10초 창을 넘는 내구성을 보장하기 위해, 쓰기는 커밋되는 즉시 가까운 별도 데이터센터의 복제본 5개로 전달되고, 그중 3개가 확인해야 쓰기가 승인된다”는 대목이 있음
      여기서 Simon은 “beyond”가 아니라 within이라고 말하려던 것 같음