dd/SQL_VIEWER_GUIDE.md

8.2 KiB

Durable Object SQLite 조회 가이드

Durable Object의 내장 SQLite 데이터베이스를 조회하는 여러 가지 방법을 제공합니다.

📋 목차

  1. 웹 인터페이스로 조회
  2. API 엔드포인트로 조회
  3. WebSocket으로 조회
  4. 사용 가능한 테이블

1. 웹 인터페이스로 조회

가장 간편한 방법입니다. 브라우저에서 바로 SQLite 데이터를 확인할 수 있습니다.

접속 방법

http://localhost:8788/sql-viewer

기능

  • 📊 DB 정보: 데이터베이스 크기, 테이블 목록, 레코드 수 등
  • 👥 사용자 목록: 모든 사용자 조회
  • 🎲 배팅 목록: 최근 배팅 내역 조회
  • ⚙️ 커스텀 쿼리: 원하는 SQL 쿼리 직접 실행

예시 화면

┌─────────────────────────────────────┐
│  🔍 Durable Object SQLite Viewer   │
├─────────────────────────────────────┤
│ [📊 DB 정보] [👥 사용자] [🎲 배팅]  │
│                                     │
│ 결과:                               │
│ {                                   │
│   "databaseSize": 12345,            │
│   "userCount": 10,                  │
│   "betCount": 50                    │
│ }                                   │
└─────────────────────────────────────┘

2. API 엔드포인트로 조회

프로그래밍 방식으로 데이터를 조회할 수 있습니다.

2.1 데이터베이스 정보 조회

# HTTP GET
curl http://localhost:8788/sql-api/info

응답 예시:

{
  "databaseSize": 24576,
  "userCount": 3,
  "betCount": 15,
  "currentGameId": "550e8400-e29b-41d4-a716-446655440000",
  "tables": [
    { "name": "user" },
    { "name": "current_bet" }
  ]
}

2.2 모든 사용자 조회

curl http://localhost:8788/sql-api/users

응답 예시:

[
  {
    "id": "user-123",
    "nickname": "홍길동",
    "email": "hong@example.com",
    "joinGameCount": 5,
    "capital": 15000
  }
]

2.3 배팅 내역 조회

# 모든 배팅 (최근 100개)
curl http://localhost:8788/sql-api/bets

# 특정 게임의 배팅
curl "http://localhost:8788/sql-api/bets?gameId=550e8400-e29b-41d4-a716-446655440000"

응답 예시:

[
  {
    "id": 1,
    "gameId": "550e8400-e29b-41d4-a716-446655440000",
    "diceNum": 12,
    "userId": "user-123",
    "betType": "odd",
    "amount": 1000,
    "isWin": 1,
    "reward": 2000
  }
]

2.4 커스텀 SQL 쿼리 실행

curl -X POST http://localhost:8788/sql-api/query \
  -H "Content-Type: application/json" \
  -d '{"query": "SELECT * FROM user WHERE capital > 10000"}'

3. WebSocket으로 조회

실시간 게임 중에 WebSocket 연결을 통해 SQL을 조회할 수 있습니다.

JavaScript 예시

// WebSocket 연결
const ws = new WebSocket('ws://localhost:8788/api/counter');

// DB 정보 조회
ws.send(JSON.stringify({
  type: 'sqlQuery',
  query: 'info'
}));

// 모든 사용자 조회
ws.send(JSON.stringify({
  type: 'sqlQuery',
  query: 'users'
}));

// 커스텀 쿼리
ws.send(JSON.stringify({
  type: 'sqlQuery',
  query: 'SELECT * FROM current_bet WHERE amount > 5000'
}));

// 결과 수신
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  
  if (data.type === 'sqlResult') {
    console.log('SQL 결과:', data.data);
  } else if (data.type === 'sqlError') {
    console.error('SQL 에러:', data.error);
  }
};

4. 사용 가능한 테이블

4.1 user 테이블

사용자 정보를 저장합니다.

컬럼 타입 설명
id TEXT 사용자 고유 ID (PRIMARY KEY)
nickname TEXT 닉네임
email TEXT 이메일 (UNIQUE)
joinGameCount INTEGER 참여한 게임 횟수
capital INTEGER 현재 자본금

예제 쿼리:

-- 모든 사용자 조회
SELECT * FROM user;

-- 자본금이 10000 이상인 사용자
SELECT * FROM user WHERE capital >= 10000;

-- 가장 많이 참여한 사용자
SELECT * FROM user ORDER BY joinGameCount DESC LIMIT 10;

4.2 current_bet 테이블

배팅 정보를 저장합니다.

컬럼 타입 설명
id INTEGER 배팅 고유 ID (PRIMARY KEY, AUTO INCREMENT)
gameId TEXT 게임 고유 ID
diceNum INTEGER 주사위 합계
userId TEXT 사용자 ID (FOREIGN KEY)
betType TEXT 배팅 타입 (odd/even/big/small)
amount INTEGER 배팅 금액
isWin INTEGER 승리 여부 (0=패배, 1=승리)
reward INTEGER 보상 금액

예제 쿼리:

-- 모든 배팅 조회
SELECT * FROM current_bet;

-- 특정 게임의 배팅
SELECT * FROM current_bet WHERE gameId = 'xxx';

-- 사용자별 총 배팅액
SELECT userId, SUM(amount) as totalBet 
FROM current_bet 
GROUP BY userId;

-- 승리한 배팅만 조회
SELECT * FROM current_bet WHERE isWin = 1;

-- 게임별 통계
SELECT 
  gameId, 
  COUNT(*) as betCount,
  SUM(amount) as totalAmount,
  SUM(CASE WHEN isWin = 1 THEN 1 ELSE 0 END) as winCount
FROM current_bet 
GROUP BY gameId;

5. 고급 쿼리 예제

5.1 사용자별 승률 계산

SELECT 
  u.nickname,
  COUNT(cb.id) as totalBets,
  SUM(CASE WHEN cb.isWin = 1 THEN 1 ELSE 0 END) as wins,
  CAST(SUM(CASE WHEN cb.isWin = 1 THEN 1 ELSE 0 END) AS FLOAT) / COUNT(cb.id) * 100 as winRate
FROM user u
LEFT JOIN current_bet cb ON u.id = cb.userId
GROUP BY u.id, u.nickname
HAVING COUNT(cb.id) > 0;

5.2 배팅 타입별 통계

SELECT 
  betType,
  COUNT(*) as count,
  SUM(amount) as totalAmount,
  AVG(amount) as avgAmount,
  SUM(CASE WHEN isWin = 1 THEN 1 ELSE 0 END) as winCount
FROM current_bet
GROUP BY betType;

5.3 최근 게임 분석

SELECT 
  gameId,
  diceNum,
  COUNT(*) as betCount,
  SUM(amount) as totalBet,
  SUM(reward) as totalReward
FROM current_bet
WHERE gameId = (SELECT gameId FROM current_bet ORDER BY id DESC LIMIT 1)
GROUP BY gameId, diceNum;

6. 주의사항

보안

  • 프로덕션 환경: /sql/query 엔드포인트는 개발용입니다. 프로덕션에서는 제거하거나 인증을 추가하세요.
  • SQL Injection: 사용자 입력을 직접 쿼리에 넣지 마세요. 항상 파라미터 바인딩을 사용하세요.

성능

  • 인덱스 활용: gameIduserId에 인덱스가 설정되어 있습니다.
  • LIMIT 사용: 큰 테이블 조회 시 항상 LIMIT를 사용하세요.

비용

  • Cloudflare Durable Objects의 SQLite 사용 시 요금 정책을 확인하세요.
  • Row read/write에 따라 비용이 발생합니다.

7. 문제 해결

"COUNTER binding not found" 에러

  • wrangler.jsonc에 Durable Object 바인딩이 올바르게 설정되어 있는지 확인하세요.

빈 결과가 반환됨

  • 아직 사용자나 배팅이 생성되지 않았을 수 있습니다.
  • 게임에 접속하여 배팅을 해보세요.

WebSocket 연결 실패

  • 개발 서버가 실행 중인지 확인하세요: pnpm run dev

8. 실전 예제

게임 시작 시 사용자 생성

// counter-do.ts의 fetch 메서드에서
this.createOrUpdateUser(
  session.id,
  'Player1',
  'player1@example.com',
  10000
);

배팅 저장

// startNoMoreBetPeriod 메서드에서 주사위를 굴린 후
const diceSum = this.dice1! + this.dice2! + this.dice3!;

this.sessions.forEach((session) => {
  if (session.oddBet > 0) {
    this.saveBet({
      gameId: this.gameId!,
      diceNum: diceSum,
      userId: session.id,
      betType: 'odd',
      amount: session.oddBet,
      isWin: diceSum % 2 === 1 ? 1 : 0,
      reward: diceSum % 2 === 1 ? session.oddBet * 2 : 0
    });
  }
});

완성! 🎉

이제 Durable Object의 SQLite 데이터베이스를 다양한 방법으로 조회할 수 있습니다.