From 7ef0ec759493fec68891e85bd05313a9ea27c849 Mon Sep 17 00:00:00 2001 From: kappa Date: Wed, 28 Jan 2026 20:26:23 +0900 Subject: [PATCH] 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 --- migrations/003_MIGRATION_NOTES.md | 161 ++++++++++++++++++++++++++ migrations/003_add_server_tables.sql | 54 +++++++++ migrations/003_server_sessions.sql | 17 +++ migrations/003_server_sessions_d1.sql | 21 ++++ migrations/004_add_terminated_at.sql | 5 + 5 files changed, 258 insertions(+) create mode 100644 migrations/003_MIGRATION_NOTES.md create mode 100644 migrations/003_add_server_tables.sql create mode 100644 migrations/003_server_sessions.sql create mode 100644 migrations/003_server_sessions_d1.sql create mode 100644 migrations/004_add_terminated_at.sql diff --git a/migrations/003_MIGRATION_NOTES.md b/migrations/003_MIGRATION_NOTES.md new file mode 100644 index 0000000..1bd7ffe --- /dev/null +++ b/migrations/003_MIGRATION_NOTES.md @@ -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 +export async function saveServerSession(kv: KVNamespace, userId: string, session: ServerSession): Promise +export async function deleteServerSession(kv: KVNamespace, userId: string): Promise + +// After (D1) +export async function getServerSession(db: D1Database, userId: string): Promise +export async function saveServerSession(db: D1Database, userId: string, session: ServerSession): Promise +export async function deleteServerSession(db: D1Database, userId: string): Promise + +// New +export async function cleanupExpiredSessions(db: D1Database): Promise +``` + +**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 diff --git a/migrations/003_add_server_tables.sql b/migrations/003_add_server_tables.sql new file mode 100644 index 0000000..a28351a --- /dev/null +++ b/migrations/003_add_server_tables.sql @@ -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); diff --git a/migrations/003_server_sessions.sql b/migrations/003_server_sessions.sql new file mode 100644 index 0000000..7bd5498 --- /dev/null +++ b/migrations/003_server_sessions.sql @@ -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); diff --git a/migrations/003_server_sessions_d1.sql b/migrations/003_server_sessions_d1.sql new file mode 100644 index 0000000..c3bab72 --- /dev/null +++ b/migrations/003_server_sessions_d1.sql @@ -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) diff --git a/migrations/004_add_terminated_at.sql b/migrations/004_add_terminated_at.sql new file mode 100644 index 0000000..3c2699a --- /dev/null +++ b/migrations/004_add_terminated_at.sql @@ -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;