chore: add server provisioning migrations

- 003_add_server_tables.sql: server_orders, server_instances tables
- 003_server_sessions.sql: KV-based session tracking
- 004_add_terminated_at.sql: track instance termination time

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-28 20:26:23 +09:00
parent 5ba555864a
commit 7ef0ec7594
5 changed files with 258 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
# Migration 003: Server Sessions from KV to D1
**Date**: 2026-01-28
**Type**: Session Storage Migration
**Status**: Ready to Deploy
## Overview
Migrated server consultation session management from KV Namespace to D1 Database for better consistency, reliability, and integrated data management.
## Changes Summary
### 1. Database Schema (`schema.sql` + `migrations/003_server_sessions_d1.sql`)
**New Table**: `server_sessions`
```sql
CREATE TABLE IF NOT EXISTS server_sessions (
user_id TEXT PRIMARY KEY,
status TEXT NOT NULL CHECK(status IN ('gathering', 'recommending', 'selecting', 'ordering', 'completed')),
collected_info TEXT,
last_recommendation TEXT,
messages TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_server_sessions_expires ON server_sessions(expires_at);
```
### 2. Code Changes
**Updated Files**:
- `/src/server-agent.ts` - Session management functions (getServerSession, saveServerSession, deleteServerSession)
- `/src/openai-service.ts` - Session check in generateOpenAIResponse()
- `/src/tools/server-tool.ts` - start_consultation, continue_consultation, cancel_consultation actions
- `/src/routes/handlers/callback-handler.ts` - server_order and server_cancel callbacks
**Function Signature Changes**:
```typescript
// Before (KV)
export async function getServerSession(kv: KVNamespace, userId: string): Promise<ServerSession | null>
export async function saveServerSession(kv: KVNamespace, userId: string, session: ServerSession): Promise<void>
export async function deleteServerSession(kv: KVNamespace, userId: string): Promise<void>
// After (D1)
export async function getServerSession(db: D1Database, userId: string): Promise<ServerSession | null>
export async function saveServerSession(db: D1Database, userId: string, session: ServerSession): Promise<void>
export async function deleteServerSession(db: D1Database, userId: string): Promise<void>
// New
export async function cleanupExpiredSessions(db: D1Database): Promise<number>
```
**Removed**:
- All `[SESSION DEBUG]` logging (unnecessary with D1's strong consistency)
- KV constants: `SESSION_TTL`, `SESSION_KEY_PREFIX`
**Added**:
- `SESSION_TTL_MS` constant (3600 * 1000 = 1 hour)
- `cleanupExpiredSessions()` function for cron cleanup
- Automatic expiry checking in queries (`WHERE expires_at > ?`)
### 3. Key Implementation Details
**Session Storage**:
- JSON serialization for complex fields (collected_info, last_recommendation, messages)
- Automatic TTL management with expires_at column
- Strong consistency (no eventual consistency issues)
**Error Handling**:
- Graceful JSON parsing with try-catch
- Null checks for expired/missing sessions
- Proper logging without debug noise
**Performance**:
- Primary key on user_id for fast lookups
- Index on expires_at for efficient cleanup
- Single query for session retrieval
### 4. Migration Path
**Automatic Migration**:
- Old KV sessions will expire naturally (1 hour TTL)
- New sessions are created in D1 automatically
- No manual data migration needed
**Zero Downtime**:
- D1 table creation is idempotent (IF NOT EXISTS)
- Code handles missing sessions gracefully
- Users can restart consultation if session expires
### 5. Benefits
**Before (KV Namespace)**:
- Eventual consistency (delayed reads)
- Complex debugging (async replication)
- Separate data store (fragmented)
- Manual cleanup needed
**After (D1 Database)**:
- Strong consistency (immediate reads)
- Simple debugging (SQL queries)
- Integrated storage (single database)
- Automatic cleanup (expires_at + cron)
### 6. Deployment Steps
```bash
# 1. Apply migration (creates table)
wrangler d1 execute telegram-conversations --file=migrations/003_server_sessions_d1.sql
# 2. Verify table creation
wrangler d1 execute telegram-conversations --command "SELECT name FROM sqlite_master WHERE type='table' AND name='server_sessions'"
# 3. Deploy code changes
npm run deploy
# 4. Test server consultation flow
# Send message: "서버 추천"
# Verify session is created in D1
wrangler d1 execute telegram-conversations --command "SELECT * FROM server_sessions LIMIT 5"
```
### 7. Rollback Plan
If issues occur:
1. **Code Rollback**: Revert to previous commit (KV-based code)
2. **Database**: Drop table if needed
```sql
DROP TABLE IF EXISTS server_sessions;
DROP INDEX IF EXISTS idx_server_sessions_expires;
```
**Note**: Old KV sessions are unaffected (will expire naturally)
### 8. Testing Checklist
- [x] Migration SQL syntax validated
- [x] Schema.sql updated
- [x] All code references updated (env.SESSION_KV → env.DB)
- [x] Logging cleaned up (no debug noise)
- [ ] Local testing (`npm run dev`)
- [ ] Production deployment
- [ ] Session creation test
- [ ] Session retrieval test
- [ ] Session expiry test
- [ ] Concurrent session handling
### 9. Notes
**Important**:
- `server_order_confirm:` and `delete_confirm:` sessions remain in KV (not migrated, separate feature)
- Troubleshoot agent still uses KV (separate migration needed if required)
- D1 binding (`env.DB`) already exists in project
**Future Improvements**:
- Consider migrating troubleshoot sessions to D1
- Add session analytics (query D1 for session stats)
- Implement session history/audit log

View File

@@ -0,0 +1,54 @@
-- Migration 003: Add server provisioning tables
-- Date: 2026-01-28
-- Description: Add server_orders and user_servers tables for Queue-based server provisioning
-- Server orders table (provisioning requests)
CREATE TABLE IF NOT EXISTS server_orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
telegram_user_id TEXT NOT NULL,
spec_id TEXT NOT NULL, -- 서버 스펙 ID (plan_name)
region TEXT NOT NULL, -- 리전 코드
label TEXT, -- 서버 라벨 (선택)
price_paid INTEGER NOT NULL, -- 지불 금액 (원)
status TEXT NOT NULL DEFAULT 'pending', -- pending, provisioning, completed, failed, refunded
provider TEXT NOT NULL, -- anvil
instance_id TEXT, -- 생성된 인스턴스 ID
ip_address TEXT, -- 할당된 IP
root_password TEXT, -- 초기 root 비밀번호 (암호화 권장)
error_message TEXT, -- 실패 시 에러 메시지
provisioned_at DATETIME, -- 프로비저닝 완료 시각
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- User servers table (active servers)
CREATE TABLE IF NOT EXISTS user_servers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
order_id INTEGER NOT NULL UNIQUE,
provider TEXT NOT NULL,
instance_id TEXT NOT NULL,
label TEXT,
spec_id TEXT NOT NULL,
region TEXT NOT NULL,
ip_address TEXT,
status TEXT NOT NULL DEFAULT 'running', -- running, stopped, deleted
monthly_price INTEGER NOT NULL,
expires_at DATETIME, -- 다음 결제일
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (order_id) REFERENCES server_orders(id)
);
-- Indexes for server_orders
CREATE INDEX IF NOT EXISTS idx_server_orders_user ON server_orders(user_id);
CREATE INDEX IF NOT EXISTS idx_server_orders_status ON server_orders(status);
CREATE INDEX IF NOT EXISTS idx_server_orders_telegram ON server_orders(telegram_user_id);
-- Indexes for user_servers
CREATE INDEX IF NOT EXISTS idx_user_servers_user ON user_servers(user_id);
CREATE INDEX IF NOT EXISTS idx_user_servers_status ON user_servers(status);
CREATE INDEX IF NOT EXISTS idx_user_servers_instance ON user_servers(provider, instance_id);

View File

@@ -0,0 +1,17 @@
-- Server consultation sessions table
CREATE TABLE IF NOT EXISTS server_sessions (
user_id TEXT PRIMARY KEY,
status TEXT NOT NULL DEFAULT 'gathering',
collected_info TEXT, -- JSON
last_recommendation TEXT, -- JSON
messages TEXT, -- JSON
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL
);
-- Index for cleanup job
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON server_sessions(expires_at);
-- Index for status queries
CREATE INDEX IF NOT EXISTS idx_sessions_status ON server_sessions(user_id, status);

View File

@@ -0,0 +1,21 @@
-- Migration 003: Server Sessions to D1 Database
-- Migrate server session management from KV Namespace to D1 Database
-- Date: 2026-01-28
-- Create server_sessions table
CREATE TABLE IF NOT EXISTS server_sessions (
user_id TEXT PRIMARY KEY,
status TEXT NOT NULL CHECK(status IN ('gathering', 'recommending', 'selecting', 'ordering', 'completed')),
collected_info TEXT,
last_recommendation TEXT,
messages TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL
);
-- Create index for expiry cleanup
CREATE INDEX IF NOT EXISTS idx_server_sessions_expires ON server_sessions(expires_at);
-- Note: Existing KV sessions will be automatically migrated on first access
-- Old sessions in KV will expire naturally (1 hour TTL)

View File

@@ -0,0 +1,5 @@
-- Migration 004: Add terminated_at column
-- Date: 2026-01-28
-- Description: Add terminated_at column for server termination tracking
ALTER TABLE server_orders ADD COLUMN terminated_at DATETIME;