Skip to content

Cloudflare Durable Objects

Serverless stateful backend를 구현할 수 있는 서비스.

Workers의 일종이고, Workers Paid Plan이어야 사용 가능함. 5$ 기본 요금이 있는데, 여기에 durable objects기본 사용량도 포함되어있음. 사용사례로 실시간 채팅, 멀티플레이 게임을 든다.

EC2컴퓨팅 돌리듯이 시간당 청구되는 옵션이 있으니, 다 썼으면 잘 끄고 다녀야한다. 특히, 연결된 스토리지를 비우지 않으면(deleteAll()) 꺼지지 않으니 주의 필요.

wrangler.toml

Durable Objects를 사용하기 위해 바인딩 설정을 확인하세요.

[[durable_objects.bindings]]
name = "MY_CHAT_ROOM"
class_name = "MyChatRoom"

[[migrations]]
tag = "v1"
new_classes = ["MyChatRoom"]

protobuf와 함께 사용하는 방법

ts-proto

import { UserProfile } from "./generated/user"; // ts-proto로 생성된 파일

export class UserDO extends DurableObject {
  async saveUser(data: UserProfile) {
    // 1. 데이터를 바이너리로 인코딩
    const encoded = UserProfile.encode(data).finish();
    // 2. DO Storage에 직접 저장 (바이너리 저장이 JSON보다 저렴)
    await this.ctx.storage.put("user_data", encoded);
  }

  async getUser(): Promise<UserProfile | undefined> {
    const stored = await this.ctx.storage.get<Uint8Array>("user_data");
    if (!stored) return undefined;
    // 3. 디코딩하여 객체로 복원
    return UserProfile.decode(stored);
  }
}

Connect RPC

API 통신까지 고려한다면 추천... ?

클라이언트와 직접 gRPC 스타일로 통신해야 한다면 Connect를 추천 한다고함.... <- 확인 필요.

protobufjs

기존에 가장 많이 쓰이던 라이브러리입니다. 모든 기능이 들어있는 전체 패키지 대신 protobufjs/light 또는 생성된 정적 코드만 사용하는 방식을 권장합니다.

Hibernation API

가장 중요한 점은 this.ctx.acceptWebSocket(ws)를 호출하여 상태를 위임하는 것입니다.

export class MyChatRoom extends DurableObject {
  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
  }

  async fetch(request: Request) {
    // 1. WebSocket 업그레이드 요청 확인
    const webSocketPair = new WebSocketPair();
    const [client, server] = Object.values(webSocketPair);

    // 2. 서버 측 소켓 수락 및 'Hibernation' 모드 활성화
    this.ctx.acceptWebSocket(server);

    return new Response(null, { status: 101, webSocket: client });
  }

  // 3. 메시지가 도착했을 때만 DO가 깨어나서 이 메서드를 실행합니다.
  async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {
    // 메시지 처리 및 브로드캐스트
    this.ctx.getWebSockets().forEach(peer => {
      if (peer !== ws) peer.send(`상대방: ${message}`);
    });
  }

  // 4. 연결이 끊겼을 때 실행
  async webSocketClose(ws: WebSocket, code: number, reason: string, wasClean: boolean) {
    console.log("연결 종료");
  }
}

상태(State) 관리 주의사항

Hibernation 모드에서는 DO가 수시로 메모리에서 내려갔다가(잠들었다가) 다시 올라옵니다. 따라서 클래스 멤버 변수에 데이터를 저장하면 안 됩니다.

  • 잘못된 예: this.users = [] (잠들면 초기화됨)
  • 올바른 예: this.ctx.storage.put() 또는 ws.serializeAttachment()를 사용하여 상태를 영속화해야 합니다.

See also

Favorite site